UNPKG

@langchain/community

Version:
1 lines • 64.6 kB
{"version":3,"file":"cassandra.cjs","names":["args: CassandraClientArgs","Client","astraArgs: AstraServiceProviderArgs","path","os","fs","scbPath: string | URL","error: unknown","scbPath: string","args: CassandraTableArgs","client?: Client","AsyncCaller","columns?: Column[]","filter?: WhereClause","orderBy?: Filter[]","limit?: number","allowFiltering?: boolean","fetchSize?: number","pagingState?: string","values: unknown[] | unknown[][]","values: unknown[][]","batchSize: number","upsertPromises: Promise<void>[]","currentBatch: unknown[][]","whereClause: WhereClause","columns: Column[]","obj: unknown","record: Record<string, unknown>","record: WhereClause | undefined","filters?: Filter[]","queryColumns: Column[]","options: string | undefined"],"sources":["../../src/utils/cassandra.ts"],"sourcesContent":["import {\n  AsyncCaller,\n  AsyncCallerParams,\n} from \"@langchain/core/utils/async_caller\";\n\nimport {\n  Client,\n  DseClientOptions,\n  types as driverTypes,\n} from \"cassandra-driver\";\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/* =====================================================================================================================\n * =====================================================================================================================\n * Cassandra Client Factory\n * =====================================================================================================================\n * =====================================================================================================================\n */\n\n/**\n * Defines the configuration options for connecting to Astra DB, DataStax's cloud-native Cassandra-as-a-Service.\n * This interface specifies the necessary parameters required to establish a connection with an Astra DB instance,\n * including authentication and targeting specific data centers or regions.\n *\n * Properties:\n * - `token`: The authentication token required for accessing the Astra DB instance. Essential for establishing a secure connection.\n * - `endpoint`: Optional. The URL or network address of the Astra DB instance. Can be used to directly specify the connection endpoint.\n * - `datacenterID`: Optional. The unique identifier of the data center to connect to. Used to compute the endpoint.\n * - `regionName`: Optional. The region name of the Astra DB instance. Used to compute the endpoint. Default to the primary region.\n * - `bundleUrlTemplate`: Optional. The URL template for downloading the secure connect bundle. Used to customize the bundle URL. \"database_id\" variable will be resolved at runtime.\n *\n * Either `endpoint` or `datacenterID` must be provided to establish a connection to Astra DB.\n */\nexport interface AstraServiceProviderArgs {\n  token: string;\n  endpoint?: string | URL;\n  datacenterID?: string;\n  regionName?: string;\n  bundleUrlTemplate?: string;\n}\n\n/**\n * Encapsulates the service provider-specific arguments required for creating a Cassandra client.\n * This interface acts as a wrapper for configurations pertaining to various Cassandra service providers,\n * allowing for extensible and flexible client configuration.\n *\n * Currently, it supports:\n * - `astra`: Optional. Configuration parameters specific to Astra DB, DataStax's cloud-native Cassandra service.\n *            Utilizing this property enables tailored connections to Astra DB instances with custom configurations.\n *\n * This structure is designed to be extended with additional service providers in the future, ensuring adaptability\n * and extensibility for connecting to various Cassandra services with distinct configuration requirements.\n */\nexport interface CassandraServiceProviderArgs {\n  astra?: AstraServiceProviderArgs;\n}\n\n/**\n * Extends the DataStax driver's client options with additional configurations for service providers,\n * enabling the customization of Cassandra client instances based on specific service requirements.\n * This interface integrates native driver configurations with custom extensions, facilitating the\n * connection to Cassandra databases, including managed services like Astra DB.\n *\n * - `serviceProviderArgs`: Optional. Contains the connection arguments for specific Cassandra service providers,\n *                           such as Astra DB. This allows for detailed and service-specific client configurations,\n *                           enhancing connectivity and functionality across different Cassandra environments.\n *\n * Incorporating this interface into client creation processes ensures a comprehensive setup, encompassing both\n * standard and extended options for robust and versatile Cassandra database interactions.\n */\nexport interface CassandraClientArgs extends DseClientOptions {\n  serviceProviderArgs?: CassandraServiceProviderArgs;\n}\n\n/**\n * Provides a centralized and streamlined factory for creating and configuring instances of the Cassandra client.\n * This class abstracts the complexities involved in instantiating and configuring Cassandra client instances,\n * enabling straightforward integration with Cassandra databases. It supports customization through various\n * configuration options, allowing for the creation of clients tailored to specific needs, such as connecting\n * to different clusters or utilizing specialized authentication and connection options.\n *\n * Key Features:\n * - Simplifies the Cassandra client creation process with method-based configurations.\n * - Supports customization for connecting to various Cassandra environments, including cloud-based services like Astra.\n * - Ensures consistent and optimal client configuration, incorporating best practices.\n *\n * Example Usage (Apache Cassandra®):\n * ```\n * const cassandraArgs = {\n *   contactPoints: ['h1', 'h2'],\n *   localDataCenter: 'datacenter1',\n *   credentials: {\n *     username: <...> as string,\n *     password: <...> as string,\n *   },\n * };\n * const cassandraClient = CassandraClientFactory.getClient(cassandraArgs);\n * ```\n *\n * Example Usage (DataStax AstraDB):\n * ```\n * const astraArgs = {\n *   serviceProviderArgs: {\n *     astra: {\n *       token: <...> as string,\n *       endpoint: <...> as string,\n *     },\n *   },\n * };\n * const cassandraClient = CassandraClientFactory.getClient(astraArgs);\n * ``` *\n */\nexport class CassandraClientFactory {\n  /**\n   * Asynchronously obtains a configured Cassandra client based on the provided arguments.\n   * This method processes the given CassandraClientArgs to produce a configured Client instance\n   * from the cassandra-driver, suitable for interacting with Cassandra databases.\n   *\n   * @param args The configuration arguments for the Cassandra client, including any service provider-specific options.\n   * @returns A Promise resolving to a Client object configured according to the specified arguments.\n   */\n  public static async getClient(args: CassandraClientArgs): Promise<Client> {\n    const modifiedArgs = await this.processArgs(args);\n    return new Client(modifiedArgs);\n  }\n\n  /**\n   * Processes the provided CassandraClientArgs for creating a Cassandra client.\n   *\n   * @param args The arguments for creating the Cassandra client, including service provider configurations.\n   * @returns A Promise resolving to the processed CassandraClientArgs, ready for client initialization.\n   * @throws Error if the configuration is unsupported, specifically if serviceProviderArgs are provided\n   * but do not include valid configurations for Astra.\n   */\n  private static processArgs(\n    args: CassandraClientArgs\n  ): Promise<CassandraClientArgs> {\n    if (!args.serviceProviderArgs) {\n      return Promise.resolve(args);\n    }\n\n    if (args.serviceProviderArgs && args.serviceProviderArgs.astra) {\n      return CassandraClientFactory.processAstraArgs(args);\n    }\n\n    throw new Error(\"Unsupported configuration for Cassandra client.\");\n  }\n\n  /**\n   * Asynchronously processes and validates the Astra service provider arguments within the\n   * Cassandra client configuration. This includes ensuring the presence of necessary Astra\n   * configurations like endpoint or datacenterID, setting up default secure connect bundle paths,\n   * and initializing default credentials if not provided.\n   *\n   * @param args The arguments for creating the Cassandra client with Astra configurations.\n   * @returns A Promise resolving to the modified CassandraClientArgs with Astra configurations processed.\n   * @throws Error if Astra configuration is incomplete or if both endpoint and datacenterID are missing.\n   */\n  private static async processAstraArgs(\n    args: CassandraClientArgs\n  ): Promise<CassandraClientArgs> {\n    const astraArgs = args.serviceProviderArgs?.astra;\n    if (!astraArgs) {\n      throw new Error(\"Astra configuration is not provided in args.\");\n    }\n\n    if (!astraArgs.endpoint && !astraArgs.datacenterID) {\n      throw new Error(\n        \"Astra endpoint or datacenterID must be provided in args.\"\n      );\n    }\n\n    // Extract datacenterID and regionName from endpoint if provided\n    if (astraArgs.endpoint) {\n      const endpoint = new URL(astraArgs.endpoint.toString());\n      const hostnameParts = endpoint.hostname.split(\"-\");\n      const domainSuffix = \".apps.astra.datastax.com\";\n\n      if (hostnameParts[hostnameParts.length - 1].endsWith(domainSuffix)) {\n        astraArgs.datacenterID =\n          astraArgs.datacenterID || hostnameParts.slice(0, 5).join(\"-\");\n\n        // Extract regionName by joining elements from index 5 to the end, and then remove the domain suffix\n        const fullRegionName = hostnameParts.slice(5).join(\"-\");\n        astraArgs.regionName =\n          astraArgs.regionName || fullRegionName.replace(domainSuffix, \"\");\n      }\n    }\n\n    // Initialize cloud configuration if not already defined\n    const modifiedArgs = {\n      ...args,\n      cloud: args.cloud || { secureConnectBundle: \"\" },\n    };\n\n    // Set default bundle location if it is not set\n    if (!modifiedArgs.cloud.secureConnectBundle) {\n      modifiedArgs.cloud.secureConnectBundle =\n        await CassandraClientFactory.getAstraDefaultBundleLocation(astraArgs);\n    }\n\n    // Ensure secure connect bundle exists\n    await CassandraClientFactory.setAstraBundle(\n      astraArgs,\n      modifiedArgs.cloud.secureConnectBundle\n    );\n\n    // Ensure credentials are set\n    modifiedArgs.credentials = modifiedArgs.credentials || {\n      username: \"token\",\n      password: astraArgs.token,\n    };\n\n    return modifiedArgs;\n  }\n\n  /**\n   * Get the default bundle filesystem location for the Astra Secure Connect Bundle.\n   *\n   * @param astraArgs The Astra service provider arguments.\n   * @returns The default bundle file path.\n   */\n  private static async getAstraDefaultBundleLocation(\n    astraArgs: AstraServiceProviderArgs\n  ): Promise<string> {\n    const dir = path.join(os.tmpdir(), \"cassandra-astra\");\n    await fs.mkdir(dir, { recursive: true });\n\n    let scbFileName = `astra-secure-connect-${astraArgs.datacenterID}`;\n    if (astraArgs.regionName) {\n      scbFileName += `-${astraArgs.regionName}`;\n    }\n    scbFileName += \".zip\";\n    const scbPath = path.join(dir, scbFileName);\n\n    return scbPath;\n  }\n\n  /**\n   * Ensures the Astra secure connect bundle specified by the path exists and is up to date.\n   * If the file does not exist or is deemed outdated (more than 360 days old), a new secure\n   * connect bundle is downloaded and saved to the specified path.\n   *\n   * @param astraArgs The Astra service provider arguments, including the datacenterID and optional regionName.\n   * @param scbPath The path (or URL) where the secure connect bundle is expected to be located.\n   * @returns A Promise that resolves when the secure connect bundle is verified or updated successfully.\n   * @throws Error if the bundle cannot be retrieved or saved to the specified path.\n   */\n  private static async setAstraBundle(\n    astraArgs: AstraServiceProviderArgs,\n    scbPath: string | URL\n  ): Promise<void> {\n    // If scbPath is a URL, we assume the URL is correct and do nothing further.\n    // But if it is a string, we need to check if the file exists and download it if necessary.\n    if (typeof scbPath === \"string\") {\n      try {\n        // Check if the file exists\n        const stats = await fs.stat(scbPath);\n\n        // Calculate the age of the file in days\n        const fileAgeInDays =\n          (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);\n\n        // File is more than 360 days old, download a fresh copy\n        if (fileAgeInDays > 360) {\n          await CassandraClientFactory.downloadAstraSecureConnectBundle(\n            astraArgs,\n            scbPath\n          );\n        }\n      } catch (error: unknown) {\n        if (\n          typeof error === \"object\" &&\n          error !== null &&\n          \"code\" in error &&\n          error.code === \"ENOENT\"\n        ) {\n          // Handle file not found error (ENOENT)\n          await CassandraClientFactory.downloadAstraSecureConnectBundle(\n            astraArgs,\n            scbPath\n          );\n        } else {\n          throw error;\n        }\n      }\n    }\n  }\n\n  /**\n   * Downloads the Astra secure connect bundle based on the provided Astra service provider arguments\n   * and saves it to the specified file path. If a regionName is specified and matches one of the\n   * available bundles, the regional bundle is preferred. Otherwise, the first available bundle URL is used.\n   *\n   * @param astraArgs - The Astra service provider arguments, including datacenterID and optional regionName.\n   * @param scbPath - The file path where the secure connect bundle should be saved.\n   * @returns A promise that resolves once the secure connect bundle is successfully downloaded and saved.\n   * @throws Error if there's an issue retrieving the bundle URLs or saving the bundle to the file path.\n   */\n  private static async downloadAstraSecureConnectBundle(\n    astraArgs: AstraServiceProviderArgs,\n    scbPath: string\n  ): Promise<void> {\n    if (!astraArgs.datacenterID) {\n      throw new Error(\"Astra datacenterID is not provided in args.\");\n    }\n\n    // First POST request gets all bundle locations for the database_id\n    const bundleURLTemplate = astraArgs.bundleUrlTemplate\n      ? astraArgs.bundleUrlTemplate\n      : \"https://api.astra.datastax.com/v2/databases/{database_id}/secureBundleURL?all=true\";\n    const url = bundleURLTemplate.replace(\n      \"{database_id}\",\n      astraArgs.datacenterID\n    );\n    const postResponse = await fetch(url, {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${astraArgs.token}`,\n        \"Content-Type\": \"application/json\",\n      },\n    });\n\n    if (!postResponse.ok) {\n      throw new Error(`HTTP error! Status: ${postResponse.status}`);\n    }\n\n    const postData = await postResponse.json();\n    if (!postData || !Array.isArray(postData) || postData.length === 0) {\n      throw new Error(\"Failed to get secure bundle URLs.\");\n    }\n\n    // Find the download URL for the region, if specified\n    let { downloadURL } = postData[0];\n    if (astraArgs.regionName) {\n      const regionalBundle = postData.find(\n        (bundle) => bundle.region === astraArgs.regionName\n      );\n      if (regionalBundle) {\n        downloadURL = regionalBundle.downloadURL;\n      }\n    }\n\n    // GET request to download the file itself, and write to disk\n    const getResponse = await fetch(downloadURL);\n    if (!getResponse.ok) {\n      throw new Error(`HTTP error! Status: ${getResponse.status}`);\n    }\n    const bundleData = await getResponse.arrayBuffer();\n    await fs.writeFile(scbPath, Buffer.from(bundleData));\n  }\n}\n\n/* =====================================================================================================================\n * =====================================================================================================================\n * Cassandra Table\n * =====================================================================================================================\n * =====================================================================================================================\n */\n\n/**\n * Represents the definition of a column within a Cassandra table schema.\n * This interface is used to specify the properties of table columns during table creation\n * and to define how columns are utilized in select queries.\n *\n * Properties:\n * - `name`: The name of the column.\n * - `type`: The data type of the column, used during table creation to define the schema.\n * - `partition`: Optional. Specifies whether the column is part of the partition key. Important for table creation.\n * - `alias`: Optional. An alias for the column that can be used in select queries for readability or to avoid naming conflicts.\n * - `binds`: Optional. Specifies values to be bound to the column in queries, supporting parameterized query construction.\n *\n */\nexport interface Column {\n  name: string;\n\n  // Used by 'create'\n  type: string;\n  partition?: boolean;\n\n  // Used by 'select'\n  alias?: string;\n  binds?: unknown | [unknown, ...unknown[]];\n}\n\n/**\n * Defines an index on a Cassandra table column, facilitating efficient querying by column values.\n * This interface specifies the necessary configuration for creating secondary indexes on table columns,\n * enhancing query performance and flexibility.\n *\n * Properties:\n * - `name`: The name of the index. Typically related to the column it indexes for clarity.\n * - `value`: The name of the column on which the index is created.\n * - `options`: Optional. Custom options for the index, specified as a string. This can include various index\n *               configurations supported by Cassandra, such as using specific indexing classes or options.\n *\n */\nexport interface Index {\n  name: string;\n  value: string;\n  options?: string;\n}\n\n/**\n * Represents a filter condition used in constructing WHERE clauses for querying Cassandra tables.\n * Filters specify the criteria used to select rows from a table, based on column values.\n *\n * Properties:\n * - `name`: The name of the column to filter on.\n * - `value`: The value(s) to match against the column. Can be a single value or an array of values for operations like IN.\n * - `operator`: Optional. The comparison operator to use (e.g., '=', '<', '>', 'IN'). Defaults to '=' if not specified.\n *\n */\nexport interface Filter {\n  name: string;\n  value: unknown | [unknown, ...unknown[]];\n  operator?: string;\n}\n\n/**\n * Defines a type for specifying WHERE clause conditions in Cassandra queries.\n * This can be a single `Filter` object, an array of `Filter` objects for multiple conditions,\n * or a `Record<string, unknown>` for simple equality conditions keyed by column name.\n */\nexport type WhereClause = Filter[] | Filter | Record<string, unknown>;\n\n/**\n * Defines the configuration arguments for initializing a Cassandra table within an application.\n * This interface extends `AsyncCallerParams`, incorporating asynchronous operation configurations,\n * and adds specific properties for table creation, query execution, and data manipulation in a\n * Cassandra database context.\n *\n * Properties:\n * - `table`: The name of the table to be used or created.\n * - `keyspace`: The keyspace within which the table exists or will be created.\n * - `primaryKey`: Specifies the column(s) that constitute the primary key of the table. This can be a single\n *                 `Column` object for a simple primary key or an array of `Column` objects for composite keys.\n * - `nonKeyColumns`: Defines columns that are not part of the primary key. Similar to `primaryKey`, this can be a\n *                    single `Column` object or an array of `Column` objects, supporting flexible table schema definitions.\n * - `withClause`: Optional. A string containing additional CQL table options to be included in the CREATE TABLE statement.\n *                 This enables the specification of various table behaviors and properties, such as compaction strategies\n *                 and TTL settings.\n * - `indices`: Optional. An array of `Index` objects defining secondary indices on the table for improved query performance\n *               on non-primary key columns.\n * - `batchSize`: Optional. Specifies the default size of batches for batched write operations to the table, affecting\n *                performance and consistency trade-offs.\n *\n */\nexport interface CassandraTableArgs extends AsyncCallerParams {\n  table: string;\n  keyspace: string;\n  primaryKey: Column | Column[];\n  nonKeyColumns: Column | Column[];\n  withClause?: string;\n  indices?: Index[];\n  batchSize?: number;\n}\n\n/**\n * Represents a Cassandra table, encapsulating functionality for schema definition, data manipulation, and querying.\n * This class provides a high-level abstraction over Cassandra's table operations, including creating tables,\n * inserting, updating, selecting, and deleting records. It leverages the CassandraClient for executing\n * operations and supports asynchronous interactions with the database.\n *\n * Key features include:\n * - Table and keyspace management: Allows for specifying table schema, including primary keys, columns,\n *   and indices, and handles the creation of these elements within the specified keyspace.\n * - Data manipulation: Offers methods for inserting (upserting) and deleting data in batches or individually,\n *   with support for asynchronous operation and concurrency control.\n * - Querying: Enables selecting data with flexible filtering, sorting, and pagination options.\n *\n * The class is designed to be instantiated with a set of configuration arguments (`CassandraTableArgs`)\n * that define the table's structure and operational parameters, providing a streamlined interface for\n * interacting with Cassandra tables in a structured and efficient manner.\n *\n * Usage Example:\n * ```typescript\n * const tableArgs: CassandraTableArgs = {\n *   table: 'my_table',\n *   keyspace: 'my_keyspace',\n *   primaryKey: [{ name: 'id', type: 'uuid', partition: true }],\n *   nonKeyColumns: [{ name: 'data', type: 'text' }],\n * };\n * const cassandraClient = new CassandraClient(clientConfig);\n * const myTable = new CassandraTable(tableArgs, cassandraClient);\n * ```\n *\n * This class simplifies Cassandra database interactions, making it easier to perform robust data operations\n * while maintaining clear separation of concerns and promoting code reusability.\n */\nexport class CassandraTable {\n  private client: Client;\n\n  private readonly keyspace: string;\n\n  private readonly table: string;\n\n  private primaryKey: Column[];\n\n  private nonKeyColumns: Column[];\n\n  private indices: Index[];\n\n  private withClause: string;\n\n  private batchSize: number;\n\n  private initializationPromise: Promise<void> | null = null;\n\n  private asyncCaller: AsyncCaller;\n\n  private constructorArgs: CassandraTableArgs;\n\n  /**\n   * Initializes a new instance of the CassandraTable class with specified configuration.\n   * This includes setting up the table schema (primary key, columns, and indices) and\n   * preparing the environment for executing queries against a Cassandra database.\n   *\n   * @param args Configuration arguments defining the table schema and operational settings.\n   * @param client Optional. A Cassandra Client instance. If not provided, one will be created\n   *               using the configuration specified in `args`.\n   */\n  constructor(args: CassandraTableArgs, client?: Client) {\n    const {\n      keyspace,\n      table,\n      primaryKey,\n      nonKeyColumns,\n      withClause = \"\",\n      indices = [],\n      batchSize = 1,\n      maxConcurrency = 25,\n    } = args;\n\n    // Set constructor args, which would include default values\n    this.constructorArgs = {\n      withClause,\n      indices,\n      batchSize,\n      maxConcurrency,\n      ...args,\n    };\n\n    this.asyncCaller = new AsyncCaller(this.constructorArgs);\n\n    // Assign properties\n    this.keyspace = keyspace;\n    this.table = table;\n    this.primaryKey = Array.isArray(primaryKey) ? primaryKey : [primaryKey];\n    this.nonKeyColumns = Array.isArray(nonKeyColumns)\n      ? nonKeyColumns\n      : [nonKeyColumns];\n    this.withClause = withClause.trim().replace(/^with\\s*/i, \"\");\n    this.indices = indices;\n    this.batchSize = batchSize;\n\n    // Start initialization but don't wait for it to complete here\n    this.initialize(client).catch((error) => {\n      console.error(\"Error during CassandraStore initialization:\", error);\n    });\n  }\n\n  /**\n   * Executes a SELECT query on the Cassandra table with optional filtering, ordering, and pagination.\n   * Allows for specifying columns to return, filter conditions, sort order, and limits on the number of results.\n   *\n   * @param columns Optional. Columns to include in the result set. If omitted, all columns are selected.\n   * @param filter Optional. Conditions to apply to the query for filtering results.\n   * @param orderBy Optional. Criteria to sort the result set.\n   * @param limit Optional. Maximum number of records to return.\n   * @param allowFiltering Optional. Enables ALLOW FILTERING option for queries that cannot be executed directly due to Cassandra's query restrictions.\n   * @param fetchSize Optional. The number of rows to fetch per page (for pagination).\n   * @param pagingState Optional. The paging state from a previous query execution, used for pagination.\n   * @returns A Promise resolving to the query result set.\n   */\n  async select(\n    columns?: Column[],\n    filter?: WhereClause,\n    orderBy?: Filter[],\n    limit?: number,\n    allowFiltering?: boolean,\n    fetchSize?: number,\n    pagingState?: string\n  ): Promise<driverTypes.ResultSet> {\n    await this.initialize();\n\n    // Ensure we have an array of Filter from the public interface\n    const filters = this.asFilters(filter);\n\n    // If no columns are specified, use all columns\n    const queryColumns = columns || [...this.primaryKey, ...this.nonKeyColumns];\n\n    const queryStr = this.buildSearchQuery(\n      queryColumns,\n      filters,\n      orderBy,\n      limit,\n      allowFiltering\n    );\n\n    const queryParams = [];\n\n    queryColumns.forEach(({ binds }) => {\n      if (binds !== undefined && binds !== null) {\n        if (Array.isArray(binds)) {\n          queryParams.push(...binds);\n        } else {\n          queryParams.push(binds);\n        }\n      }\n    });\n\n    if (filters) {\n      filters.forEach(({ value }) => {\n        if (Array.isArray(value)) {\n          queryParams.push(...value);\n        } else {\n          queryParams.push(value);\n        }\n      });\n    }\n\n    if (orderBy) {\n      orderBy.forEach(({ value }) => {\n        if (value !== undefined && value !== null) {\n          if (Array.isArray(value)) {\n            queryParams.push(...value);\n          } else {\n            queryParams.push(value);\n          }\n        }\n      });\n    }\n\n    if (limit) {\n      queryParams.push(limit);\n    }\n\n    const execOptions = {\n      prepare: true,\n      fetchSize: fetchSize || undefined,\n      pageState: pagingState || undefined,\n    };\n\n    return this.client.execute(queryStr, queryParams, execOptions);\n  }\n\n  /**\n   * Validates the correspondence between provided values and specified columns for database operations.\n   * This method checks if the number of values matches the number of specified columns, ensuring\n   * data integrity before executing insert or update operations. It also defaults to using all table columns\n   * if specific columns are not provided. Throws an error if the validation fails.\n   *\n   * @param values An array of values or an array of arrays of values to be inserted or updated. Each\n   *               inner array represents a set of values corresponding to one row in the table.\n   * @param columns Optional. An array of `Column` objects specifying the columns to be used for the operation.\n   *                If not provided, the method defaults to using both primary key and non-key columns of the table.\n   * @returns An array of `Column` objects that have been validated for the operation.\n   * @throws Error if the number of provided values does not match the number of specified columns.\n   * @private\n   */\n  private _columnCheck(\n    values: unknown[] | unknown[][],\n    columns?: Column[]\n  ): Column[] {\n    const cols = columns || [...this.primaryKey, ...this.nonKeyColumns];\n\n    if (!cols || cols.length === 0) {\n      throw new Error(\"Columns must be specified.\");\n    }\n\n    const firstValueSet = Array.isArray(values[0]) ? values[0] : values;\n\n    if (firstValueSet && firstValueSet.length !== cols.length) {\n      throw new Error(\"The number of values must match the number of columns.\");\n    }\n\n    return cols;\n  }\n\n  /**\n   * Inserts or updates records in the Cassandra table in batches, managing concurrency and batching size.\n   * This method organizes the provided values into batches and uses `_upsert` to perform the database operations.\n   *\n   * @param values An array of arrays, where each inner array contains values for a single record.\n   * @param columns Optional. Columns to be included in the insert/update operations. Defaults to all table columns.\n   * @param batchSize Optional. The size of each batch for the operation. Defaults to the class's batchSize property.\n   * @returns A Promise that resolves once all records have been upserted.\n   */\n  async upsert(\n    values: unknown[][],\n    columns?: Column[],\n    batchSize: number = this.batchSize\n  ): Promise<void> {\n    if (values.length === 0) {\n      return;\n    }\n\n    // Ensure the store is initialized before proceeding\n    await this.initialize();\n\n    const upsertColumns = this._columnCheck(values, columns);\n\n    // Initialize an array to hold promises for each batch insert\n    const upsertPromises: Promise<void>[] = [];\n\n    // Buffers to hold the current batch of vectors and documents\n    let currentBatch: unknown[][] = [];\n\n    // Loop through each vector/document pair to insert; we use\n    // <= vectors.length to ensure the last batch is inserted\n    for (let i = 0; i <= values.length; i += 1) {\n      // Check if we're still within the array boundaries\n      if (i < values.length) {\n        // Add the current vector and document to the batch\n        currentBatch.push(values[i]);\n      }\n\n      // Check if we've reached the batch size or end of the array\n      if (currentBatch.length >= batchSize || i === values.length) {\n        // Only proceed if there are items in the current batch\n        if (currentBatch.length > 0) {\n          // Create copies of the current batch arrays to use in the async insert operation\n          const batch = [...currentBatch];\n\n          // Execute the insert using the AsyncCaller - it will handle concurrency and queueing.\n          upsertPromises.push(\n            this.asyncCaller.call(() => this._upsert(batch, upsertColumns))\n          );\n\n          // Clear the current buffers for the next iteration\n          currentBatch = [];\n        }\n      }\n    }\n\n    // Wait for all insert operations to complete.\n    await Promise.all(upsertPromises);\n  }\n\n  /**\n   * Deletes rows from the Cassandra table that match the specified WHERE clause conditions.\n   *\n   * @param whereClause Defines the conditions that must be met for rows to be deleted. Can be a single filter,\n   * an array of filters, or a key-value map translating to filter conditions.\n   * @returns A Promise that resolves when the DELETE operation has completed.\n   */\n  async delete(whereClause: WhereClause) {\n    await this.initialize();\n\n    const filters = this.asFilters(whereClause);\n\n    const queryStr = `DELETE FROM ${this.keyspace}.${\n      this.table\n    } ${this.buildWhereClause(filters)}`;\n\n    const queryParams = filters.flatMap(({ value }) => {\n      if (Array.isArray(value)) {\n        return value;\n      } else {\n        return [value];\n      }\n    });\n\n    return this.client.execute(queryStr, queryParams, {\n      prepare: true,\n    });\n  }\n\n  /**\n   * Retrieves the Node.js Cassandra client instance associated with this table.\n   * This method ensures that the client is initialized and ready for use, returning the\n   * Cassandra client object that can be used for database operations directly.\n   * It initializes the client if it has not already been initialized.\n   *\n   * @returns A Promise that resolves to the Cassandra Client instance used by this table for database interactions.\n   */\n  async getClient() {\n    await this.initialize();\n    return this.client;\n  }\n\n  /**\n   * Constructs the PRIMARY KEY clause for a Cassandra CREATE TABLE statement based on the specified columns.\n   * This method organizes the provided columns into partition and clustering keys, forming the necessary syntax\n   * for the PRIMARY KEY clause in a Cassandra table schema definition. It supports complex primary key structures,\n   * including composite partition keys and clustering columns.\n   *\n   * - Partition columns are those marked with the `partition` property. If multiple partition columns are provided,\n   *   they are grouped together in parentheses as a composite partition key.\n   * - Clustering columns are those not marked as partition keys and are listed after the partition key(s).\n   *   They determine the sort order of rows within a partition.\n   *\n   * The method ensures the correct syntax for primary keys, handling both simple and composite key structures,\n   * and throws an error if no partition or clustering columns are provided.\n   *\n   * @param columns An array of `Column` objects representing the columns to be included in the primary key.\n   *                Each column must have a `name` and may have a `partition` boolean indicating if it is part\n   *                of the partition key.\n   * @returns The PRIMARY KEY clause as a string, ready to be included in a CREATE TABLE statement.\n   * @throws Error if no columns are marked as partition keys or if no columns are provided.\n   * @private\n   */\n  private buildPrimaryKey(columns: Column[]): string {\n    // Partition columns may be specified with optional attribute col.partition\n    const partitionColumns = columns\n      .filter((col) => col.partition)\n      .map((col) => col.name)\n      .join(\", \");\n\n    // All columns not part of the partition key are clustering columns\n    const clusteringColumns = columns\n      .filter((col) => !col.partition)\n      .map((col) => col.name)\n      .join(\", \");\n\n    let primaryKey = \"\";\n\n    // If partition columns are specified, they are included in a () wrapper\n    // If not, the clustering columns are used, and the first clustering column\n    // is the partition key per normal Cassandra behaviour.\n    if (partitionColumns && clusteringColumns) {\n      primaryKey = `PRIMARY KEY ((${partitionColumns}), ${clusteringColumns})`;\n    } else if (partitionColumns) {\n      primaryKey = `PRIMARY KEY (${partitionColumns})`;\n    } else if (clusteringColumns) {\n      primaryKey = `PRIMARY KEY (${clusteringColumns})`;\n    } else {\n      throw new Error(\n        \"No partition or clustering columns provided for PRIMARY KEY definition.\"\n      );\n    }\n\n    return primaryKey;\n  }\n\n  /**\n   * Type guard that checks if a given object conforms to the `Filter` interface.\n   * This method is used to determine if an object can be treated as a filter for Cassandra\n   * query conditions. It evaluates the object's structure, specifically looking for `name`\n   * and `value` properties, which are essential for defining a filter in Cassandra queries.\n   *\n   * @param obj The object to be evaluated.\n   * @returns A boolean value indicating whether the object is a `Filter`. Returns `true`\n   *          if the object has both `name` and `value` properties, signifying it meets the\n   *          criteria for being used as a filter in database operations; otherwise, returns `false`.\n   * @private\n   */\n  private isFilter(obj: unknown): obj is Filter {\n    return (\n      typeof obj === \"object\" && obj !== null && \"name\" in obj && \"value\" in obj\n    );\n  }\n\n  /**\n   * Helper to convert Record<string,unknown> to a Filter[]\n   * @param record: a key-value Record collection\n   * @returns Record as a Filter[]\n   */\n  private convertToFilters(record: Record<string, unknown>): Filter[] {\n    return Object.entries(record).map(([name, value]) => ({\n      name,\n      value,\n      operator: \"=\",\n    }));\n  }\n\n  /**\n   * Converts a key-value pair record into an array of `Filter` objects suitable for Cassandra query conditions.\n   * This utility method allows for a more flexible specification of filter conditions by transforming\n   * a simple object notation into the structured format expected by Cassandra query builders. Each key-value\n   * pair in the record is interpreted as a filter condition, where the key represents the column name and\n   * the value represents the filtering criterion.\n   *\n   * The method assumes a default equality operator for each filter. It is particularly useful for\n   * converting concise filter specifications into the detailed format required for constructing CQL queries.\n   *\n   * @param record A key-value pair object where each entry represents a filter condition, with the key\n   *               as the column name and the value as the filter value. The value can be a single value\n   *               or an array to support IN queries with multiple criteria.\n   * @returns An array of `Filter` objects, each representing a condition extracted from the input record.\n   *          The array can be directly used in constructing query WHERE clauses.\n   * @private\n   */\n  private asFilters(record: WhereClause | undefined): Filter[] {\n    if (!record) {\n      return [];\n    }\n\n    // If record is already an array\n    if (Array.isArray(record)) {\n      return record.flatMap((item) => {\n        // Check if item is a Filter before passing it to convertToFilters\n        if (this.isFilter(item)) {\n          return [item];\n        } else {\n          // Here item is treated as Record<string, unknown>\n          return this.convertToFilters(item);\n        }\n      });\n    }\n\n    // If record is a single Filter object, return it in an array\n    if (this.isFilter(record)) {\n      return [record];\n    }\n\n    // If record is a Record<string, unknown>, convert it to an array of Filter\n    return this.convertToFilters(record);\n  }\n\n  /**\n   * Constructs the WHERE clause of a CQL query from an array of `Filter` objects.\n   * This method generates the conditional part of a Cassandra Query Language (CQL) statement,\n   * allowing for complex query constructions based on provided filters. Each filter in the array\n   * translates into a condition within the WHERE clause, with support for various comparison operators.\n   *\n   * The method handles the assembly of these conditions into a syntactically correct CQL WHERE clause,\n   * including the appropriate use of placeholders (?) for parameter binding in prepared statements.\n   * It supports a range of operators, defaulting to \"=\" (equality) if an operator is not explicitly specified\n   * in a filter. Filters with multiple values (e.g., for IN conditions) are also correctly formatted.\n   *\n   * @param filters Optional. An array of `Filter` objects representing the conditions to apply in the WHERE clause.\n   *                Each `Filter` includes a column name (`name`), a value or array of values (`value`), and optionally,\n   *                an operator (`operator`). If no filters are provided, an empty string is returned.\n   * @returns The constructed WHERE clause as a string, ready to be appended to a CQL query. If no filters\n   *          are provided, returns an empty string, indicating no WHERE clause should be applied.\n   * @private\n   */\n  private buildWhereClause(filters?: Filter[]): string {\n    if (!filters || filters.length === 0) {\n      return \"\";\n    }\n\n    const whereConditions = filters.map(({ name, operator = \"=\", value }) => {\n      // Normalize the operator to handle case-insensitive comparison\n      const normalizedOperator = operator.toUpperCase();\n\n      // Convert value to an array if it's not one, to simplify processing\n      const valueArray = Array.isArray(value) ? value : [value];\n\n      if (valueArray.length === 1 && normalizedOperator !== \"IN\") {\n        return `${name} ${operator} ?`;\n      } else {\n        // Remove quoted strings from 'name' to prevent counting '?' inside quotes as placeholders\n        const quotesPattern = /'[^']*'|\"[^\"]*\"/g;\n        const modifiedName = name.replace(quotesPattern, \"\");\n        const nameQuestionMarkCount = (modifiedName.match(/\\?/g) || []).length;\n\n        // Check if there are enough elements in the array for the right side of the operator,\n        // adjusted for any '?' placeholders within the 'name' itself\n        if (valueArray.length < nameQuestionMarkCount + 1) {\n          throw new Error(\n            \"Insufficient bind variables for the filter condition.\"\n          );\n        }\n\n        // Generate placeholders, considering any '?' placeholders that might have been part of 'name'\n        const effectiveLength = Math.max(\n          valueArray.length - nameQuestionMarkCount,\n          1\n        );\n        const placeholders = new Array(effectiveLength).fill(\"?\").join(\", \");\n\n        // Wrap placeolders in a () if the operator is IN\n        if (normalizedOperator === \"IN\") {\n          return `${name} ${operator} (${placeholders})`;\n        } else {\n          return `${name} ${operator} ${placeholders}`;\n        }\n      }\n    });\n\n    return `WHERE ${whereConditions.join(\" AND \")}`;\n  }\n\n  /**\n   * Generates the ORDER BY clause for a CQL query from an array of `Filter` objects.\n   * This method forms the sorting part of a Cassandra Query Language (CQL) statement,\n   * allowing for detailed control over the order of results based on specified column names\n   * and directions. Each filter in the array represents a column and direction to sort by.\n   *\n   * It is important to note that unlike the traditional use of `Filter` objects for filtering,\n   * in this context, they are repurposed to specify sorting criteria. The `name` field indicates\n   * the column to sort by, and the `operator` field is used to specify the sort direction (`ASC` or `DESC`).\n   * The `value` field is not utilized for constructing the ORDER BY clause and can be omitted.\n   *\n   * @param filters Optional. An array of `Filter` objects where each object specifies a column and\n   *                direction for sorting. The `name` field of each filter represents the column name,\n   *                and the `operator` field should contain the sorting direction (`ASC` or `DESC`).\n   *                If no filters are provided, the method returns an empty string.\n   * @returns The constructed ORDER BY clause as a string, suitable for appending to a CQL query.\n   *          If no sorting criteria are provided, returns an empty string, indicating no ORDER BY\n   *          clause should be applied to the query.\n   * @private\n   */\n  private buildOrderByClause(filters?: Filter[]): string {\n    if (!filters || filters.length === 0) {\n      return \"\";\n    }\n\n    const orderBy = filters.map(({ name, operator, value }) => {\n      if (value) {\n        return `${name} ${operator} ?`;\n      } else if (operator) {\n        return `${name} ${operator}`;\n      } else {\n        return name;\n      }\n    });\n\n    return `ORDER BY ${orderBy.join(\" , \")}`;\n  }\n\n  /**\n   * Constructs a CQL search query string for retrieving records from a Cassandra table.\n   * This method combines various query components, including selected columns, filters, sorting criteria,\n   * and pagination options, to form a complete and executable CQL query. It allows for fine-grained control\n   * over the query construction process, enabling the inclusion of conditional filtering, ordering of results,\n   * and limiting the number of returned records, with an optional allowance for filtering.\n   *\n   * The method meticulously constructs the SELECT part of the query using the provided columns, applies\n   * the WHERE clause based on given filters, sorts the result set according to the orderBy criteria, and\n   * restricts the number of results with the limit parameter. Additionally, it can enable the ALLOW FILTERING\n   * option for queries that require server-side filtering beyond the capabilities of primary and secondary indexes.\n   *\n   * @param queryColumns An array of `Column` objects specifying which columns to include in the result set.\n   *                     Each column can also have an alias defined for use in the query's result set.\n   * @param filters Optional. An array of `Filter` objects to apply as conditions in the WHERE clause of the query.\n   * @param orderBy Optional. An array of `Filter` objects specifying the ordering of the returned records.\n   *                Although repurposed as `Filter` objects, here they define the column names and the sort direction (ASC/DESC).\n   * @param limit Optional. A numeric value specifying the maximum number of records the query should return.\n   * @param allowFiltering Optional. A boolean flag that, when true, includes the ALLOW FILTERING clause in the query,\n   *                        permitting Cassandra to execute queries that might not be efficiently indexable.\n   * @returns A string representing the fully constructed CQL search query, ready for execution against a Cassandra table.\n   * @private\n   */\n  private buildSearchQuery(\n    queryColumns: Column[],\n    filters?: Filter[],\n    orderBy?: Filter[],\n    limit?: number,\n    allowFiltering?: boolean\n  ): string {\n    const selectColumns = queryColumns\n      .map((col) => (col.alias ? `${col.name} AS ${col.alias}` : col.name))\n      .join(\", \");\n\n    const whereClause = filters ? this.buildWhereClause(filters) : \"\";\n\n    const orderByClause = orderBy ? this.buildOrderByClause(orderBy) : \"\";\n\n    const limitClause = limit ? \"LIMIT ?\" : \"\";\n\n    const allowFilteringClause = allowFiltering ? \"ALLOW FILTERING\" : \"\";\n\n    const cqlQuery = `SELECT ${selectColumns} FROM ${this.keyspace}.${this.table} ${whereClause} ${orderByClause} ${limitClause} ${allowFilteringClause}`;\n\n    return cqlQuery;\n  }\n\n  /**\n   * Initializes the CassandraTable instance, ensuring it is ready for database operations.\n   * This method is responsible for setting up the internal Cassandra client, creating the table\n   * if it does not already exist, and preparing any indices as specified in the table configuration.\n   * The initialization process is performed only once; subsequent calls return the result of the\n   * initial setup. If a Cassandra `Client` instance is provided, it is used directly; otherwise,\n   * a new client is created based on the table's configuration.\n   *\n   * The initialization includes:\n   * - Assigning the provided or newly created Cassandra client to the internal client property.\n   * - Executing a CQL statement to create the table with the specified columns, primary key, and\n   *   any additional options provided in the `withClause`.\n   * - Creating any custom indices as defined in the table's indices array.\n   *\n   * This method leverages the asynchronous nature of JavaScript to perform potentially time-consuming\n   * tasks, such as network requests to the Cassandra cluster, without blocking the execution thread.\n   *\n   * @param client Optional. A `Client` instance from the cassandra-driver package. If provided, this client\n   *               is used for all database operations performed by the instance. Otherwise, a new client\n   *               is instantiated based on the configuration provided at the CassandraTable instance creation.\n   * @returns A Promise that resolves once the initialization process has completed, indicating the instance\n   *          is ready for database operations. If initialization has already occurred, the method returns\n   *          immediately without repeating the setup process.\n   * @private\n   */\n  private async initialize(client?: Client): Promise<void> {\n    // If already initialized or initialization is in progress, return the existing promise\n    if (this.initializationPromise) {\n      return this.initializationPromise;\n    }\n\n    // Start the initialization process and store the promise\n    this.initializationPromise = this.performInitialization(client)\n      .then(() => {\n        // Initialization successful\n      })\n      .catch((error) => {\n        // Reset to allow retrying in case of failure\n        this.initializationPromise = null;\n        throw error;\n      });\n\n    return this.initializationPromise;\n  }\n\n  /**\n   * Performs the actual initialization tasks for the CassandraTable instance.\n   * This method is invoked by the `initialize` method to carry out the concrete steps necessary for preparing\n   * the CassandraTable instance for operation. It includes establishing the Cassandra client (either by utilizing\n   * an existing client passed as a parameter or by creating a new one based on the instance's configuration),\n   * and executing the required CQL statements to create the table and its indices according to the specifications\n   * provided during the instance's creation.\n   *\n   * The process encapsulates:\n   * 1. Assigning the provided Cassandra `Client` to the instance, or creating a new one if none is provided.\n   * 2. Creating the table with the specified schema if it does not exist. This involves constructing a CQL\n   *    `CREATE TABLE` statement that includes columns, primary key configuration, and any specified table options.\n   * 3. Creating any indices specified in the instance's configuration using CQL `CREATE INDEX` statements, allowing\n   *    for custom index options if provided.\n   *\n   * This method ensures that the table and its environment are correctly set up for subsequent database operations,\n   * encapsulating initialization logic to maintain separation of concerns and improve code readability and maintainability.\n   *\n   * @param client Optional. An instance of the Cassandra `Client` from the cassandra-driver package. If provided,\n   *               this client is used for all interactions with the Cassandra database. If not provided, a new client\n   *               is instantiated based on the provided configuration during the CassandraTable instance creation.\n   * @returns A Promise that resolves when all initialization steps have been successfully completed, indicating\n   *          that the CassandraTable instance is fully prepared for database operations.\n   * @private\n   */\n  private async performInitialization(client?: Client) {\n    if (client) {\n      this.client = client;\n    } else {\n      this.client = await CassandraClientFactory.getClient(\n        this.constructorArgs\n      );\n    }\n\n    const allColumns = [...this.primaryKey, ...this.nonKeyColumns];\n\n    let cql = \"\";\n    cql = `CREATE TABLE IF NOT EXISTS ${this.keyspace}.${this.table} (\n      ${\n        allColumns.length > 0\n          ? `${allColumns.map((col) => `${col.name} ${col.type}`).join(\", \")}`\n          : \"\"\n      }\n      , ${this.buildPrimaryKey(this.primaryKey)}\n  ) ${this.withClause ? `WITH ${this.withClause}` : \"\"};`;\n\n    await this.client.execute(cql);\n\n    // Helper function to format custom index OPTIONS clause\n    const _formatOptions = (options: string | undefined): string => {\n      if (!options) {\n        return \"\";\n      }\n\n      let formattedOptions = options.trim();\n      if (!formattedOptions.toLowerCase().startsWith(\"with options =\")) {\n        formattedOptions = `WITH OPTIONS =  ${formattedOptions}`;\n      }\n\n      return formattedOptions;\n    };\n\n    for await (const { name, value, options } of this.indices) {\n      const optionsClause = _formatOptions(options);\n      cql = `CREATE CUSTOM INDEX IF NOT EXISTS idx_${this.table}_${name}\n               ON ${this.keyspace}.${this.table} ${value} USING 'StorageAttachedIndex' ${optionsClause};`;\n      await this.client.execute(cql);\n    }\n  }\n\n  /**\n   * Performs the actual insert or update operation (upsert) on the Cassandra table for a batch of values.\n   * This method constructs and executes a CQL INSERT statement for each value in the batch.\n   *\n   * @param values An array of arrays, where each inner array contains values corresponding to the specified columns.\n   * @param columns Optional. Specifies the columns into which the values should be inserted. Defaults to all columns.\n   * @returns A Promise that resolves when the operation has completed.\n   * @private\n   */\n  private async _upsert(\n    values: unknown[][],\n    columns?: Column[]\n  ): Promise<void> {\n    if (values.length === 0) {\n      return;\n    }\n\n    await this.initialize();\n\n    const upsertColumns = this._columnCheck(values, columns);\n\n    const upsertColumnNames = upsertColumns.map((col) => col.name);\n\n    const columnCount = upsertColumnNames.length;\n\n    const bindPlaceholders = Array(columnCount).fill(\"?\").join(\", \");\n\n    const upsertString = `INSERT INTO ${this.keyspace}.${\n      this.table\n    } (${upsertColumnNames.join(\", \")}) VALUES (${bindPlaceholders})`;\n\n    // Initialize an array to hold query objects\n    const queries = [];\n\n    for (let i = 0; i < values.length; i += 1) {\n      const query = {\n        query: upsertString,\n        params: values[i],\n      };\n\n      // Add the query to the list\n      queries.push(query);\n    }\n\n    // Execute the queries: use a batch if multiple, otherwise execute a single query\n    if (queries.length === 1) {\n      await this.client.execute(queries[0].query, queries[0].params, {\n        prepare: true,\n      });\n    } else {\n      await this.client.batch(queries, { prepare: true, logged: false });\n    }\n  }\n}\n"],"mappings":"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"}