UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

249 lines (248 loc) 9.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KVTransaction = void 0; const pg_format_1 = __importDefault(require("pg-format")); /** * Utility function to format SQL commands with parameters. * Replaces $1, $2, etc., with %L or %s based on parameter types. */ function formatSqlCommand(sql, params) { const formatParams = []; // Replace $1, $2, etc., with %L or %s and collect parameters const formattedSql = sql.replace(/\$(\d+)/g, (match, p1) => { const index = parseInt(p1, 10) - 1; // Convert $1 to index 0 const param = params[index]; if (param === null || param === undefined) { return 'NULL'; } let specifier; if (typeof param === 'number') { specifier = '%s'; } else if (typeof param === 'string') { specifier = '%L'; } else if (typeof param === 'boolean') { specifier = '%L'; } else { // For other types like arrays or objects, you may need to handle them differently specifier = '%L'; } formatParams.push(param); return specifier; }); // Use pg-format to safely interpolate parameters into the SQL command return (0, pg_format_1.default)(formattedSql, ...formatParams); } class KVTransaction { constructor(kvsql) { this.kvsql = kvsql; this.commands = []; } addCommand(sql, params, returnType, transform) { this.commands.push({ sql, params, returnType, transform }); return this; } // Methods that add SQL commands to the batch set(key, value, options) { const { sql, params } = this.kvsql._set(key, value, options); return this.addCommand(sql, params, 'boolean'); } setnx(key, value) { const { sql, params } = this.kvsql._set(key, value, { nx: true }); return this.addCommand(sql, params, 'boolean'); } setnxex(key, value, expireSeconds) { const { sql, params } = this.kvsql._set(key, value, { nx: true, ex: expireSeconds, }); return this.addCommand(sql, params, 'boolean'); } get(key) { const { sql, params } = this.kvsql._get(key); return this.addCommand(sql, params, 'string'); } del(key) { const { sql, params } = this.kvsql._del(key); return this.addCommand(sql, params, 'number'); } expire(key, seconds) { const { sql, params } = this.kvsql._expire(key, seconds); return this.addCommand(sql, params, 'boolean'); } hset(key, fields, options) { const { sql, params } = this.kvsql._hset(key, fields, options); return this.addCommand(sql, params, 'number'); } hget(key, field) { const { sql, params } = this.kvsql._hget(key, field); return this.addCommand(sql, params, 'string'); } hdel(key, fields) { const { sql, params } = this.kvsql._hdel(key, fields); return this.addCommand(sql, params, 'number'); } hmget(key, fields) { const { sql, params } = this.kvsql._hmget(key, fields); const transform = (rows) => { const fieldValueMap = new Map(rows.map((row) => [row.field, row.value])); return fields.map((field) => fieldValueMap.get(field) || null); }; return this.addCommand(sql, params, 'array', transform); } hgetall(key) { this.kvsql.hgetall(key, this); const transform = (rows) => { const result = {}; for (const row of rows) { result[row.field] = row.value; } return result; }; return this; } hincrbyfloat(key, field, increment) { const { sql, params } = this.kvsql._hincrbyfloat(key, field, increment); return this.addCommand(sql, params, 'number', (rows) => { try { return parseFloat(rows[0].value); } catch (err) { console.error('hincrbyfloat error', err, sql, params, rows); } }); } hscan(key, cursor, count = 10) { const { sql, params } = this.kvsql._hscan(key, cursor, count); const transform = (rows) => { const items = {}; for (const row of rows) { items[row.field] = row.value; } const newCursor = cursor + rows.length; return { cursor: newCursor.toString(), items }; }; return this.addCommand(sql, params, 'object', transform); } lrange(key, start, end) { const { sql, params } = this.kvsql._lrange(key, start, end); return this.addCommand(sql, params, 'array', (rows) => rows.map((row) => row.value)); } rpush(key, value) { const { sql, params } = this.kvsql._rpush(key, value); return this.addCommand(sql, params, 'number', (rows) => rows[0]?.count || 0); } lpush(key, value) { const { sql, params } = this.kvsql._lpush(key, value); return this.addCommand(sql, params, 'number', (rows) => rows[0]?.count || 0); } lpop(key) { const { sql, params } = this.kvsql._lpop(key); return this.addCommand(sql, params, 'string'); } lmove(source, destination, srcPosition, destPosition) { const { sql, params } = this.kvsql._lmove(source, destination, srcPosition, destPosition); return this.addCommand(sql, params, 'string'); } zadd(key, score, member, options) { const { sql, params } = this.kvsql._zadd(key, score, member, options); return this.addCommand(sql, params, 'number', (rows) => rows[0]?.count || 0); } zrange(key, start, stop) { const { sql, params } = this.kvsql._zrange(key, start, stop); return this.addCommand(sql, params, 'array', (rows) => rows.map((row) => row.member)); } zrangebyscore(key, min, max) { const { sql, params } = this.kvsql._zrangebyscore(key, min, max); return this.addCommand(sql, params, 'array', (rows) => rows.map((row) => row.member)); } zrangebyscore_withscores(key, min, max) { const { sql, params } = this.kvsql._zrangebyscore_withscores(key, min, max); return this.addCommand(sql, params, 'array', (rows) => rows.map((row) => ({ member: row.member, score: row.score }))); } zrem(key, member) { const { sql, params } = this.kvsql._zrem(key, member); return this.addCommand(sql, params, 'number', (rows) => rows[0]?.count || 0); } zrank(key, member) { const { sql, params } = this.kvsql._zrank(key, member); return this.addCommand(sql, params, 'number', (rows) => rows[0]?.rank !== undefined ? parseInt(rows[0].rank, 10) - 1 : null); } scan(cursor, count = 10) { const { sql, params } = this.kvsql._scan(cursor, count); const transform = (rows) => { const keys = rows.map((row) => row.key); const newCursor = cursor + rows.length; return { cursor: newCursor, keys }; }; return this.addCommand(sql, params, 'object', transform); } rename(oldKey, newKey) { const { sql, params } = this.kvsql._rename(oldKey, newKey); return this.addCommand(sql, params, 'void'); } async exec() { const client = this.kvsql.pgClient; try { const results = []; //build the transaction SQL string const sqlStatements = ['BEGIN']; for (const cmd of this.commands) { const formattedSql = formatSqlCommand(cmd.sql, cmd.params); sqlStatements.push(formattedSql); } sqlStatements.push('COMMIT;'); // Combine into one and execute const combinedSql = sqlStatements.join(';\n'); const res = await client.query(combinedSql); // Process the results const resultSets = Array.isArray(res) ? res : [res]; //filter out the BEGIN and COMMIT results resultSets.shift(); resultSets.pop(); let resIndex = 0; //iterate set of cached commands that were just executed to process the results for (const cmd of this.commands) { const resultSet = resultSets[resIndex++]; const rows = resultSet?.rows || []; let result; if (cmd.transform) { result = cmd.transform(rows); } else { switch (cmd.returnType) { case 'string': result = rows[0]?.value || null; break; case 'number': result = rows[0]?.count || 0; break; case 'boolean': result = rows[0]?.success ?? rows.length > 0; break; case 'array': result = rows.map((row) => row.value); break; case 'object': result = rows[0] || {}; break; default: result = rows; } } results.push(result); } return results; } catch (err) { await client.query('ROLLBACK'); throw err; } } } exports.KVTransaction = KVTransaction;