mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 23:01:09 +03:00
Bugfix/update nodevm sandbox options, sanitize tablename (#3818)
* update nodevm sandbox options, sanitize tablename * sanitize file name when getFileFromStorage
This commit is contained in:
+12
-2
@@ -106,7 +106,14 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sandbox: any = { $input: input }
|
let sandbox: any = {
|
||||||
|
$input: input,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
sandbox['$flow'] = flow
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
@@ -128,7 +135,10 @@ class CustomDocumentLoader_DocumentLoaders implements INode {
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(nodeVMOptions)
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
this.threadId = threadId
|
this.threadId = threadId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { datasourceOptions } = this.config
|
const { datasourceOptions } = this.config
|
||||||
if (!datasourceOptions) {
|
if (!datasourceOptions) {
|
||||||
@@ -38,8 +50,9 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
await queryRunner.manager.query(`
|
await queryRunner.manager.query(`
|
||||||
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
||||||
thread_id VARCHAR(255) NOT NULL,
|
thread_id VARCHAR(255) NOT NULL,
|
||||||
checkpoint_id VARCHAR(255) NOT NULL,
|
checkpoint_id VARCHAR(255) NOT NULL,
|
||||||
parent_id VARCHAR(255),
|
parent_id VARCHAR(255),
|
||||||
@@ -62,12 +75,13 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
|
|
||||||
const thread_id = config.configurable?.thread_id || this.threadId
|
const thread_id = config.configurable?.thread_id || this.threadId
|
||||||
const checkpoint_id = config.configurable?.checkpoint_id
|
const checkpoint_id = config.configurable?.checkpoint_id
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const sql = checkpoint_id
|
const sql = checkpoint_id
|
||||||
? `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = ? AND checkpoint_id = ?`
|
? `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = ? AND checkpoint_id = ?`
|
||||||
: `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
|
: `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
|
||||||
|
|
||||||
const rows = await queryRunner.manager.query(sql, checkpoint_id ? [thread_id, checkpoint_id] : [thread_id])
|
const rows = await queryRunner.manager.query(sql, checkpoint_id ? [thread_id, checkpoint_id] : [thread_id])
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -108,7 +122,8 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
try {
|
try {
|
||||||
const threadId = config.configurable?.thread_id || this.threadId
|
const threadId = config.configurable?.thread_id || this.threadId
|
||||||
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ${
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ${
|
||||||
before ? 'AND checkpoint_id < ?' : ''
|
before ? 'AND checkpoint_id < ?' : ''
|
||||||
} ORDER BY checkpoint_id DESC`
|
} ORDER BY checkpoint_id DESC`
|
||||||
if (limit) {
|
if (limit) {
|
||||||
@@ -163,8 +178,9 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
|
Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
|
||||||
Buffer.from(this.serde.stringify(metadata)) // Encode to binary
|
Buffer.from(this.serde.stringify(metadata)) // Encode to binary
|
||||||
]
|
]
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const query = `INSERT INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
|
const query = `INSERT INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE checkpoint = VALUES(checkpoint), metadata = VALUES(metadata)`
|
ON DUPLICATE KEY UPDATE checkpoint = VALUES(checkpoint), metadata = VALUES(metadata)`
|
||||||
|
|
||||||
@@ -190,10 +206,11 @@ export class MySQLSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
await this.setup(dataSource)
|
await this.setup(dataSource)
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const query = `DELETE FROM ${this.tableName} WHERE thread_id = ?;`
|
const query = `DELETE FROM ${tableName} WHERE thread_id = ?;`
|
||||||
await queryRunner.manager.query(query, [threadId])
|
await queryRunner.manager.query(query, [threadId])
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ export class PostgresSaver extends BaseCheckpointSaver implements MemoryMethods
|
|||||||
this.threadId = threadId
|
this.threadId = threadId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { datasourceOptions } = this.config
|
const { datasourceOptions } = this.config
|
||||||
if (!datasourceOptions) {
|
if (!datasourceOptions) {
|
||||||
@@ -40,8 +52,9 @@ export class PostgresSaver extends BaseCheckpointSaver implements MemoryMethods
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
await queryRunner.manager.query(`
|
await queryRunner.manager.query(`
|
||||||
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
||||||
thread_id TEXT NOT NULL,
|
thread_id TEXT NOT NULL,
|
||||||
checkpoint_id TEXT NOT NULL,
|
checkpoint_id TEXT NOT NULL,
|
||||||
parent_id TEXT,
|
parent_id TEXT,
|
||||||
@@ -63,12 +76,13 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const thread_id = config.configurable?.thread_id || this.threadId
|
const thread_id = config.configurable?.thread_id || this.threadId
|
||||||
const checkpoint_id = config.configurable?.checkpoint_id
|
const checkpoint_id = config.configurable?.checkpoint_id
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
if (checkpoint_id) {
|
if (checkpoint_id) {
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const keys = [thread_id, checkpoint_id]
|
const keys = [thread_id, checkpoint_id]
|
||||||
const sql = `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = $1 AND checkpoint_id = $2`
|
const sql = `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = $1 AND checkpoint_id = $2`
|
||||||
|
|
||||||
const rows = await queryRunner.manager.query(sql, keys)
|
const rows = await queryRunner.manager.query(sql, keys)
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -89,8 +103,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error retrieving ${this.tableName}`, error)
|
console.error(`Error retrieving ${tableName}`, error)
|
||||||
throw new Error(`Error retrieving ${this.tableName}`)
|
throw new Error(`Error retrieving ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -98,7 +112,7 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const keys = [thread_id]
|
const keys = [thread_id]
|
||||||
const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = $1 ORDER BY checkpoint_id DESC LIMIT 1`
|
const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = $1 ORDER BY checkpoint_id DESC LIMIT 1`
|
||||||
|
|
||||||
const rows = await queryRunner.manager.query(sql, keys)
|
const rows = await queryRunner.manager.query(sql, keys)
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -124,8 +138,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error retrieving ${this.tableName}`, error)
|
console.error(`Error retrieving ${tableName}`, error)
|
||||||
throw new Error(`Error retrieving ${this.tableName}`)
|
throw new Error(`Error retrieving ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -139,7 +153,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const thread_id = config.configurable?.thread_id || this.threadId
|
const thread_id = config.configurable?.thread_id || this.threadId
|
||||||
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = $1`
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = $1`
|
||||||
const args = [thread_id]
|
const args = [thread_id]
|
||||||
|
|
||||||
if (before?.configurable?.checkpoint_id) {
|
if (before?.configurable?.checkpoint_id) {
|
||||||
@@ -179,8 +194,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error listing ${this.tableName}`, error)
|
console.error(`Error listing ${tableName}`, error)
|
||||||
throw new Error(`Error listing ${this.tableName}`)
|
throw new Error(`Error listing ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -200,8 +215,9 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
|
Buffer.from(this.serde.stringify(checkpoint)), // Encode to binary
|
||||||
Buffer.from(this.serde.stringify(metadata)) // Encode to binary
|
Buffer.from(this.serde.stringify(metadata)) // Encode to binary
|
||||||
]
|
]
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const query = `INSERT INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
|
const query = `INSERT INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
ON CONFLICT (thread_id, checkpoint_id)
|
ON CONFLICT (thread_id, checkpoint_id)
|
||||||
DO UPDATE SET checkpoint = EXCLUDED.checkpoint, metadata = EXCLUDED.metadata`
|
DO UPDATE SET checkpoint = EXCLUDED.checkpoint, metadata = EXCLUDED.metadata`
|
||||||
@@ -230,8 +246,9 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
await this.setup(dataSource)
|
await this.setup(dataSource)
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const query = `DELETE FROM "${this.tableName}" WHERE thread_id = $1;`
|
const query = `DELETE FROM "${tableName}" WHERE thread_id = $1;`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ export class SqliteSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
this.threadId = threadId
|
this.threadId = threadId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { datasourceOptions } = this.config
|
const { datasourceOptions } = this.config
|
||||||
const dataSource = new DataSource(datasourceOptions)
|
const dataSource = new DataSource(datasourceOptions)
|
||||||
@@ -33,8 +45,9 @@ export class SqliteSaver extends BaseCheckpointSaver implements MemoryMethods {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
await queryRunner.manager.query(`
|
await queryRunner.manager.query(`
|
||||||
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
||||||
thread_id TEXT NOT NULL,
|
thread_id TEXT NOT NULL,
|
||||||
checkpoint_id TEXT NOT NULL,
|
checkpoint_id TEXT NOT NULL,
|
||||||
parent_id TEXT,
|
parent_id TEXT,
|
||||||
@@ -56,12 +69,13 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const thread_id = config.configurable?.thread_id || this.threadId
|
const thread_id = config.configurable?.thread_id || this.threadId
|
||||||
const checkpoint_id = config.configurable?.checkpoint_id
|
const checkpoint_id = config.configurable?.checkpoint_id
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
if (checkpoint_id) {
|
if (checkpoint_id) {
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const keys = [thread_id, checkpoint_id]
|
const keys = [thread_id, checkpoint_id]
|
||||||
const sql = `SELECT checkpoint, parent_id, metadata FROM ${this.tableName} WHERE thread_id = ? AND checkpoint_id = ?`
|
const sql = `SELECT checkpoint, parent_id, metadata FROM ${tableName} WHERE thread_id = ? AND checkpoint_id = ?`
|
||||||
|
|
||||||
const rows = await queryRunner.manager.query(sql, [...keys])
|
const rows = await queryRunner.manager.query(sql, [...keys])
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -82,8 +96,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error retrieving ${this.tableName}`, error)
|
console.error(`Error retrieving ${tableName}`, error)
|
||||||
throw new Error(`Error retrieving ${this.tableName}`)
|
throw new Error(`Error retrieving ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -91,7 +105,7 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const keys = [thread_id]
|
const keys = [thread_id]
|
||||||
const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
|
const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
|
||||||
|
|
||||||
const rows = await queryRunner.manager.query(sql, [...keys])
|
const rows = await queryRunner.manager.query(sql, [...keys])
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -117,8 +131,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error retrieving ${this.tableName}`, error)
|
console.error(`Error retrieving ${tableName}`, error)
|
||||||
throw new Error(`Error retrieving ${this.tableName}`)
|
throw new Error(`Error retrieving ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -132,7 +146,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
const thread_id = config.configurable?.thread_id || this.threadId
|
const thread_id = config.configurable?.thread_id || this.threadId
|
||||||
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${this.tableName} WHERE thread_id = ? ${
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${tableName} WHERE thread_id = ? ${
|
||||||
before ? 'AND checkpoint_id < ?' : ''
|
before ? 'AND checkpoint_id < ?' : ''
|
||||||
} ORDER BY checkpoint_id DESC`
|
} ORDER BY checkpoint_id DESC`
|
||||||
if (limit) {
|
if (limit) {
|
||||||
@@ -167,8 +182,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error listing ${this.tableName}`, error)
|
console.error(`Error listing ${tableName}`, error)
|
||||||
throw new Error(`Error listing ${this.tableName}`)
|
throw new Error(`Error listing ${tableName}`)
|
||||||
} finally {
|
} finally {
|
||||||
await dataSource.destroy()
|
await dataSource.destroy()
|
||||||
}
|
}
|
||||||
@@ -188,8 +203,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
this.serde.stringify(checkpoint),
|
this.serde.stringify(checkpoint),
|
||||||
this.serde.stringify(metadata)
|
this.serde.stringify(metadata)
|
||||||
]
|
]
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
const query = `INSERT OR REPLACE INTO ${this.tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata) VALUES (?, ?, ?, ?, ?)`
|
const query = `INSERT OR REPLACE INTO ${tableName} (thread_id, checkpoint_id, parent_id, checkpoint, metadata) VALUES (?, ?, ?, ?, ?)`
|
||||||
|
|
||||||
await queryRunner.manager.query(query, row)
|
await queryRunner.manager.query(query, row)
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -215,8 +230,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
await this.setup(dataSource)
|
await this.setup(dataSource)
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
const query = `DELETE FROM "${this.tableName}" WHERE thread_id = ?;`
|
const query = `DELETE FROM "${tableName}" WHERE thread_id = ?;`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
|||||||
@@ -178,6 +178,18 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { mysqlOptions } = this.config
|
const { mysqlOptions } = this.config
|
||||||
if (!mysqlOptions) {
|
if (!mysqlOptions) {
|
||||||
@@ -196,8 +208,9 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
try {
|
try {
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
await queryRunner.manager.query(`create table if not exists \`${this.tableName}\` (
|
await queryRunner.manager.query(`create table if not exists \`${this.sanitizeTableName(tableName)}\` (
|
||||||
\`uuid\` varchar(36) primary key default (UUID()),
|
\`uuid\` varchar(36) primary key default (UUID()),
|
||||||
\`key\` varchar(255) not null,
|
\`key\` varchar(255) not null,
|
||||||
\`namespace\` varchar(255) not null,
|
\`namespace\` varchar(255) not null,
|
||||||
@@ -211,11 +224,11 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
// MySQL does not support 'IF NOT EXISTS' function for Index
|
// MySQL does not support 'IF NOT EXISTS' function for Index
|
||||||
const Check = await queryRunner.manager.query(
|
const Check = await queryRunner.manager.query(
|
||||||
`SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS
|
`SELECT COUNT(1) IndexIsThere FROM INFORMATION_SCHEMA.STATISTICS
|
||||||
WHERE table_schema=DATABASE() AND table_name='${this.tableName}' AND index_name='${column}_index';`
|
WHERE table_schema=DATABASE() AND table_name='${tableName}' AND index_name='${column}_index';`
|
||||||
)
|
)
|
||||||
if (Check[0].IndexIsThere === 0)
|
if (Check[0].IndexIsThere === 0)
|
||||||
await queryRunner.manager.query(`CREATE INDEX \`${column}_index\`
|
await queryRunner.manager.query(`CREATE INDEX \`${column}_index\`
|
||||||
ON \`${this.tableName}\` (\`${column}\`);`)
|
ON \`${tableName}\` (\`${column}\`);`)
|
||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -253,6 +266,7 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const updatedAt = await this.getTime()
|
const updatedAt = await this.getTime()
|
||||||
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
||||||
@@ -275,7 +289,7 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
])
|
])
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO \`${this.tableName}\` (\`key\`, \`namespace\`, \`updated_at\`, \`group_id\`)
|
INSERT INTO \`${tableName}\` (\`key\`, \`namespace\`, \`updated_at\`, \`group_id\`)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE \`updated_at\` = VALUES(\`updated_at\`)`
|
ON DUPLICATE KEY UPDATE \`updated_at\` = VALUES(\`updated_at\`)`
|
||||||
|
|
||||||
@@ -302,12 +316,13 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
// Prepare the placeholders and the query
|
// Prepare the placeholders and the query
|
||||||
const placeholders = keys.map(() => `?`).join(', ')
|
const placeholders = keys.map(() => `?`).join(', ')
|
||||||
const query = `
|
const query = `
|
||||||
SELECT \`key\`
|
SELECT \`key\`
|
||||||
FROM \`${this.tableName}\`
|
FROM \`${tableName}\`
|
||||||
WHERE \`namespace\` = ? AND \`key\` IN (${placeholders})`
|
WHERE \`namespace\` = ? AND \`key\` IN (${placeholders})`
|
||||||
|
|
||||||
// Initialize an array to fill with the existence checks
|
// Initialize an array to fill with the existence checks
|
||||||
@@ -335,10 +350,11 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { before, after, limit, groupIds } = options ?? {}
|
const { before, after, limit, groupIds } = options ?? {}
|
||||||
let query = `SELECT \`key\` FROM \`${this.tableName}\` WHERE \`namespace\` = ?`
|
let query = `SELECT \`key\` FROM \`${tableName}\` WHERE \`namespace\` = ?`
|
||||||
const values: (string | number | string[])[] = [this.namespace]
|
const values: (string | number | string[])[] = [this.namespace]
|
||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
@@ -385,9 +401,10 @@ class MySQLRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const placeholders = keys.map(() => '?').join(', ')
|
const placeholders = keys.map(() => '?').join(', ')
|
||||||
const query = `DELETE FROM \`${this.tableName}\` WHERE \`namespace\` = ? AND \`key\` IN (${placeholders});`
|
const query = `DELETE FROM \`${tableName}\` WHERE \`namespace\` = ? AND \`key\` IN (${placeholders});`
|
||||||
const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
|
const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
|
||||||
|
|
||||||
// Directly using try/catch with async/await for cleaner flow
|
// Directly using try/catch with async/await for cleaner flow
|
||||||
|
|||||||
+27
-9
@@ -186,6 +186,18 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { postgresConnectionOptions } = this.config
|
const { postgresConnectionOptions } = this.config
|
||||||
if (!postgresConnectionOptions) {
|
if (!postgresConnectionOptions) {
|
||||||
@@ -204,9 +216,10 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
try {
|
try {
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
await queryRunner.manager.query(`
|
await queryRunner.manager.query(`
|
||||||
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
||||||
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
key TEXT NOT NULL,
|
key TEXT NOT NULL,
|
||||||
namespace TEXT NOT NULL,
|
namespace TEXT NOT NULL,
|
||||||
@@ -214,10 +227,10 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
group_id TEXT,
|
group_id TEXT,
|
||||||
UNIQUE (key, namespace)
|
UNIQUE (key, namespace)
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS updated_at_index ON "${this.tableName}" (updated_at);
|
CREATE INDEX IF NOT EXISTS updated_at_index ON "${tableName}" (updated_at);
|
||||||
CREATE INDEX IF NOT EXISTS key_index ON "${this.tableName}" (key);
|
CREATE INDEX IF NOT EXISTS key_index ON "${tableName}" (key);
|
||||||
CREATE INDEX IF NOT EXISTS namespace_index ON "${this.tableName}" (namespace);
|
CREATE INDEX IF NOT EXISTS namespace_index ON "${tableName}" (namespace);
|
||||||
CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
CREATE INDEX IF NOT EXISTS group_id_index ON "${tableName}" (group_id);`)
|
||||||
|
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -269,6 +282,7 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const updatedAt = await this.getTime()
|
const updatedAt = await this.getTime()
|
||||||
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
||||||
@@ -287,7 +301,7 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const valuesPlaceholders = recordsToUpsert.map((_, j) => this.generatePlaceholderForRowAt(j, recordsToUpsert[0].length)).join(', ')
|
const valuesPlaceholders = recordsToUpsert.map((_, j) => this.generatePlaceholderForRowAt(j, recordsToUpsert[0].length)).join(', ')
|
||||||
|
|
||||||
const query = `INSERT INTO "${this.tableName}" (key, namespace, updated_at, group_id) VALUES ${valuesPlaceholders} ON CONFLICT (key, namespace) DO UPDATE SET updated_at = EXCLUDED.updated_at;`
|
const query = `INSERT INTO "${tableName}" (key, namespace, updated_at, group_id) VALUES ${valuesPlaceholders} ON CONFLICT (key, namespace) DO UPDATE SET updated_at = EXCLUDED.updated_at;`
|
||||||
try {
|
try {
|
||||||
await queryRunner.manager.query(query, recordsToUpsert.flat())
|
await queryRunner.manager.query(query, recordsToUpsert.flat())
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
@@ -306,12 +320,13 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const startIndex = 2
|
const startIndex = 2
|
||||||
const arrayPlaceholders = keys.map((_, i) => `$${i + startIndex}`).join(', ')
|
const arrayPlaceholders = keys.map((_, i) => `$${i + startIndex}`).join(', ')
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
SELECT k, (key is not null) ex from unnest(ARRAY[${arrayPlaceholders}]) k left join "${this.tableName}" on k=key and namespace = $1;
|
SELECT k, (key is not null) ex from unnest(ARRAY[${arrayPlaceholders}]) k left join "${tableName}" on k=key and namespace = $1;
|
||||||
`
|
`
|
||||||
try {
|
try {
|
||||||
const res = await queryRunner.manager.query(query, [this.namespace, ...keys.flat()])
|
const res = await queryRunner.manager.query(query, [this.namespace, ...keys.flat()])
|
||||||
@@ -327,7 +342,9 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
||||||
const { before, after, limit, groupIds } = options ?? {}
|
const { before, after, limit, groupIds } = options ?? {}
|
||||||
let query = `SELECT key FROM "${this.tableName}" WHERE namespace = $1`
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
|
let query = `SELECT key FROM "${tableName}" WHERE namespace = $1`
|
||||||
const values: (string | number | (string | null)[])[] = [this.namespace]
|
const values: (string | number | (string | null)[])[] = [this.namespace]
|
||||||
|
|
||||||
let index = 2
|
let index = 2
|
||||||
@@ -379,9 +396,10 @@ class PostgresRecordManager implements RecordManagerInterface {
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const query = `DELETE FROM "${this.tableName}" WHERE namespace = $1 AND key = ANY($2);`
|
const query = `DELETE FROM "${tableName}" WHERE namespace = $1 AND key = ANY($2);`
|
||||||
await queryRunner.manager.query(query, [this.namespace, keys])
|
await queryRunner.manager.query(query, [this.namespace, keys])
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -156,6 +156,18 @@ class SQLiteRecordManager implements RecordManagerInterface {
|
|||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
private async getDataSource(): Promise<DataSource> {
|
private async getDataSource(): Promise<DataSource> {
|
||||||
const { sqliteOptions } = this.config
|
const { sqliteOptions } = this.config
|
||||||
if (!sqliteOptions) {
|
if (!sqliteOptions) {
|
||||||
@@ -170,9 +182,10 @@ class SQLiteRecordManager implements RecordManagerInterface {
|
|||||||
try {
|
try {
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
await queryRunner.manager.query(`
|
await queryRunner.manager.query(`
|
||||||
CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
CREATE TABLE IF NOT EXISTS "${tableName}" (
|
||||||
uuid TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
uuid TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
key TEXT NOT NULL,
|
key TEXT NOT NULL,
|
||||||
namespace TEXT NOT NULL,
|
namespace TEXT NOT NULL,
|
||||||
@@ -180,10 +193,10 @@ CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|||||||
group_id TEXT,
|
group_id TEXT,
|
||||||
UNIQUE (key, namespace)
|
UNIQUE (key, namespace)
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS updated_at_index ON "${this.tableName}" (updated_at);
|
CREATE INDEX IF NOT EXISTS updated_at_index ON "${tableName}" (updated_at);
|
||||||
CREATE INDEX IF NOT EXISTS key_index ON "${this.tableName}" (key);
|
CREATE INDEX IF NOT EXISTS key_index ON "${tableName}" (key);
|
||||||
CREATE INDEX IF NOT EXISTS namespace_index ON "${this.tableName}" (namespace);
|
CREATE INDEX IF NOT EXISTS namespace_index ON "${tableName}" (namespace);
|
||||||
CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
CREATE INDEX IF NOT EXISTS group_id_index ON "${tableName}" (group_id);`)
|
||||||
|
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -219,6 +232,7 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
|||||||
}
|
}
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const updatedAt = await this.getTime()
|
const updatedAt = await this.getTime()
|
||||||
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {}
|
||||||
@@ -241,7 +255,7 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
|||||||
])
|
])
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO "${this.tableName}" (key, namespace, updated_at, group_id)
|
INSERT INTO "${tableName}" (key, namespace, updated_at, group_id)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at`
|
ON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at`
|
||||||
|
|
||||||
@@ -264,12 +278,13 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
|||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
// Prepare the placeholders and the query
|
// Prepare the placeholders and the query
|
||||||
const placeholders = keys.map(() => `?`).join(', ')
|
const placeholders = keys.map(() => `?`).join(', ')
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT key
|
SELECT key
|
||||||
FROM "${this.tableName}"
|
FROM "${tableName}"
|
||||||
WHERE namespace = ? AND key IN (${placeholders})`
|
WHERE namespace = ? AND key IN (${placeholders})`
|
||||||
|
|
||||||
// Initialize an array to fill with the existence checks
|
// Initialize an array to fill with the existence checks
|
||||||
@@ -299,7 +314,9 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
|||||||
|
|
||||||
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
async listKeys(options?: ListKeyOptions): Promise<string[]> {
|
||||||
const { before, after, limit, groupIds } = options ?? {}
|
const { before, after, limit, groupIds } = options ?? {}
|
||||||
let query = `SELECT key FROM "${this.tableName}" WHERE namespace = ?`
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
|
let query = `SELECT key FROM "${tableName}" WHERE namespace = ?`
|
||||||
const values: (string | number | string[])[] = [this.namespace]
|
const values: (string | number | string[])[] = [this.namespace]
|
||||||
|
|
||||||
if (before) {
|
if (before) {
|
||||||
@@ -350,9 +367,10 @@ CREATE INDEX IF NOT EXISTS group_id_index ON "${this.tableName}" (group_id);`)
|
|||||||
|
|
||||||
const dataSource = await this.getDataSource()
|
const dataSource = await this.getDataSource()
|
||||||
const queryRunner = dataSource.createQueryRunner()
|
const queryRunner = dataSource.createQueryRunner()
|
||||||
|
const tableName = this.sanitizeTableName(this.tableName)
|
||||||
|
|
||||||
const placeholders = keys.map(() => '?').join(', ')
|
const placeholders = keys.map(() => '?').join(', ')
|
||||||
const query = `DELETE FROM "${this.tableName}" WHERE namespace = ? AND key IN (${placeholders});`
|
const query = `DELETE FROM "${tableName}" WHERE namespace = ? AND key IN (${placeholders});`
|
||||||
const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
|
const values = [this.namespace, ...keys].map((v) => (typeof v !== 'string' ? `${v}` : v))
|
||||||
|
|
||||||
// Directly using try/catch with async/await for cleaner flow
|
// Directly using try/catch with async/await for cleaner flow
|
||||||
|
|||||||
@@ -196,7 +196,13 @@ class State_SeqAgents implements INode {
|
|||||||
input
|
input
|
||||||
}
|
}
|
||||||
|
|
||||||
let sandbox: any = {}
|
let sandbox: any = {
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
sandbox['$flow'] = flow
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
@@ -212,7 +218,10 @@ class State_SeqAgents implements INode {
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(nodeVMOptions)
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
|||||||
@@ -153,7 +153,13 @@ export const processImageMessage = async (llm: BaseChatModel, nodeData: INodeDat
|
|||||||
export const getVM = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData, flow: ICommonObject) => {
|
export const getVM = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData, flow: ICommonObject) => {
|
||||||
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||||
|
|
||||||
let sandbox: any = {}
|
let sandbox: any = {
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
sandbox['$flow'] = flow
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
@@ -169,7 +175,10 @@ export const getVM = async (appDataSource: DataSource, databaseEntities: IDataba
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
return new NodeVM(nodeVMOptions)
|
return new NodeVM(nodeVMOptions)
|
||||||
|
|||||||
@@ -318,7 +318,15 @@ class ChatflowTool extends StructuredTool {
|
|||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sandbox = { $callOptions: options, $callBody: body }
|
let sandbox = {
|
||||||
|
$callOptions: options,
|
||||||
|
$callBody: body,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
|
|
||||||
const code = `
|
const code = `
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
@@ -349,7 +357,10 @@ try {
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(vmOptions)
|
const vm = new NodeVM(vmOptions)
|
||||||
|
|||||||
@@ -111,7 +111,13 @@ export class DynamicStructuredTool<
|
|||||||
_?: CallbackManagerForToolRun,
|
_?: CallbackManagerForToolRun,
|
||||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let sandbox: any = {}
|
let sandbox: any = {
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
if (typeof arg === 'object' && Object.keys(arg).length) {
|
if (typeof arg === 'object' && Object.keys(arg).length) {
|
||||||
for (const item in arg) {
|
for (const item in arg) {
|
||||||
sandbox[`$${item}`] = arg[item]
|
sandbox[`$${item}`] = arg[item]
|
||||||
@@ -137,7 +143,10 @@ export class DynamicStructuredTool<
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(options)
|
const vm = new NodeVM(options)
|
||||||
|
|||||||
@@ -196,7 +196,13 @@ export class DynamicStructuredTool<
|
|||||||
_?: CallbackManagerForToolRun,
|
_?: CallbackManagerForToolRun,
|
||||||
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let sandbox: any = {}
|
let sandbox: any = {
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
if (typeof arg === 'object' && Object.keys(arg).length) {
|
if (typeof arg === 'object' && Object.keys(arg).length) {
|
||||||
for (const item in arg) {
|
for (const item in arg) {
|
||||||
sandbox[`$${item}`] = arg[item]
|
sandbox[`$${item}`] = arg[item]
|
||||||
@@ -237,7 +243,10 @@ export class DynamicStructuredTool<
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(options)
|
const vm = new NodeVM(options)
|
||||||
|
|||||||
@@ -117,7 +117,14 @@ class CustomFunction_Utilities implements INode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sandbox: any = { $input: input }
|
let sandbox: any = {
|
||||||
|
$input: input,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
sandbox['$flow'] = flow
|
sandbox['$flow'] = flow
|
||||||
sandbox['$tools'] = tools
|
sandbox['$tools'] = tools
|
||||||
@@ -140,7 +147,10 @@ class CustomFunction_Utilities implements INode {
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(nodeVMOptions)
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
|||||||
@@ -119,7 +119,14 @@ class IfElseFunction_Utilities implements INode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sandbox: any = { $input: input }
|
let sandbox: any = {
|
||||||
|
$input: input,
|
||||||
|
util: undefined,
|
||||||
|
Symbol: undefined,
|
||||||
|
child_process: undefined,
|
||||||
|
fs: undefined,
|
||||||
|
process: undefined
|
||||||
|
}
|
||||||
sandbox['$vars'] = prepareSandboxVars(variables)
|
sandbox['$vars'] = prepareSandboxVars(variables)
|
||||||
sandbox['$flow'] = flow
|
sandbox['$flow'] = flow
|
||||||
|
|
||||||
@@ -141,7 +148,10 @@ class IfElseFunction_Utilities implements INode {
|
|||||||
require: {
|
require: {
|
||||||
external: { modules: deps },
|
external: { modules: deps },
|
||||||
builtin: builtinDeps
|
builtin: builtinDeps
|
||||||
}
|
},
|
||||||
|
eval: false,
|
||||||
|
wasm: false,
|
||||||
|
timeout: 10000
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
const vm = new NodeVM(nodeVMOptions)
|
const vm = new NodeVM(nodeVMOptions)
|
||||||
|
|||||||
@@ -28,13 +28,25 @@ export abstract class VectorStoreDriver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTableName() {
|
getTableName() {
|
||||||
return getTableName(this.nodeData)
|
return this.sanitizeTableName(getTableName(this.nodeData))
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmbeddings() {
|
getEmbeddings() {
|
||||||
return this.nodeData.inputs?.embeddings as Embeddings
|
return this.nodeData.inputs?.embeddings as Embeddings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sanitizeTableName(tableName: string): string {
|
||||||
|
// Trim and normalize case, turn whitespace into underscores
|
||||||
|
tableName = tableName.trim().toLowerCase().replace(/\s+/g, '_')
|
||||||
|
|
||||||
|
// Validate using a regex (alphanumeric and underscores only)
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) {
|
||||||
|
throw new Error('Invalid table name')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName
|
||||||
|
}
|
||||||
|
|
||||||
async getCredentials() {
|
async getCredentials() {
|
||||||
const credentialData = await getCredentialData(this.nodeData.credential ?? '', this.options)
|
const credentialData = await getCredentialData(this.nodeData.credential ?? '', this.options)
|
||||||
const user = getCredentialParam('user', credentialData, this.nodeData, process.env.POSTGRES_VECTORSTORE_USER)
|
const user = getCredentialParam('user', credentialData, this.nodeData, process.env.POSTGRES_VECTORSTORE_USER)
|
||||||
|
|||||||
@@ -122,10 +122,12 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
|
|||||||
|
|
||||||
export const getFileFromStorage = async (file: string, ...paths: string[]): Promise<Buffer> => {
|
export const getFileFromStorage = async (file: string, ...paths: string[]): Promise<Buffer> => {
|
||||||
const storageType = getStorageType()
|
const storageType = getStorageType()
|
||||||
|
const sanitizedFilename = _sanitizeFilename(file)
|
||||||
|
|
||||||
if (storageType === 's3') {
|
if (storageType === 's3') {
|
||||||
const { s3Client, Bucket } = getS3Config()
|
const { s3Client, Bucket } = getS3Config()
|
||||||
|
|
||||||
let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + file
|
let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + sanitizedFilename
|
||||||
if (Key.startsWith('/')) {
|
if (Key.startsWith('/')) {
|
||||||
Key = Key.substring(1)
|
Key = Key.substring(1)
|
||||||
}
|
}
|
||||||
@@ -147,7 +149,7 @@ export const getFileFromStorage = async (file: string, ...paths: string[]): Prom
|
|||||||
const buffer = Buffer.concat(response.Body.toArray())
|
const buffer = Buffer.concat(response.Body.toArray())
|
||||||
return buffer
|
return buffer
|
||||||
} else {
|
} else {
|
||||||
const fileInStorage = path.join(getStoragePath(), ...paths, file)
|
const fileInStorage = path.join(getStoragePath(), ...paths, sanitizedFilename)
|
||||||
return fs.readFileSync(fileInStorage)
|
return fs.readFileSync(fileInStorage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user