openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
232 lines (231 loc) • 8.13 kB
JavaScript
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js";
import { _ as queryErrorHandlerByDatabase, g as kyselyByDatabase, h as clearNodeSqliteKyselyCacheForDatabase, y as statementCacheSymbol } from "./node-sqlite-BpQX3W0e.js";
import { toUSVString } from "node:util";
import { InsertQueryNode, Kysely, SelectQueryNode, SqliteDialect, sql } from "kysely";
//#region src/infra/kysely-sync.ts
const statementInvalidationSymbol = Symbol.for("openclaw.kyselySyncStatementInvalidation");
const statementCacheEnabledSymbol = Symbol.for("openclaw.kyselySyncStatementCacheEnabled");
const authorizerActiveSymbol = Symbol.for("openclaw.kyselySyncAuthorizerActive");
const statementCacheCapacity = 32;
const statementCacheEntryBytes = 65536;
const compileOnlySqliteDialect = new SqliteDialect({ database: async () => {
throw new Error("getNodeSqliteKysely() returns a compile-only Kysely facade; use executeSqliteQuerySync() to execute node:sqlite queries.");
} });
function getNodeSqliteKysely(db) {
const existing = kyselyByDatabase.get(db);
if (existing) return existing;
const kysely = new Kysely({ dialect: compileOnlySqliteDialect });
kyselyByDatabase.set(db, kysely);
return kysely;
}
/** A single bound set avoids SQLite parameter and JS variadic-call limits. */
function sqliteStringSet(values) {
const encoded = JSON.stringify(values.map(toUSVString)).replace(/\\(?:\\|u0000)/g, (escape) => escape === "\\u0000" ? "\\x00" : escape);
return sql`(SELECT value FROM json_each(${encoded}))`;
}
function reportNodeSqliteKyselyQueryError(db, error) {
try {
queryErrorHandlerByDatabase.get(db)?.(error);
} catch {}
}
function installStatementInvalidation(owner) {
if (owner[statementInvalidationSymbol]) return;
if (typeof owner.setAuthorizer === "function") {
const setAuthorizer = owner.setAuthorizer.bind(owner);
Object.defineProperty(owner, "setAuthorizer", {
configurable: true,
writable: true,
value(callback) {
setAuthorizer(callback);
this[authorizerActiveSymbol] = callback !== null;
delete this[statementCacheSymbol];
}
});
}
if (typeof owner.deserialize === "function") {
const deserialize = owner.deserialize.bind(owner);
Object.defineProperty(owner, "deserialize", {
configurable: true,
writable: true,
value(...args) {
try {
deserialize(...args);
} finally {
delete this[statementCacheSymbol];
}
}
});
}
if (typeof owner.close === "function") {
const close = owner.close.bind(owner);
Object.defineProperty(owner, "close", {
configurable: true,
writable: true,
value() {
clearNodeSqliteKyselyCacheForDatabase(this);
return close();
}
});
}
if (typeof owner[Symbol.dispose] === "function") {
const dispose = owner[Symbol.dispose].bind(owner);
Object.defineProperty(owner, Symbol.dispose, {
configurable: true,
writable: true,
value() {
clearNodeSqliteKyselyCacheForDatabase(this);
return dispose();
}
});
}
Object.defineProperty(owner, statementInvalidationSymbol, {
configurable: true,
value: true
});
}
/**
* Enable bounded statement caching for a lifecycle-owned database that has not
* installed an authorizer before this call.
*/
function enableNodeSqliteKyselyStatementCache(db) {
const owner = db;
installStatementInvalidation(owner);
owner[statementCacheEnabledSymbol] = true;
}
function queryFitsStatementCache(sql, parameters) {
let bytes = Buffer.byteLength(sql);
if (bytes > statementCacheEntryBytes) return false;
for (const parameter of parameters) {
if (typeof parameter === "string") bytes += Buffer.byteLength(parameter);
else if (ArrayBuffer.isView(parameter)) bytes += parameter.byteLength;
if (bytes > statementCacheEntryBytes) return false;
}
return true;
}
function executeWithCachedStatement(db, sql, parameters, execute) {
const owner = db;
installStatementInvalidation(owner);
if (!owner[statementCacheEnabledSymbol] || owner[authorizerActiveSymbol] || !queryFitsStatementCache(sql, parameters)) return execute(db.prepare(sql));
let cache = owner[statementCacheSymbol];
if (!cache) {
cache = {
statements: /* @__PURE__ */ new Map(),
candidates: /* @__PURE__ */ new Set(),
active: /* @__PURE__ */ new WeakSet()
};
Object.defineProperty(owner, statementCacheSymbol, {
configurable: true,
value: cache
});
}
const cached = cache.statements.get(sql);
let statement;
if (cached && !cache.active.has(cached)) {
cache.statements.delete(sql);
cache.statements.set(sql, cached);
statement = cached;
} else {
statement = db.prepare(sql);
if (!cached && cache.candidates.delete(sql)) {
cache.statements.set(sql, statement);
pruneMapToMaxSize(cache.statements, statementCacheCapacity);
} else if (!cached) {
cache.candidates.add(sql);
if (cache.candidates.size > statementCacheCapacity) {
const oldestCandidate = cache.candidates.values().next().value;
if (oldestCandidate !== void 0) cache.candidates.delete(oldestCandidate);
}
}
}
cache.active.add(statement);
try {
return execute(statement);
} finally {
cache.active.delete(statement);
}
}
/** Execute a compiled Kysely query synchronously against node:sqlite. */
function executeCompiledSqliteQuerySync(db, compiledQuery) {
const parameters = compiledQuery.parameters;
try {
return executeWithCachedStatement(db, compiledQuery.sql, parameters, (statement) => {
if (SelectQueryNode.is(compiledQuery.query) || statement.columns().length > 0) {
const iterator = statement.iterate(...parameters);
try {
return { rows: [...iterator] };
} catch (error) {
try {
iterator.return?.();
} catch {}
throw error;
}
}
const { changes, lastInsertRowid } = statement.run(...parameters);
const result = {
numAffectedRows: BigInt(changes),
rows: []
};
if (InsertQueryNode.is(compiledQuery.query) && changes > 0) return {
...result,
insertId: BigInt(lastInsertRowid)
};
return result;
});
} catch (error) {
reportNodeSqliteKyselyQueryError(db, error);
throw error;
}
}
/** Compile and execute a Kysely query synchronously. */
function executeSqliteQuerySync(db, query) {
return executeCompiledSqliteQuerySync(db, query.compile());
}
/** Compile fixed SQL and fresh bindings without taking ownership of a native statement. */
function compileSqliteQueryBindings(build) {
const bindings = /* @__PURE__ */ new Map();
const compiled = build((read) => {
const marker = Symbol("sqlite-query-parameter");
bindings.set(marker, read);
return sql`${marker}`;
}).compile();
const readers = compiled.parameters.map((value) => bindings.get(value) ?? (() => value));
return {
compiled,
bind: (params) => readers.map((read) => read(params))
};
}
/** Compile a fixed query once; bind fresh values through the normal sync executor on each call. */
function prepareSqliteQuerySync(db, build) {
const { compiled, bind } = compileSqliteQueryBindings(build);
return (params) => executeCompiledSqliteQuerySync(db, {
...compiled,
parameters: bind(params)
});
}
/** Compile and lazily iterate a Kysely query synchronously against node:sqlite. */
function* iterateSqliteQuerySync(db, query) {
const compiledQuery = query.compile();
try {
const statement = db.prepare(compiledQuery.sql);
if (!SelectQueryNode.is(compiledQuery.query) && statement.columns().length === 0) return;
const parameters = compiledQuery.parameters;
const iterator = statement.iterate(...parameters);
try {
yield* iterator;
} catch (error) {
try {
iterator.return?.();
} catch {}
throw error;
}
} catch (error) {
reportNodeSqliteKyselyQueryError(db, error);
throw error;
}
}
/** Execute a Kysely query synchronously and return its first row. */
function executeSqliteQueryTakeFirstSync(db, query) {
return executeSqliteQuerySync(db, query).rows[0];
}
//#endregion
export { getNodeSqliteKysely as a, sqliteStringSet as c, executeSqliteQueryTakeFirstSync as i, enableNodeSqliteKyselyStatementCache as n, iterateSqliteQuerySync as o, executeSqliteQuerySync as r, prepareSqliteQuerySync as s, compileSqliteQueryBindings as t };