Z ZPI 文档 v0.3.8
介绍页

源码

packages/desktop/src/main/transcript-schema.ts

typescript79 行查看原文
import { realpathSync } from "node:fs";
import { basename, dirname, relative, resolve } from "node:path";
import type { DatabaseSync } from "node:sqlite";

// Current rebuildable transcript index schema.
export const transcriptSchema = `
      CREATE TABLE IF NOT EXISTS transcript_state(path TEXT PRIMARY KEY, generation TEXT NOT NULL, header TEXT NOT NULL, size INTEGER NOT NULL, modified REAL NOT NULL, inode TEXT NOT NULL, seq INTEGER NOT NULL, parent_id TEXT, current_run TEXT NOT NULL, cache_read INTEGER NOT NULL, cache_input INTEGER NOT NULL, activity REAL);
      CREATE TABLE IF NOT EXISTS transcript_entries(path TEXT NOT NULL, seq INTEGER NOT NULL, entry_id TEXT NOT NULL, type TEXT NOT NULL, role TEXT, offset INTEGER NOT NULL, length INTEGER NOT NULL, run_id TEXT, file_change INTEGER NOT NULL, kept_seq INTEGER, PRIMARY KEY(path,seq), UNIQUE(path,entry_id));
      CREATE INDEX IF NOT EXISTS transcript_entries_type ON transcript_entries(path,type,seq);
      CREATE TABLE IF NOT EXISTS transcript_runs(path TEXT NOT NULL, run_id TEXT NOT NULL, start INTEGER NOT NULL, start_end INTEGER NOT NULL, end INTEGER, end_end INTEGER, ordinal INTEGER NOT NULL, auxiliary INTEGER NOT NULL, accepted_seq INTEGER, accepted_end INTEGER, queue_item_id TEXT, PRIMARY KEY(path,run_id));
      CREATE TABLE IF NOT EXISTS transcript_calls(path TEXT NOT NULL, seq INTEGER NOT NULL, run_id TEXT NOT NULL, ordinal INTEGER NOT NULL, start INTEGER NOT NULL, end INTEGER NOT NULL, complete INTEGER NOT NULL, PRIMARY KEY(path,seq));
      CREATE TABLE IF NOT EXISTS transcript_projections(path TEXT NOT NULL, key TEXT NOT NULL, seq INTEGER NOT NULL, value TEXT NOT NULL, PRIMARY KEY(path,key));
      CREATE TABLE IF NOT EXISTS transcript_checkpoints(path TEXT NOT NULL, seq INTEGER NOT NULL, state TEXT NOT NULL, PRIMARY KEY(path,seq));
      CREATE TABLE IF NOT EXISTS transcript_journals(path TEXT NOT NULL, journal_id TEXT NOT NULL, seq INTEGER NOT NULL, anchor_id TEXT, committed INTEGER NOT NULL, PRIMARY KEY(path,journal_id,seq));
      CREATE TABLE IF NOT EXISTS transcript_anchors(path TEXT NOT NULL, offset INTEGER NOT NULL, value TEXT NOT NULL, PRIMARY KEY(path,offset));
      CREATE TABLE IF NOT EXISTS transcript_pending_journals(path TEXT NOT NULL, journal_id TEXT NOT NULL, first_seq INTEGER NOT NULL, anchor_id TEXT, PRIMARY KEY(path,journal_id));
      CREATE TABLE IF NOT EXISTS transcript_search(path TEXT NOT NULL, seq INTEGER NOT NULL, run_id TEXT NOT NULL, text TEXT NOT NULL, PRIMARY KEY(path,seq));
    `;

export const transcriptIndexes = `
CREATE INDEX IF NOT EXISTS transcript_calls_open ON transcript_calls(path) WHERE complete=0;
CREATE INDEX IF NOT EXISTS transcript_runs_start ON transcript_runs(path,start);
`;

export const transcriptTables = [
  "state",
  "entries",
  "runs",
  "calls",
  "projections",
  "checkpoints",
  "journals",
  "search",
  "anchors",
  "pending_journals",
] as const;

const searchFunctions = new WeakSet<DatabaseSync>();

/** Missing projections are rebuildable; existing incompatible definitions are rejected at startup. */
export function ensureTranscriptSchema(db: DatabaseSync, readOnly = false): boolean {
  if (!searchFunctions.has(db)) {
    // SQLite's built-in lower() only folds ASCII. Match the renderer's Unicode search semantics.
    db.function("zpi_lower", { deterministic: true }, (value) =>
      typeof value === "string" ? value.toLocaleLowerCase() : null,
    );
    searchFunctions.add(db);
  }
  const missing = transcriptTables.some(
    (table) =>
      !db.prepare("SELECT 1 FROM sqlite_schema WHERE type='table' AND name=?").get(`transcript_${table}`),
  );
  if (readOnly) {
    if (missing) throw new Error("storage: Missing prepared transcript cache");
    return false;
  }
  db.exec(transcriptSchema + transcriptIndexes);
  if (missing) db.exec("DELETE FROM transcript_state");
  return missing;
}

export function deleteTranscriptIndex(db: DatabaseSync, path: string): void {
  for (const table of transcriptTables) db.prepare(`DELETE FROM transcript_${table} WHERE path=?`).run(path);
}

// SQLite resolves /var to /private/var on macOS. Use the same directory identity for file keys.
export function canonicalTranscriptPath(path: string): string {
  try {
    return resolve(realpathSync(dirname(path)), basename(path));
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
    return resolve(path);
  }
}
export function transcriptPathKey(db: DatabaseSync, path: string): string {
  const location = db.location();
  return location ? relative(dirname(location), canonicalTranscriptPath(path)) : path;
}