UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

134 lines (133 loc) • 7.5 kB
import { d as createNewerSqliteSchemaVersionError, p as readSqliteUserVersion, tt as sqlitePrimaryResultCode, v as executeWithCachedStatement } from "./sqlite-worker-identity-CfwbX7eo.mjs"; import { F as getNodeSqliteKysely, L as prepareSqliteQuerySync, M as createSqliteQueryCache, h as getSqliteReadScopeRevision, m as getAdmittedSqliteSchemaFacts, n as normalizeOpenClawStateSchemaReadError } from "./openclaw-state-db-schema-migration-required-zCjR9CdD.mjs"; //#region src/infra/sqlite-schema-issues.ts /** A completed inspection proved schema drift; native read failures retain their own type. */ var SqliteSchemaMismatchError = class extends Error { constructor(..._args) { super(..._args); this.name = "SqliteSchemaMismatchError"; } }; const ISSUE_DESCRIPTIONS = { "missing-table": "missing table", "missing-column": "column definitions differ for", "unexpected-column": "column definitions differ for", "column-definition-drift": "column definitions differ for", "table-constraint-drift": "table constraints differ for", "table-definition-drift": "table definition differs for", "missing-or-drifted-index": "missing or drifted index", "unexpected-unique-index": "unexpected unique index", "missing-or-drifted-trigger": "missing or drifted trigger", "unexpected-trigger": "unexpected trigger", "virtual-table-definition-drift": "virtual table definition differs for", "table-options-drift": "table options differ for" }; function isColumnIssue(code) { return code === "column-definition-drift" || code === "missing-column" || code === "unexpected-column"; } function defaultIssueMessage(code, objectName) { if (!Object.hasOwn(ISSUE_DESCRIPTIONS, code)) throw new Error("Unsupported SQLite schema issue code", { cause: code }); const target = isColumnIssue(code) ? objectName.split(".", 1)[0] : objectName; return `${ISSUE_DESCRIPTIONS[code]} ${target}`; } function createSqliteSchemaIssue(code, objectName, message) { return { code, objectName, message: message ?? defaultIssueMessage(code, objectName) }; } function legacySqliteSchemaIssueMessages(issues) { const columnIssueTables = new Set(issues.filter((issue) => isColumnIssue(issue.code)).map((issue) => issue.objectName.split(".", 1)[0])); return [...new Set(issues.filter((issue) => issue.code !== "table-constraint-drift" || !columnIssueTables.has(issue.objectName)).map((issue) => issue.message))]; } function throwSqliteSchemaMismatches(databaseLabel, mismatches) { const shown = mismatches.slice(0, 8); if (mismatches.length > shown.length) shown.push(`${mismatches.length - shown.length} additional mismatch(es)`); throw new SqliteSchemaMismatchError(`SQLite schema is incomplete or noncanonical for ${databaseLabel}: ${shown.join("; ")}; run openclaw doctor --fix to repair it.`); } //#endregion //#region src/state/openclaw-state-db-schema-helpers.ts function tableHasColumn(db, tableName, columnName) { return readTableColumns(db, tableName).has(columnName); } function readTableColumns(db, tableName) { const rows = db.prepare(`PRAGMA table_info(${tableName})`).all(); return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : [])); } /** Inspect only failed queries; native unavailability must not become a schema refusal. */ function classifySqliteTableReadError(db, tableName, columnNames, error) { if (sqlitePrimaryResultCode(error) !== 1) return error; try { const existing = readTableColumns(db, tableName); if (existing.size > 0 && columnNames.some((column) => !existing.has(column))) return new SqliteSchemaMismatchError(`SQLite table ${tableName} is missing required columns; run openclaw doctor --fix to repair it.`, { cause: error }); } catch {} return error; } function tablePrimaryKeyColumns(db, tableName) { return db.prepare(`PRAGMA table_info(${tableName})`).all().filter((row) => Number(row.pk ?? 0) > 0 && typeof row.name === "string").toSorted((left, right) => Number(left.pk ?? 0) - Number(right.pk ?? 0)).map((row) => row.name); } function tableExists(db, tableName) { const schema = getAdmittedSqliteSchemaFacts(db); if (schema) return schema.tables.has(tableName); return executeWithCachedStatement(db, "SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?", [tableName], (statement) => statement.get(tableName))?.ok === 1; } function ensureColumn(db, tableName, columnSql) { const columnName = columnSql.trim().split(/\s+/, 1)[0]; if (!columnName || !tableExists(db, tableName) || tableHasColumn(db, tableName, columnName)) return false; db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnSql};`); return true; } //#endregion //#region src/state/openclaw-state-db-schema-version.ts const CONTENT_VERSION_KEY = "state.schema.contentVersion"; const contentVersionQueries = createSqliteQueryCache((db) => { const query = prepareSqliteQuerySync(db, () => getNodeSqliteKysely(db).selectFrom("config_machine_state").select("value_json").where("state_key", "=", CONTENT_VERSION_KEY)); let retained; return { readRow: () => query().rows[0], readVersion(readRow = readStateSchemaContentVersionRow) { const revision = getSqliteReadScopeRevision(db); if (revision && retained?.revision === revision) return retained.version; retained = void 0; const version = parseContentVersion(tableExists(db, "config_machine_state") ? readRow(db)?.value_json : void 0); if (revision && getSqliteReadScopeRevision(db) === revision) retained = { revision, version }; return version; } }; }); /** Content and its marker commit together, even while older readers retain their version floor. */ function readStateSchemaContentVersion(db, published, readRow) { const schema = getAdmittedSqliteSchemaFacts(db); const version = published ?? schema?.userVersion ?? readSqliteUserVersion(db); return Math.max(version, contentVersionQueries(db).readVersion(readRow)); } function readStateSchemaContentVersionRow(db) { return contentVersionQueries(db).readRow(); } function parseContentVersion(valueJson) { if (valueJson === void 0) return 0; let contentVersion; try { contentVersion = valueJson === null ? null : JSON.parse(valueJson); } catch (cause) { throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`, { cause }); } if (typeof contentVersion !== "number" || !Number.isSafeInteger(contentVersion) || contentVersion < 0) throw new SqliteSchemaMismatchError(`Invalid shared state schema content version in ${CONTENT_VERSION_KEY}.`); return contentVersion; } function assertSupportedStateSchemaVersion(db, pathname, prepared, readRow) { try { const userVersion = prepared?.userVersion ?? getAdmittedSqliteSchemaFacts(db)?.userVersion ?? readSqliteUserVersion(db); const contentVersion = prepared?.contentVersion ?? (userVersion > 20 ? userVersion : readStateSchemaContentVersion(db, userVersion, readRow)); if (contentVersion > 20) throw createNewerSqliteSchemaVersionError("OpenClaw state database", pathname, contentVersion, 20); return userVersion; } catch (error) { throw normalizeOpenClawStateSchemaReadError(error, pathname); } } //#endregion export { ensureColumn as a, tablePrimaryKeyColumns as c, legacySqliteSchemaIssueMessages as d, throwSqliteSchemaMismatches as f, classifySqliteTableReadError as i, SqliteSchemaMismatchError as l, assertSupportedStateSchemaVersion as n, tableExists as o, readStateSchemaContentVersion as r, tableHasColumn as s, CONTENT_VERSION_KEY as t, createSqliteSchemaIssue as u };