openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
428 lines (427 loc) • 20.3 kB
JavaScript
import { r as getAsyncWorkSignal } from "./async-work-scope-DDiH0P7W.mjs";
import { r as enableNodeSqliteKyselyStatementCache } from "./kysely-sync-cache-state-DbAMR_DD.mjs";
import { s as getSqliteRuntimeCapabilities } from "./bun-sqlite-library-DlNkYpEb.mjs";
import { _ as getSqlitePinnedReadSnapshot, h as runSqliteReadOperationSync, l as isSqliteSchemaAdmissionCold, t as admitSqliteSchema } from "./sqlite-schema-facts-BVf0E_0z.mjs";
import { d as readDatabasePathIdentitySync, r as assertExistingDatabaseIdentity } from "./sqlite-worker-identity-BRNRpRyc.mjs";
import { r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs";
import { t as isPromiseLike } from "./promise-like-D7-l5Fsp.mjs";
import { a as createSqliteLifecycleAggregateError, i as SqliteCoordinatorError, s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs";
import { i as isSqliteCorruptionError } from "./sqlite-error-diagnostics-CWRyv4lz.mjs";
import { t as acquireSqliteSnapshotReadToken } from "./sqlite-snapshot-staging-DskavSq5.mjs";
import { u as retainSnapshotTempDirectory } from "./sqlite-readonly-location-cleanup-CoQX1AVj.mjs";
import { s as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db-contract-BYv0nUQr.mjs";
import { t as assertTransactionUsable } from "./sqlite-transaction-DBRLvCDM.mjs";
import { a as readSqliteUserVersion, n as createNewerSqliteSchemaVersionError } from "./sqlite-user-version-DLMl1A7W.mjs";
import { a as registerSqliteCacheExitClose, o as runInSqliteMaintenanceContext } from "./sqlite-wal-DfKCrDTQ.mjs";
import { d as isOpenClawDatabaseMaintenanceResourceOwned, m as observeOpenClawDatabaseMaintenanceResource, u as getOpenClawDatabaseMaintenanceScope } from "./openclaw-state-db-async-lifecycle-DwFy2ZTk.mjs";
import { r as normalizeOpenClawStateSchemaReadError } from "./openclaw-state-db-schema-migration-required-DBia8oMG.mjs";
import { n as assertSupportedStateSchemaVersion } from "./openclaw-state-db-schema-version-DZI_59qS.mjs";
import { t as SQLITE_IDLE_HANDLE_TTL_MS } from "./sqlite-handle-lifecycle-dWd9h3ii.mjs";
import { p as openClawStateDatabaseCache, v as registerOpenClawStateDatabaseAsyncResource, y as registerOpenClawStateDatabaseLifecycleListener } from "./openclaw-state-db-cache-CmYLFRSt.mjs";
import { a as isExistingOpenClawStateSchema } from "./openclaw-state-db-schema-policy-B65l9xTe.mjs";
import { a as openTrackedStateDatabaseResult, n as invalidateOpenClawStateRuntimeIntegrity } from "./openclaw-state-db-integrity-admission-D2ZPZec-.mjs";
import { n as assertExistingOpenClawStateRuntimeSchema } from "./openclaw-state-db-existing-schema-CdYR46xe.mjs";
import { a as getSqliteWorkerStateIntegrityAdmission, o as runWithSqliteWorkerStateContext } from "./sqlite-worker-state-context-G3Xbcj1W.mjs";
import path from "node:path";
//#region src/state/openclaw-state-db-read-schema.ts
function assertStateReadSchemaForPolicy(database, pathname, existingSchema, integrityPolicy, readContentVersionRow) {
if (existingSchema) assertExistingOpenClawStateRuntimeSchema(database, pathname, getSqliteWorkerStateIntegrityAdmission(), integrityPolicy);
else assertSupportedStateSchemaVersion(database, pathname, void 0, readContentVersionRow);
}
function admitStateReadSchemaFacts(database, pathname) {
try {
admitSqliteSchema(database, (userVersion) => assertSupportedStateSchemaVersion(database, pathname, {
userVersion,
contentVersion: userVersion
}));
} catch (error) {
let version;
try {
version = readSqliteUserVersion(database);
} catch {
throw normalizeOpenClawStateSchemaReadError(error, pathname);
}
if (version > 20) throw createNewerSqliteSchemaVersionError("OpenClaw state database", pathname, version, 20);
throw normalizeOpenClawStateSchemaReadError(error, pathname);
}
}
//#endregion
//#region src/state/openclaw-state-db-read-connection.ts
const retainedReaders = /* @__PURE__ */ new Map();
const corruptedReaders = /* @__PURE__ */ new WeakSet();
let unregisterExitClose;
/** Ordered partial results may carry corruption as data; the same reader owner still retires it. */
function retireOpenClawStateReadConnectionAfterCorruption(database) {
invalidateOpenClawStateRuntimeIntegrity(database);
corruptedReaders.add(database);
}
function retireReader(reader) {
if (retainedReaders.get(reader.identity.key) !== reader) return;
clearTimeout(reader.idleTimer);
reader.retiring = true;
reader.connection.close();
retainedReaders.delete(reader.identity.key);
reader.unregisterDirectClose?.();
if (!retainedReaders.size) {
unregisterExitClose?.();
unregisterExitClose = void 0;
}
}
function ownDirectReaderLifecycle(reader) {
if (reader.unregisterDirectClose) return;
const { identity } = reader;
const unregisterResource = registerOpenClawStateDatabaseAsyncResource({ async close(closingIdentity) {
if (!closingIdentity || closingIdentity.key === identity.key || closingIdentity.canonicalPath === identity.canonicalPath) retireReader(reader);
} });
const unregisterLifecycle = registerOpenClawStateDatabaseLifecycleListener((event) => {
if ((event.kind === "closed" || event.kind === "terminal-failure") && (event.path === reader.connection.database.path || event.identity?.key === identity.key)) retireReader(reader);
});
reader.unregisterDirectClose = () => {
unregisterResource();
unregisterLifecycle();
};
}
/** Prepare a live authority reader; each later use executes only its caller's synchronous query. */
function prepareOpenClawStateDirectReader(context) {
const pathname = context.admission.databasePath;
const identity = { ...context.admission.identity };
const signal = getAsyncWorkSignal();
const assertSource = () => {
signal?.throwIfAborted();
getAsyncWorkSignal()?.throwIfAborted();
context.maintenanceScope?.assertReadAdmission();
context.admission.assertCurrent();
openClawStateDatabaseCache.assertOpenClawStateDatabaseOpenAllowed(pathname, "cached-read");
assertExistingDatabaseIdentity(pathname, identity.key, identity.birthtime);
const integrity = context.stateIntegrity;
if (integrity && Atomics.load(new BigInt64Array(integrity.revision), 0) !== integrity.epoch || context.existingSchemaPath !== void 0 && (!integrity || Atomics.load(new BigInt64Array(integrity.proof), 0) === -1n)) throw new Error("Direct shared-state reader requires current integrity admission");
};
const inContext = (operation) => {
const run = () => runWithSqliteWorkerStateContext(context, operation);
return context.runInCapturedSchemaScope ? context.runInCapturedSchemaScope(run) : run();
};
const reader = inContext(() => {
assertSource();
if (!identity.key.startsWith("file:")) throw new Error("Direct shared-state reader requires an existing physical source");
const previous = retainedReaders.get(identity.key);
const borrowed = borrowStateReadConnection(pathname, identity.key);
if (borrowed.status === "unavailable") throw borrowed.error;
const retained = retainedReaders.get(identity.key);
if (!retained || retained.connection.database.db !== borrowed.value.database.db) {
borrowed.value.close();
throw new Error("Direct shared-state reader lost its retained connection");
}
if (retained.connection.database.db.isTransaction || getSqlitePinnedReadSnapshot(retained.connection.database.db)) throw new Error("Direct shared-state reader cannot admit a transaction or snapshot");
try {
const existingSchema = isExistingOpenClawStateSchema(pathname, retained.connection.database.db);
const admitted = retained.directAdmission;
if (!admitted || admitted.existingSchema !== existingSchema || admitted.integrity?.revision !== context.stateIntegrity?.revision || admitted.integrity?.epoch !== context.stateIntegrity?.epoch) {
runSqliteReadOperationSync(retained.connection.database.db, () => {
admitStateReadSchemaFacts(retained.connection.database.db, pathname);
assertStateReadSchemaForPolicy(retained.connection.database.db, pathname, existingSchema, existingSchema ? "require-proof" : void 0);
});
retained.directAdmission = {
existingSchema,
integrity: context.stateIntegrity
};
}
ownDirectReaderLifecycle(retained);
if (retained !== previous) context.maintenanceScope?.own(retained, "shared-resources", () => {
const closingScope = getOpenClawDatabaseMaintenanceScope();
if (closingScope && isOpenClawDatabaseMaintenanceResourceOwned(retained, closingScope)) retireReader(retained);
});
assertSource();
scheduleReaderRetirement(retained);
return retained;
} catch (error) {
try {
retireReader(retained);
} catch (cleanupError) {
throwSqliteLifecycleErrors([error, cleanupError], "Direct shared-state reader admission and cleanup failed");
}
throw error;
}
});
const assertCurrent = () => {
assertSource();
const db = reader.connection.database.db;
if (retainedReaders.get(identity.key) !== reader || reader.retiring || !db.isOpen) throw new Error("Direct shared-state reader is closed");
if (db.isTransaction || getSqlitePinnedReadSnapshot(db)) throw new Error("Direct shared-state reader cannot use a transaction or snapshot");
assertTransactionUsable(db);
};
return { read(operation) {
return inContext(() => {
assertCurrent();
try {
const value = operation(reader.connection.database);
if (isPromiseLike(value)) throw new SqliteCoordinatorError("Direct shared-state read must remain synchronous");
assertCurrent();
scheduleReaderRetirement(reader);
return value;
} catch (error) {
if (isSqliteCorruptionError(error)) {
invalidateOpenClawStateRuntimeIntegrity(reader.connection.database.db);
try {
retireReader(reader);
} catch (cleanupError) {
throwSqliteLifecycleErrors([error, cleanupError], "Direct shared-state read and cleanup failed");
}
}
throw error;
}
});
} };
}
function scheduleReaderRetirement(reader) {
if (!getSqliteRuntimeCapabilities().explicitSqliteCloseReleasesNativeResources || retainedReaders.get(reader.identity.key) !== reader) return;
clearTimeout(reader.idleTimer);
reader.idleTimer = runInSqliteMaintenanceContext(() => setTimeout(() => {
try {
retireReader(reader);
} catch (error) {
process.emitWarning(`Idle shared-state reader cleanup failed: ${String(error)}`);
scheduleReaderRetirement(reader);
}
}, SQLITE_IDLE_HANDLE_TTL_MS));
reader.idleTimer.unref?.();
}
/** The host joins this receipt before allowing replacement or deletion of live state. */
function closeRetainedOpenClawStateReadConnections(identity) {
const errors = [];
for (const reader of retainedReaders.values()) if (identity === void 0 || reader.identity.key === identity) try {
retireReader(reader);
} catch (error) {
errors.push(error);
}
throwSqliteLifecycleErrors(errors, "Retained shared-state reader cleanup failed.");
}
function borrowStateReadConnection(pathname, expectedIdentity) {
isExistingOpenClawStateSchema(pathname);
const identity = readDatabasePathIdentitySync(pathname);
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(pathname, expectedIdentity);
for (const previous of retainedReaders.values()) if (previous.identity.canonicalPath === identity.canonicalPath && previous.identity.key !== identity.key) retireReader(previous);
if (!identity.key.startsWith("file:")) return openStateReadConnectionResult(pathname, pathname, expectedIdentity);
let reader = retainedReaders.get(identity.key);
if (reader?.retiring || reader && !reader.connection.database.db.isOpen) {
retireReader(reader);
reader = void 0;
}
if (!reader) {
const opening = openStateReadConnectionResult(pathname, pathname, identity.key);
if (opening.status === "unavailable") return opening;
reader = {
connection: opening.value,
identity,
retiring: false
};
retainedReaders.set(identity.key, reader);
unregisterExitClose ??= registerSqliteCacheExitClose(closeRetainedOpenClawStateReadConnections);
}
const retained = reader;
observeOpenClawDatabaseMaintenanceResource(retained);
clearTimeout(retained.idleTimer);
return {
status: "available",
value: {
database: {
db: retained.connection.database.db,
path: pathname
},
close(keep) {
if (keep && retained.connection.database.db.isOpen && !retained.connection.database.db.isTransaction) scheduleReaderRetirement(retained);
else retireReader(retained);
return true;
}
}
};
}
var SnapshotCleanupIncompleteError = class extends Error {};
function assertStateReadSchema(database, pathname, integrityPolicy) {
assertStateReadSchemaForPolicy(database, pathname, isExistingOpenClawStateSchema(pathname, database), integrityPolicy);
}
function withOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false, readContentVersionRow) {
const result = readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection, readContentVersionRow);
if (result.status === "unavailable") throw result.error;
return result.value;
}
/** Return a failed read only after its native reader and admission have settled. */
function readOpenClawStateReadOnlyLocation(operation, pathname, source, openStateSchemaReadAdmission, expectedIdentity, snapshotRoot, retainConnection = false, readContentVersionRow) {
const opening = retainConnection && source === pathname && !snapshotRoot ? borrowStateReadConnection(pathname, expectedIdentity) : openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, true);
if (opening.status === "unavailable") return opening;
const opened = opening.value;
const errors = [];
let closeAdmission;
let result;
try {
closeAdmission = openStateSchemaReadAdmission?.(opened.database.db);
const existingSchema = isExistingOpenClawStateSchema(pathname, opened.database.db);
try {
result = {
status: "available",
value: runSqliteReadOperationSync(opened.database.db, () => {
const coldAdmission = !existingSchema && isSqliteSchemaAdmissionCold(opened.database.db);
admitStateReadSchemaFacts(opened.database.db, pathname);
if (coldAdmission) return runSqliteReadOperationSync(opened.database.db, () => {
assertStateReadSchemaForPolicy(opened.database.db, pathname, existingSchema, void 0, readContentVersionRow);
return operation(opened.database);
}, "fresh");
assertStateReadSchemaForPolicy(opened.database.db, pathname, existingSchema, void 0, readContentVersionRow);
return operation(opened.database);
})
};
} catch (error) {
if (isSqliteCorruptionError(error)) retireOpenClawStateReadConnectionAfterCorruption(opened.database.db);
result = {
status: "unavailable",
error
};
}
const location = typeof source === "string" ? source : source.location;
if (result.status === "available" && location === pathname && isPromiseLike(result.value)) throw new SqliteCoordinatorError("SQLite source read must remain synchronous");
assertTransactionUsable(opened.database.db);
} catch (error) {
errors.push(error);
}
try {
closeAdmission?.();
} catch (error) {
errors.push(error);
}
try {
const allowDeferredSnapshotCleanup = typeof source !== "string" && errors.length === 0 && result?.status === "available" && !getSqliteRuntimeCapabilities().explicitSqliteCloseReleasesNativeResources;
if (!opened.close(errors.length === 0 && result?.status === "available" && !corruptedReaders.has(opened.database.db)) && !allowDeferredSnapshotCleanup) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
} catch (error) {
errors.push(error);
}
if (errors.length) {
if (result?.status === "unavailable" && !errors.includes(result.error)) errors.unshift(result.error);
throwSqliteLifecycleErrors(errors, "Shared-state read and reader cleanup failed.");
}
return result;
}
function openOpenClawStateReadOnlyLocation(pathname, source) {
const connection = openOpenClawStateReadConnection(pathname, source);
try {
runSqliteReadOperationSync(connection.database.db, () => {
admitStateReadSchemaFacts(connection.database.db, pathname);
assertStateReadSchema(connection.database.db, pathname);
});
} catch (error) {
try {
connection.close();
} catch (cleanupError) {
throwSqliteLifecycleErrors([error, cleanupError], "Shared-state reader admission and cleanup failed.");
}
throw error;
}
return connection;
}
/** Own one native reader; callers retain their runtime or maintenance schema policy. */
function openOpenClawStateReadConnection(pathname, source, expectedIdentity, snapshotRoot) {
const result = openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot);
if (result.status === "unavailable") throw result.error;
return result.value;
}
function openStateReadConnectionResult(pathname, source, expectedIdentity, snapshotRoot, checkSchemaPolicy = false) {
const snapshot = typeof source === "string" ? void 0 : source;
const location = typeof source === "string" ? source : source.location;
const options = {
readOnly: true,
timeout: OPENCLAW_SQLITE_BUSY_TIMEOUT_MS
};
let releaseToken;
const cleanupFailedOpen = (error) => {
const errors = [error];
try {
releaseToken?.();
} catch (cleanupError) {
errors.push(cleanupError);
}
try {
if (snapshot && !snapshot.cleanup()) errors.push(new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete."));
} catch (cleanupError) {
errors.push(cleanupError);
}
if (errors.length > 1) throw createSqliteLifecycleAggregateError(errors, "Shared-state reader open and cleanup failed.", error);
};
let native;
try {
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity);
releaseToken = snapshotRoot ? acquireSqliteSnapshotReadToken(snapshotRoot) : void 0;
if (checkSchemaPolicy) isExistingOpenClawStateSchema(pathname);
if (location === pathname) native = openTrackedStateDatabaseResult(pathname, options);
else try {
native = {
status: "available",
database: openNodeSqliteDatabase(location, options)
};
} catch (error) {
native = {
status: "unavailable",
error
};
}
} catch (error) {
cleanupFailedOpen(error);
throw error;
}
if (native.status === "unavailable") {
cleanupFailedOpen(native.error);
return native;
}
const db = native.database;
let closed = false;
let closing = false;
const database = {
db,
path: pathname,
afterClose: () => {
releaseToken?.();
if (snapshot && !snapshot.cleanup()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
}
};
const connection = {
database: {
db,
path: pathname
},
snapshotSource: snapshot ? { retain() {
const assertCurrent = () => {
if (closing || closed) throw new Error("Shared-state snapshot source is closing or closed");
};
assertCurrent();
return {
location: snapshot.location,
cleanupRoot: snapshot.cleanupRoot,
assertCurrent,
release: retainSnapshotTempDirectory(snapshot.cleanupRoot ?? path.dirname(snapshot.location))
};
} } : void 0,
close() {
if (closed) return false;
closing = true;
const errors = openClawStateDatabaseCache.closeOpenClawStateDatabaseHandle(database);
if (errors.length === 1 && errors[0] instanceof SnapshotCleanupIncompleteError) return false;
throwSqliteLifecycleErrors(errors, "Shared-state reader cleanup failed.");
closed = true;
return true;
}
};
try {
enableNodeSqliteKyselyStatementCache(db);
if (expectedIdentity !== void 0) assertExistingDatabaseIdentity(location, expectedIdentity);
} catch (error) {
try {
if (!connection.close()) throw new SnapshotCleanupIncompleteError("Shared-state snapshot cleanup is incomplete.");
} catch (cleanupError) {
throw createSqliteLifecycleAggregateError([error, cleanupError], "Shared-state reader identity and cleanup failed.", error);
}
throw error;
}
return {
status: "available",
value: connection
};
}
//#endregion
export { withOpenClawStateReadOnlyLocation as a, prepareOpenClawStateDirectReader as i, openOpenClawStateReadConnection as n, openOpenClawStateReadOnlyLocation as r, assertStateReadSchema as t };