{"version":3,"file":"redis.mjs","names":["#opts","#client","#ensure","#fields","#materialize","#parseSearchReply","#project","#decode"],"sources":["../../../src/batteries/vector/redis/index.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/batteries/vector/redis\n *\n * One adapter for the whole Redis/Valkey family. The vector engine is RediSearch\n * (`FT.CREATE` / `FT.SEARCH` with a `VECTOR` field over a HASH keyspace); records are\n * stored as Redis hashes keyed `${collection}:${id}`, with the vector packed as a\n * little-endian Float32 buffer. Metadata scalars are stored as TAG/NUMERIC fields so the\n * neutral filter tree compiles to RediSearch query syntax.\n *\n * Works against `redis/redis-stack-server` (bundles RediSearch) and any Redis/Valkey with\n * the RediSearch module loaded.\n */\n\nimport { BaseVectorStore } from '../contract'\nimport { validateRecords } from '../validation'\nimport { isInstanceOf } from '@nhtio/adk/guards'\nimport { normalizeScore, sanitizeMetadata } from '../helpers'\nimport { isFilterCondition, isRawFilter, isFilterGroup } from '../filters'\nimport {\n  E_VECTOR_STORE_DRIVER_UNAVAILABLE,\n  E_VECTOR_STORE_COLLECTION_FAILED,\n  E_VECTOR_STORE_UPSERT_FAILED,\n  E_VECTOR_STORE_SEARCH_FAILED,\n  E_VECTOR_STORE_DELETE_FAILED,\n  E_VECTOR_STORE_DIMENSION_MISMATCH,\n  E_VECTOR_STORE_UNSUPPORTED_FILTER_OPERATOR,\n  E_VECTOR_STORE_UNSUPPORTED_OPERATION,\n  E_VECTOR_STORE_CONNECTION_FAILED,\n} from '../exceptions'\nimport type { VectorFilter } from '../filters'\nimport type { SearchPlan, UpsertPlan, DeletePlan, CollectionSpec } from '../plan'\nimport type {\n  VectorMatch,\n  VectorStoreCapabilities,\n  BaseVectorStoreOptions,\n  VectorMetadata,\n  DistanceMetric,\n} from '../types'\n\nexport interface RedisVectorStoreOptions extends BaseVectorStoreOptions {\n  /** Connection and authentication parameters for the backend. */\n  connection?: { url?: string; username?: string; password?: string }\n}\n\nconst getRedisClient = async () => {\n  try {\n    const mod = await import('redis')\n    return mod.createClient\n  } catch {\n    throw new E_VECTOR_STORE_DRIVER_UNAVAILABLE(['redis'])\n  }\n}\n\n// RediSearch index name + key prefix derived from the logical collection.\nconst indexName = (collection: string): string => `idx:${collection}`\nconst keyPrefix = (collection: string): string => `${collection}:`\nconst keyFor = (collection: string, id: string): string => `${collection}:${id}`\n\n// Pack a number[] into a little-endian Float32 Buffer (RediSearch FLOAT32 vector blob).\nconst packVector = (vector: number[]): Buffer => {\n  const buf = Buffer.allocUnsafe(vector.length * 4)\n  for (let i = 0; i < vector.length; i++) buf.writeFloatLE(vector[i], i * 4)\n  return buf\n}\n\nconst unpackVector = (buf: Buffer): number[] => {\n  const out: number[] = []\n  for (let i = 0; i < buf.length; i += 4) out.push(buf.readFloatLE(i))\n  return out\n}\n\n// RediSearch distance metric for the index. Cosine + L2 are native; 'dot' maps to IP.\nconst metricToRedis = (metric: DistanceMetric): string =>\n  metric === 'euclidean' ? 'L2' : metric === 'dot' ? 'IP' : 'COSINE'\n\n// Escape a value for a RediSearch TAG filter (`@field:{value}`). Special chars must be escaped.\nconst escapeTag = (value: string): string =>\n  value.replace(/[,.<>{}[\\]\"':;!@#$%^&*()\\-+=~|/\\\\ ]/g, '\\\\$&')\n\n/**\n * Compile the neutral filter tree to a RediSearch query fragment. TAG fields hold string /\n * boolean metadata; NUMERIC fields hold numbers. Returns a fragment to AND with the KNN\n * clause (or to use alone for a filter-scan). Throws on operators RediSearch can't express.\n */\nexport const translateRedisFilter = (filter?: VectorFilter): string => {\n  if (!filter) return '*'\n  if (isRawFilter(filter)) {\n    if (filter.$dialect === 'redis' && typeof filter.$raw === 'string') {\n      return filter.$raw\n    }\n    throw new E_VECTOR_STORE_UNSUPPORTED_FILTER_OPERATOR(['redis', String(filter.$dialect)])\n  }\n  if (isFilterCondition(filter)) {\n    const { field, op, value } = filter\n    const tag = (v: unknown): string => `@${field}:{${escapeTag(String(v))}}`\n    const num = (lo: string, hi: string): string => `@${field}:[${lo} ${hi}]`\n    // Range bounds must be finite numbers — a non-numeric value would be concatenated\n    // raw into the query fragment, letting it break out of the `[lo hi]` bracket.\n    const bound = (v: unknown): string => {\n      const n = Number(v)\n      if (!Number.isFinite(n)) {\n        throw new E_VECTOR_STORE_UNSUPPORTED_FILTER_OPERATOR([\n          'redis',\n          `${op} with non-numeric value`,\n        ])\n      }\n      return String(n)\n    }\n    switch (op) {\n      case 'eq':\n        return typeof value === 'number' ? num(bound(value), bound(value)) : tag(value)\n      case 'ne':\n        return typeof value === 'number' ? `-${num(bound(value), bound(value))}` : `-${tag(value)}`\n      case 'gt':\n        return num(`(${bound(value)}`, '+inf')\n      case 'gte':\n        return num(bound(value), '+inf')\n      case 'lt':\n        return num('-inf', `(${bound(value)}`)\n      case 'lte':\n        return num('-inf', bound(value))\n      case 'in': {\n        if (!Array.isArray(value) || value.length === 0) return '@__never:{__never}'\n        return `@${field}:{${value.map((v) => escapeTag(String(v))).join('|')}}`\n      }\n      case 'nin': {\n        if (!Array.isArray(value) || value.length === 0) return '*'\n        return `-@${field}:{${value.map((v) => escapeTag(String(v))).join('|')}}`\n      }\n      case 'exists':\n        return `@${field}:*`\n      default:\n        throw new E_VECTOR_STORE_UNSUPPORTED_FILTER_OPERATOR(['redis', op])\n    }\n  }\n  if (isFilterGroup(filter)) {\n    const { and, or, not } = filter\n    if (and) {\n      const parts = and.map(translateRedisFilter).filter((p) => p !== '*')\n      return parts.length === 0 ? '*' : `(${parts.join(' ')})`\n    }\n    if (or) {\n      const parts = or.map(translateRedisFilter).filter((p) => p !== '*')\n      return parts.length === 0 ? '*' : `(${parts.join(' | ')})`\n    }\n    if (not) {\n      const inner = translateRedisFilter(not)\n      return inner === '*' ? '*' : `-${inner}`\n    }\n    return '*'\n  }\n  return '*'\n}\n\nexport class RedisVectorStore extends BaseVectorStore {\n  readonly capabilities: VectorStoreCapabilities = {\n    transactions: false,\n    namedVectors: false,\n    rename: false,\n    rawSql: false,\n    builtInEncoding: false,\n    // RediSearch indexes synchronously on HSET; a write is visible on resolve. No-op option.\n    consistency: { configurable: false, default: 'strong', modes: ['strong'] },\n  }\n  #client: any | null = null\n  // Per-collection declared metadata fields, so FT.CREATE can declare TAG/NUMERIC schema.\n  #fields: Map<string, CollectionSpec['fields']> = new Map()\n\n  get #opts(): RedisVectorStoreOptions {\n    return this.options as RedisVectorStoreOptions\n  }\n\n  /** Static availability probe: whether this adapter's runtime driver can load in the current environment. */\n  static isAvailable(): boolean {\n    return typeof process !== 'undefined'\n  }\n  isAvailable(): boolean {\n    return typeof process !== 'undefined'\n  }\n\n  async connect(): Promise<void> {\n    if (this.#client) return\n    const createClient = await getRedisClient()\n    const c = this.#opts.connection || {}\n    try {\n      this.#client = createClient({\n        url: c.url ?? 'redis://localhost:6379',\n        username: c.username,\n        password: c.password,\n      })\n      await this.#client.connect()\n    } catch (err) {\n      throw new E_VECTOR_STORE_CONNECTION_FAILED([String(err)])\n    }\n  }\n\n  async close(): Promise<void> {\n    if (this.#client) {\n      try {\n        await this.#client.quit()\n      } catch {\n        // ignore quit errors on teardown\n      }\n      this.#client = null\n    }\n  }\n\n  async #ensure(): Promise<any> {\n    if (!this.#client) await this.connect()\n    return this.#client!\n  }\n\n  async createCollection(spec: CollectionSpec, ifNotExists: boolean): Promise<void> {\n    const client = await this.#ensure()\n    this.#fields.set(spec.collection, spec.fields)\n    if (ifNotExists && (await this.hasCollection(spec.collection))) return\n    const dim = spec.vector.dimensions\n    const distance = metricToRedis(spec.vector.metric)\n    // Build the FT.CREATE schema: the vector field + a TAG/NUMERIC field per declared payload\n    // column. Declared fields are advisory; undeclared metadata is still stored on the hash\n    // (just not independently filterable).\n    const schema: string[] = [\n      '__vector',\n      'VECTOR',\n      'FLAT',\n      '6',\n      'TYPE',\n      'FLOAT32',\n      'DIM',\n      String(dim),\n      'DISTANCE_METRIC',\n      distance,\n    ]\n    for (const f of spec.fields) {\n      schema.push(f.name)\n      schema.push(f.type === 'integer' || f.type === 'number' ? 'NUMERIC' : 'TAG')\n    }\n    try {\n      await client.sendCommand([\n        'FT.CREATE',\n        indexName(spec.collection),\n        'ON',\n        'HASH',\n        'PREFIX',\n        '1',\n        keyPrefix(spec.collection),\n        'SCHEMA',\n        ...schema,\n      ])\n    } catch (err) {\n      const msg = String(err)\n      if (msg.includes('Index already exists')) return\n      throw new E_VECTOR_STORE_COLLECTION_FAILED(['createCollection', msg])\n    }\n  }\n\n  async dropCollection(collection: string, ifExists: boolean): Promise<void> {\n    const client = await this.#ensure()\n    if (ifExists && !(await this.hasCollection(collection))) return\n    try {\n      // DD drops the indexed documents too.\n      await client.sendCommand(['FT.DROPINDEX', indexName(collection), 'DD'])\n    } catch (err) {\n      const msg = String(err)\n      if (ifExists && msg.includes('Unknown')) return\n      throw new E_VECTOR_STORE_COLLECTION_FAILED(['dropCollection', msg])\n    }\n  }\n\n  async hasCollection(collection: string): Promise<boolean> {\n    const client = await this.#ensure()\n    try {\n      await client.sendCommand(['FT.INFO', indexName(collection)])\n      return true\n    } catch {\n      return false\n    }\n  }\n\n  async renameCollection(_from: string, _to: string): Promise<void> {\n    throw new E_VECTOR_STORE_UNSUPPORTED_OPERATION(['renameCollection', 'redis'])\n  }\n\n  async executeUpsert(plan: UpsertPlan): Promise<void> {\n    if (plan.records.length === 0) return\n    validateRecords(plan.records)\n    const client = await this.#ensure()\n    const expected = this.#opts.dimensions\n    try {\n      for (const r of plan.records) {\n        let vector = r.vector\n        if (!vector && r.document) {\n          const [v] = await this.encode([r.document], 'document')\n          vector = v\n        }\n        if (!vector) {\n          throw new E_VECTOR_STORE_UPSERT_FAILED(['Record missing vector and document'])\n        }\n        if (expected !== undefined && vector.length !== expected) {\n          throw new E_VECTOR_STORE_DIMENSION_MISMATCH([expected, vector.length])\n        }\n        // Hash fields: the vector blob, the document, and each metadata scalar (stringified\n        // for TAG, numeric kept as-is). __id/__document are reserved internal fields.\n        const blob = packVector(vector)\n        const entries: Record<string, string | Buffer> = {\n          // Raw FLOAT32 blob for RediSearch to index on...\n          __vector: blob,\n          // ...plus a base64 copy so the vector survives a plain (string) hGetAll read-back\n          // (node-redis v6's buffer-mode read API is version-fragile; base64 is portable).\n          __vecb64: blob.toString('base64'),\n          __id: r.id,\n        }\n        if (r.document !== undefined) entries.__document = r.document\n        if (r.metadata) {\n          for (const [k, v] of Object.entries(sanitizeMetadata(r.metadata))) {\n            if (v === null || v === undefined) continue\n            entries[k] = typeof v === 'object' ? JSON.stringify(v) : String(v)\n          }\n        }\n        await client.hSet(keyFor(plan.collection, r.id), entries)\n      }\n    } catch (err) {\n      if (\n        isInstanceOf(err, 'E_VECTOR_STORE_DIMENSION_MISMATCH', E_VECTOR_STORE_DIMENSION_MISMATCH) ||\n        isInstanceOf(err, 'E_VECTOR_STORE_UPSERT_FAILED', E_VECTOR_STORE_UPSERT_FAILED)\n      )\n        throw err\n      throw new E_VECTOR_STORE_UPSERT_FAILED([String(err)])\n    }\n  }\n\n  async executeSearch(plan: SearchPlan): Promise<VectorMatch[]> {\n    const client = await this.#ensure()\n    const metric = this.#opts.metric ?? 'cosine'\n    let queryVector: number[] | undefined\n    if (plan.near) {\n      if ('vector' in plan.near) {\n        queryVector = plan.near.vector\n      } else if ('serverText' in plan.near) {\n        const [v] = await this.encode([plan.near.serverText], 'query')\n        queryVector = v\n      } else if ('id' in plan.near) {\n        const raw = await client.hGetAll(keyFor(plan.collection, plan.near.id))\n        const b64 = raw?.__vecb64\n        if (!b64) {\n          throw new E_VECTOR_STORE_SEARCH_FAILED(['Referenced id not found: ' + plan.near.id])\n        }\n        queryVector = unpackVector(Buffer.from(String(b64), 'base64'))\n      }\n    }\n\n    try {\n      if (queryVector) {\n        const filterFrag = translateRedisFilter(plan.filter)\n        const k = plan.topK + (plan.offset ?? 0)\n        const query = `(${filterFrag})=>[KNN ${k} @__vector $BLOB AS __score]`\n        const reply: any = await client.sendCommand([\n          'FT.SEARCH',\n          indexName(plan.collection),\n          query,\n          'PARAMS',\n          '2',\n          'BLOB',\n          packVector(queryVector),\n          'SORTBY',\n          '__score',\n          'DIALECT',\n          '2',\n          'LIMIT',\n          String(plan.offset ?? 0),\n          String(plan.topK),\n          'RETURN',\n          '1',\n          '__score',\n        ])\n        return await this.#materialize(client, reply, plan, metric, true)\n      } else {\n        // Filter-scan: FT.SEARCH with the filter fragment, no KNN.\n        const filterFrag = translateRedisFilter(plan.filter)\n        const reply: any = await client.sendCommand([\n          'FT.SEARCH',\n          indexName(plan.collection),\n          filterFrag,\n          'DIALECT',\n          '2',\n          'LIMIT',\n          String(plan.offset ?? 0),\n          String(plan.topK),\n          'NOCONTENT',\n        ])\n        return await this.#materialize(client, reply, plan, metric, false)\n      }\n    } catch (err) {\n      throw new E_VECTOR_STORE_SEARCH_FAILED([String(err)])\n    }\n  }\n\n  // Parse a node-redis v6 FT.SEARCH reply (structured object OR legacy flat array) into\n  // [{ key, score? }] pairs. Score is read from extra_attributes.__score on KNN searches.\n  #parseSearchReply(reply: any): Array<{ key: string; score?: number }> {\n    // node-redis v6 returns { total_results, results: [{ id, extra_attributes }] }\n    if (reply && typeof reply === 'object' && Array.isArray(reply.results)) {\n      return reply.results.map((r: any) => ({\n        key: String(r.id),\n        score:\n          r.extra_attributes && r.extra_attributes.__score !== undefined\n            ? Number(r.extra_attributes.__score)\n            : undefined,\n      }))\n    }\n    // Legacy flat array reply: [total, key, key, ...]\n    if (Array.isArray(reply)) {\n      return reply.filter((_: unknown, i: number) => i > 0).map((k: any) => ({ key: String(k) }))\n    }\n    return []\n  }\n\n  // Parse the FT.SEARCH reply for keys (+ KNN score), then re-fetch each hash as buffers to\n  // project. Re-fetching keeps the vector blob intact (RediSearch's per-field reply mangles it).\n  async #materialize(\n    client: any,\n    reply: any,\n    plan: SearchPlan,\n    metric: string,\n    isKnn: boolean\n  ): Promise<VectorMatch[]> {\n    const hits = this.#parseSearchReply(reply)\n    const out: VectorMatch[] = []\n    for (const hit of hits) {\n      const raw = await client.hGetAll(hit.key)\n      if (!raw) continue\n      out.push(this.#project(raw, plan, metric, isKnn, hit.score))\n    }\n    return out\n  }\n\n  #decode(buf: unknown): string | undefined {\n    if (buf === undefined || buf === null) return undefined\n    return Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf)\n  }\n\n  #project(\n    raw: Record<string, unknown>,\n    plan: SearchPlan,\n    metric: string,\n    isKnn: boolean,\n    rawScore?: number\n  ): VectorMatch {\n    const proj = plan.projection\n    const out: VectorMatch = {}\n    if (proj.id) out.id = this.#decode(raw.__id)\n    if (proj.vector && raw.__vecb64) {\n      out.vector = unpackVector(Buffer.from(String(this.#decode(raw.__vecb64)), 'base64'))\n    }\n    if (proj.document) out.document = this.#decode(raw.__document)\n    if (proj.metadata) {\n      const meta: VectorMetadata = {}\n      const reserved = new Set(['__vector', '__vecb64', '__id', '__document', '__score'])\n      for (const [k, v] of Object.entries(raw)) {\n        if (reserved.has(k)) continue\n        meta[k] = this.#decode(v) as string\n      }\n      out.metadata = meta\n    }\n    if (isKnn && rawScore !== undefined) {\n      // RediSearch returns the raw distance for the metric; normalize to [0,1] higher-is-better.\n      out.score = normalizeScore(rawScore, metric as DistanceMetric, 'distance')\n    }\n    return out\n  }\n\n  async executeDelete(plan: DeletePlan): Promise<void> {\n    const client = await this.#ensure()\n    try {\n      if (plan.ids && plan.ids.length > 0) {\n        await client.del(plan.ids.map((id) => keyFor(plan.collection, id)))\n      } else if (plan.filter) {\n        // Resolve ids via a filter-scan, then delete the hashes.\n        const filterFrag = translateRedisFilter(plan.filter)\n        const reply: any = await client.sendCommand([\n          'FT.SEARCH',\n          indexName(plan.collection),\n          filterFrag,\n          'DIALECT',\n          '2',\n          'LIMIT',\n          '0',\n          '10000',\n          'NOCONTENT',\n        ])\n        const keys = this.#parseSearchReply(reply).map((h) => h.key)\n        if (keys.length > 0) await client.del(keys)\n      } else {\n        // Delete-all: drop every hash under the prefix via the index.\n        const reply: any = await client.sendCommand([\n          'FT.SEARCH',\n          indexName(plan.collection),\n          '*',\n          'DIALECT',\n          '2',\n          'LIMIT',\n          '0',\n          '10000',\n          'NOCONTENT',\n        ])\n        const keys = this.#parseSearchReply(reply).map((h) => h.key)\n        if (keys.length > 0) await client.del(keys)\n      }\n    } catch (err) {\n      throw new E_VECTOR_STORE_DELETE_FAILED([String(err)])\n    }\n  }\n}\n"],"mappings":"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"}