{"version":3,"file":"file_system.cjs","names":["BaseStore","fields: { rootPath: string }","key: string","fs","e: any","content: Uint8Array","path","keys: string[]","values: (Uint8Array | undefined)[]","keyValuePairs: [string, Uint8Array][]","prefix?: string","rootPath: string"],"sources":["../../src/storage/file_system.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { BaseStore } from \"@langchain/core/stores\";\n\n/**\n * File system implementation of the BaseStore using a dictionary. Used for\n * storing key-value pairs in the file system.\n * @example\n * ```typescript\n * const store = await LocalFileStore.fromPath(\"./messages\");\n * await store.mset(\n *   Array.from({ length: 5 }).map((_, index) => [\n *     `message:id:${index}`,\n *     new TextEncoder().encode(\n *       JSON.stringify(\n *         index % 2 === 0\n *           ? new AIMessage(\"ai stuff...\")\n *           : new HumanMessage(\"human stuff...\"),\n *       ),\n *     ),\n *   ]),\n * );\n * const retrievedMessages = await store.mget([\"message:id:0\", \"message:id:1\"]);\n * console.log(retrievedMessages.map((v) => new TextDecoder().decode(v)));\n * for await (const key of store.yieldKeys(\"message:id:\")) {\n *   await store.mdelete([key]);\n * }\n * ```\n *\n * @security **Security Notice** This file store\n * can alter any text file in the provided directory and any subfolders.\n * Make sure that the path you specify when initializing the store is free\n * of other files.\n */\nexport class LocalFileStore extends BaseStore<string, Uint8Array> {\n  lc_namespace = [\"langchain\", \"storage\"];\n\n  rootPath: string;\n\n  constructor(fields: { rootPath: string }) {\n    super(fields);\n    this.rootPath = fields.rootPath;\n  }\n\n  /**\n   * Read and parse the file at the given path.\n   * @param key The key to read the file for.\n   * @returns Promise that resolves to the parsed file content.\n   */\n  private async getParsedFile(key: string): Promise<Uint8Array | undefined> {\n    // Validate the key to prevent path traversal\n    if (!/^[a-zA-Z0-9_\\-:.]+$/.test(key)) {\n      throw new Error(\n        \"Invalid key. Only alphanumeric characters, underscores, hyphens, colons, and periods are allowed.\"\n      );\n    }\n    try {\n      const fileContent = await fs.readFile(this.getFullPath(key));\n      if (!fileContent) {\n        return undefined;\n      }\n      return fileContent;\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    } catch (e: any) {\n      // File does not exist yet.\n      if (\"code\" in e && e.code === \"ENOENT\") {\n        return undefined;\n      }\n      throw new Error(\n        `Error reading and parsing file at path: ${\n          this.rootPath\n        }.\\nError: ${JSON.stringify(e)}`\n      );\n    }\n  }\n\n  /**\n   * Writes the given key-value pairs to the file at the given path.\n   * @param fileContent An object with the key-value pairs to be written to the file.\n   */\n  private async setFileContent(content: Uint8Array, key: string) {\n    try {\n      await fs.writeFile(this.getFullPath(key), content);\n    } catch (error) {\n      throw new Error(\n        `Error writing file at path: ${this.getFullPath(\n          key\n        )}.\\nError: ${JSON.stringify(error)}`\n      );\n    }\n  }\n\n  /**\n   * Returns the full path of the file where the value of the given key is stored.\n   * @param key the key to get the full path for\n   */\n  private getFullPath(key: string): string {\n    try {\n      const keyAsTxtFile = `${key}.txt`;\n\n      // Validate the key to prevent path traversal\n      if (!/^[a-zA-Z0-9_.\\-/]+$/.test(key)) {\n        throw new Error(`Invalid characters in key: ${key}`);\n      }\n\n      const fullPath = path.resolve(this.rootPath, keyAsTxtFile);\n      const commonPath = path.resolve(this.rootPath);\n\n      if (!fullPath.startsWith(commonPath)) {\n        throw new Error(\n          `Invalid key: ${key}. Key should be relative to the root path. ` +\n            `Root path: ${this.rootPath}, Full path: ${fullPath}`\n        );\n      }\n\n      return fullPath;\n    } catch (e) {\n      throw new Error(\n        `Error getting full path for key: ${key}.\\nError: ${String(e)}`\n      );\n    }\n  }\n\n  /**\n   * Retrieves the values associated with the given keys from the store.\n   * @param keys Keys to retrieve values for.\n   * @returns Array of values associated with the given keys.\n   */\n  async mget(keys: string[]) {\n    const values: (Uint8Array | undefined)[] = [];\n    for (const key of keys) {\n      const fileContent = await this.getParsedFile(key);\n      values.push(fileContent);\n    }\n    return values;\n  }\n\n  /**\n   * Sets the values for the given keys in the store.\n   * @param keyValuePairs Array of key-value pairs to set in the store.\n   * @returns Promise that resolves when all key-value pairs have been set.\n   */\n  async mset(keyValuePairs: [string, Uint8Array][]): Promise<void> {\n    await Promise.all(\n      keyValuePairs.map(([key, value]) => this.setFileContent(value, key))\n    );\n  }\n\n  /**\n   * Deletes the given keys and their associated values from the store.\n   * @param keys Keys to delete from the store.\n   * @returns Promise that resolves when all keys have been deleted.\n   */\n  async mdelete(keys: string[]): Promise<void> {\n    await Promise.all(keys.map((key) => fs.unlink(this.getFullPath(key))));\n  }\n\n  /**\n   * Asynchronous generator that yields keys from the store. If a prefix is\n   * provided, it only yields keys that start with the prefix.\n   * @param prefix Optional prefix to filter keys.\n   * @returns AsyncGenerator that yields keys from the store.\n   */\n  async *yieldKeys(prefix?: string): AsyncGenerator<string> {\n    const allFiles = await fs.readdir(this.rootPath);\n    const allKeys = allFiles.map((file) => file.replace(\".txt\", \"\"));\n    for (const key of allKeys) {\n      if (prefix === undefined || key.startsWith(prefix)) {\n        yield key;\n      }\n    }\n  }\n\n  /**\n   * Static method for initializing the class.\n   * Preforms a check to see if the directory exists, and if not, creates it.\n   * @param path Path to the directory.\n   * @returns Promise that resolves to an instance of the class.\n   */\n  static async fromPath(rootPath: string): Promise<LocalFileStore> {\n    try {\n      // Verifies the directory exists at the provided path, and that it is readable and writable.\n      await fs.access(rootPath, fs.constants.R_OK | fs.constants.W_OK);\n    } catch {\n      try {\n        // Directory does not exist, create it.\n        await fs.mkdir(rootPath, { recursive: true });\n      } catch (error) {\n        throw new Error(\n          `An error occurred creating directory at: ${rootPath}.\\nError: ${JSON.stringify(\n            error\n          )}`\n        );\n      }\n    }\n\n    return new this({ rootPath });\n  }\n}\n"],"mappings":"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"}