d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
192 lines
4.9 KiB
JavaScript
192 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const initSqlJs = require('sql.js');
|
|
|
|
const { applyMigrations, getAppliedMigrations } = require('./migrations');
|
|
const { createQueryApi } = require('./queries');
|
|
const { assertValidEntity, validateEntity } = require('./schema');
|
|
|
|
const DEFAULT_STATE_STORE_RELATIVE_PATH = path.join('.claude', 'ecc', 'state.db');
|
|
|
|
function resolveStateStorePath(options = {}) {
|
|
if (options.dbPath) {
|
|
if (options.dbPath === ':memory:') {
|
|
return options.dbPath;
|
|
}
|
|
return path.resolve(options.dbPath);
|
|
}
|
|
|
|
const homeDir = options.homeDir || process.env.HOME || os.homedir();
|
|
return path.join(homeDir, DEFAULT_STATE_STORE_RELATIVE_PATH);
|
|
}
|
|
|
|
/**
|
|
* Wraps a sql.js Database with a better-sqlite3-compatible API surface so
|
|
* that the rest of the state-store code (migrations.js, queries.js) can
|
|
* operate without knowing which driver is in use.
|
|
*
|
|
* IMPORTANT: sql.js db.export() implicitly ends any active transaction, so
|
|
* we must defer all disk writes until after the transaction commits.
|
|
*/
|
|
function wrapSqlJsDatabase(rawDb, dbPath) {
|
|
let inTransaction = false;
|
|
|
|
function saveToDisk() {
|
|
if (dbPath === ':memory:' || inTransaction) {
|
|
return;
|
|
}
|
|
const data = rawDb.export();
|
|
const buffer = Buffer.from(data);
|
|
fs.writeFileSync(dbPath, buffer);
|
|
}
|
|
|
|
const db = {
|
|
exec(sql) {
|
|
rawDb.run(sql);
|
|
saveToDisk();
|
|
},
|
|
|
|
pragma(pragmaStr) {
|
|
try {
|
|
rawDb.run(`PRAGMA ${pragmaStr}`);
|
|
} catch (_error) {
|
|
// Ignore unsupported pragmas (e.g. WAL for in-memory databases).
|
|
}
|
|
},
|
|
|
|
prepare(sql) {
|
|
return {
|
|
all(...positionalArgs) {
|
|
const stmt = rawDb.prepare(sql);
|
|
if (positionalArgs.length === 1 && typeof positionalArgs[0] !== 'object') {
|
|
stmt.bind([positionalArgs[0]]);
|
|
} else if (positionalArgs.length > 1) {
|
|
stmt.bind(positionalArgs);
|
|
}
|
|
|
|
const rows = [];
|
|
while (stmt.step()) {
|
|
rows.push(stmt.getAsObject());
|
|
}
|
|
stmt.free();
|
|
return rows;
|
|
},
|
|
|
|
get(...positionalArgs) {
|
|
const stmt = rawDb.prepare(sql);
|
|
if (positionalArgs.length === 1 && typeof positionalArgs[0] !== 'object') {
|
|
stmt.bind([positionalArgs[0]]);
|
|
} else if (positionalArgs.length > 1) {
|
|
stmt.bind(positionalArgs);
|
|
}
|
|
|
|
let row = null;
|
|
if (stmt.step()) {
|
|
row = stmt.getAsObject();
|
|
}
|
|
stmt.free();
|
|
return row;
|
|
},
|
|
|
|
run(namedParams) {
|
|
const stmt = rawDb.prepare(sql);
|
|
if (namedParams && typeof namedParams === 'object' && !Array.isArray(namedParams)) {
|
|
const sqlJsParams = {};
|
|
for (const [key, value] of Object.entries(namedParams)) {
|
|
sqlJsParams[`@${key}`] = value === undefined ? null : value;
|
|
}
|
|
stmt.bind(sqlJsParams);
|
|
}
|
|
stmt.step();
|
|
stmt.free();
|
|
saveToDisk();
|
|
},
|
|
};
|
|
},
|
|
|
|
transaction(fn) {
|
|
return (...args) => {
|
|
rawDb.run('BEGIN');
|
|
inTransaction = true;
|
|
try {
|
|
const result = fn(...args);
|
|
rawDb.run('COMMIT');
|
|
inTransaction = false;
|
|
saveToDisk();
|
|
return result;
|
|
} catch (error) {
|
|
try {
|
|
rawDb.run('ROLLBACK');
|
|
} catch (_rollbackError) {
|
|
// Transaction may already be rolled back.
|
|
}
|
|
inTransaction = false;
|
|
throw error;
|
|
}
|
|
};
|
|
},
|
|
|
|
close() {
|
|
saveToDisk();
|
|
rawDb.close();
|
|
},
|
|
};
|
|
|
|
return db;
|
|
}
|
|
|
|
async function openDatabase(SQL, dbPath) {
|
|
if (dbPath !== ':memory:') {
|
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
}
|
|
|
|
let rawDb;
|
|
if (dbPath !== ':memory:' && fs.existsSync(dbPath)) {
|
|
const fileBuffer = fs.readFileSync(dbPath);
|
|
rawDb = new SQL.Database(fileBuffer);
|
|
} else {
|
|
rawDb = new SQL.Database();
|
|
}
|
|
|
|
const db = wrapSqlJsDatabase(rawDb, dbPath);
|
|
db.pragma('foreign_keys = ON');
|
|
try {
|
|
db.pragma('journal_mode = WAL');
|
|
} catch (_error) {
|
|
// Some SQLite environments reject WAL for in-memory or readonly contexts.
|
|
}
|
|
return db;
|
|
}
|
|
|
|
async function createStateStore(options = {}) {
|
|
const dbPath = resolveStateStorePath(options);
|
|
const SQL = await initSqlJs();
|
|
const db = await openDatabase(SQL, dbPath);
|
|
const appliedMigrations = applyMigrations(db);
|
|
const queryApi = createQueryApi(db);
|
|
|
|
return {
|
|
dbPath,
|
|
close() {
|
|
db.close();
|
|
},
|
|
getAppliedMigrations() {
|
|
return getAppliedMigrations(db);
|
|
},
|
|
validateEntity,
|
|
assertValidEntity,
|
|
...queryApi,
|
|
_database: db,
|
|
_migrations: appliedMigrations,
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_STATE_STORE_RELATIVE_PATH,
|
|
createStateStore,
|
|
resolveStateStorePath,
|
|
};
|