UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

232 lines (231 loc) 8.13 kB
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 };