{"version":3,"file":"sqlite.cjs","names":["Database"],"sources":["../../src/indexes/sqlite.ts"],"sourcesContent":["import Database, { Database as DatabaseType, Statement } from \"better-sqlite3\";\nimport {\n  ListKeyOptions,\n  RecordManagerInterface,\n  UpdateOptions,\n} from \"@langchain/core/indexing\";\n\ninterface TimeRow {\n  epoch: number;\n}\n\ninterface KeyRecord {\n  key: string;\n}\n\n/**\n * Options for configuring the SQLiteRecordManager class.\n */\nexport type SQLiteRecordManagerOptions = {\n  /**\n   * The file path of the SQLite database.\n   * One of either `localPath` or `connectionString` is required.\n   */\n  localPath?: string;\n  /**\n   * The connection string of the SQLite database.\n   * One of either `localPath` or `connectionString` is required.\n   */\n  connectionString?: string;\n  /**\n   * The name of the table in the SQLite database.\n   */\n  tableName: string;\n};\n\nexport class SQLiteRecordManager implements RecordManagerInterface {\n  lc_namespace = [\"langchain\", \"recordmanagers\", \"sqlite\"];\n\n  tableName: string;\n\n  db: DatabaseType;\n\n  namespace: string;\n\n  constructor(namespace: string, config: SQLiteRecordManagerOptions) {\n    const { localPath, connectionString, tableName } = config;\n    if (!connectionString && !localPath) {\n      throw new Error(\n        \"One of either `localPath` or `connectionString` is required.\"\n      );\n    }\n    if (connectionString && localPath) {\n      throw new Error(\n        \"Only one of either `localPath` or `connectionString` is allowed.\"\n      );\n    }\n    this.namespace = namespace;\n    this.tableName = tableName;\n    this.db = new Database(connectionString ?? localPath);\n  }\n\n  async createSchema(): Promise<void> {\n    try {\n      this.db.exec(`\nCREATE TABLE IF NOT EXISTS \"${this.tableName}\" (\n  uuid TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),\n  key TEXT NOT NULL,\n  namespace TEXT NOT NULL,\n  updated_at REAL NOT NULL,\n  group_id TEXT,\n  UNIQUE (key, namespace)\n);\nCREATE INDEX IF NOT EXISTS updated_at_index ON \"${this.tableName}\" (updated_at);\nCREATE INDEX IF NOT EXISTS key_index ON \"${this.tableName}\" (key);\nCREATE INDEX IF NOT EXISTS namespace_index ON \"${this.tableName}\" (namespace);\nCREATE INDEX IF NOT EXISTS group_id_index ON \"${this.tableName}\" (group_id);`);\n    } catch (error) {\n      console.error(\"Error creating schema\");\n      throw error; // Re-throw the error to let the caller handle it\n    }\n  }\n\n  async getTime(): Promise<number> {\n    try {\n      const statement: Statement<[]> = this.db.prepare(\n        \"SELECT strftime('%s', 'now') AS epoch\"\n      );\n      const { epoch } = statement.get() as TimeRow;\n      return Number(epoch);\n    } catch (error) {\n      console.error(\"Error getting time in SQLiteRecordManager:\");\n      throw error;\n    }\n  }\n\n  async update(keys: string[], updateOptions?: UpdateOptions): Promise<void> {\n    if (keys.length === 0) {\n      return;\n    }\n\n    const updatedAt = await this.getTime();\n    const { timeAtLeast, groupIds: _groupIds } = updateOptions ?? {};\n\n    if (timeAtLeast && updatedAt < timeAtLeast) {\n      throw new Error(\n        `Time sync issue with database ${updatedAt} < ${timeAtLeast}`\n      );\n    }\n\n    const groupIds = _groupIds ?? keys.map(() => null);\n\n    if (groupIds.length !== keys.length) {\n      throw new Error(\n        `Number of keys (${keys.length}) does not match number of group_ids (${groupIds.length})`\n      );\n    }\n\n    const recordsToUpsert = keys.map((key, i) => [\n      key,\n      this.namespace,\n      updatedAt,\n      groupIds[i] ?? null, // Ensure groupIds[i] is null if undefined\n    ]);\n\n    // Consider using a transaction for batch operations\n    const updateTransaction = this.db.transaction(() => {\n      for (const row of recordsToUpsert) {\n        this.db\n          .prepare(\n            `\nINSERT INTO \"${this.tableName}\" (key, namespace, updated_at, group_id)\nVALUES (?, ?, ?, ?)\nON CONFLICT (key, namespace) DO UPDATE SET updated_at = excluded.updated_at`\n          )\n          .run(...row);\n      }\n    });\n    updateTransaction();\n  }\n\n  async exists(keys: string[]): Promise<boolean[]> {\n    if (keys.length === 0) {\n      return [];\n    }\n\n    // Prepare the placeholders and the query\n    const placeholders = keys.map(() => `?`).join(\", \");\n    const sql = `\nSELECT key\nFROM \"${this.tableName}\"\nWHERE namespace = ? AND key IN (${placeholders})`;\n\n    // Initialize an array to fill with the existence checks\n    const existsArray = new Array(keys.length).fill(false);\n\n    try {\n      // Execute the query\n      const rows = this.db\n        .prepare(sql)\n        .all(this.namespace, ...keys) as KeyRecord[];\n      // Create a set of existing keys for faster lookup\n      const existingKeysSet = new Set(rows.map((row) => row.key));\n      // Map the input keys to booleans indicating if they exist\n      keys.forEach((key, index) => {\n        existsArray[index] = existingKeysSet.has(key);\n      });\n      return existsArray;\n    } catch (error) {\n      console.error(\"Error checking existence of keys\");\n      throw error; // Allow the caller to handle the error\n    }\n  }\n\n  async listKeys(options?: ListKeyOptions): Promise<string[]> {\n    const { before, after, limit, groupIds } = options ?? {};\n    let query = `SELECT key FROM \"${this.tableName}\" WHERE namespace = ?`;\n    const values: (string | number | string[])[] = [this.namespace];\n\n    if (before) {\n      query += ` AND updated_at < ?`;\n      values.push(before);\n    }\n\n    if (after) {\n      query += ` AND updated_at > ?`;\n      values.push(after);\n    }\n\n    if (limit) {\n      query += ` LIMIT ?`;\n      values.push(limit);\n    }\n\n    if (groupIds && Array.isArray(groupIds)) {\n      query += ` AND group_id IN (${groupIds\n        .filter((gid) => gid !== null)\n        .map(() => \"?\")\n        .join(\", \")})`;\n      values.push(...groupIds.filter((gid): gid is string => gid !== null));\n    }\n\n    query += \";\";\n\n    // Directly using try/catch with async/await for cleaner flow\n    try {\n      const result = this.db.prepare(query).all(...values) as { key: string }[];\n      return result.map((row) => row.key);\n    } catch (error) {\n      console.error(\"Error listing keys.\");\n      throw error; // Re-throw the error to be handled by the caller\n    }\n  }\n\n  async deleteKeys(keys: string[]): Promise<void> {\n    if (keys.length === 0) {\n      return;\n    }\n\n    const placeholders = keys.map(() => \"?\").join(\", \");\n    const query = `DELETE FROM \"${this.tableName}\" WHERE namespace = ? AND key IN (${placeholders});`;\n    const values = [this.namespace, ...keys].map((v) =>\n      typeof v !== \"string\" ? `${v}` : v\n    );\n\n    // Directly using try/catch with async/await for cleaner flow\n    try {\n      this.db.prepare(query).run(...values);\n    } catch (error) {\n      console.error(\"Error deleting keys\");\n      throw error; // Re-throw the error to be handled by the caller\n    }\n  }\n}\n"],"mappings":"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"}