@inferagraph/redis
Advanced tools
+3
-3
@@ -39,3 +39,3 @@ "use strict"; | ||
| // src/redisCacheProvider.ts | ||
| var import_core = require("@inferagraph/core"); | ||
| var import_data = require("@inferagraph/core/data"); | ||
| var import_redis = require("redis"); | ||
@@ -61,6 +61,6 @@ var DEFAULT_PREFIX = "infera:cache:"; | ||
| this.maxEntries = 500; | ||
| this.defaultTtlMs = (0, import_core.parseTTL)("24h"); | ||
| this.defaultTtlMs = (0, import_data.parseTTL)("24h"); | ||
| } else { | ||
| this.maxEntries = hasMaxEntries ? normalizeMaxEntries(config.maxEntries) : -1; | ||
| this.defaultTtlMs = hasTtl ? (0, import_core.parseTTL)(config.ttl) : -1; | ||
| this.defaultTtlMs = hasTtl ? (0, import_data.parseTTL)(config.ttl) : -1; | ||
| } | ||
@@ -67,0 +67,0 @@ this.client = config.client ?? (0, import_redis.createClient)({ url: config.url }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/redisCacheProvider.ts","../src/redisConversationStore.ts","../src/redisVectorEmbeddingStore.ts","../src/redisInferredEdgeStore.ts","../src/provisionRedisVectorIndex.ts"],"sourcesContent":["export type {\n RedisLikeClient,\n RedisCacheConfig,\n RedisFtCommands,\n RedisFtSearchReply,\n RedisHashCommands,\n} from './types.js';\n\nexport { RedisCacheProvider, redisCacheProvider, redisCache } from './redisCacheProvider.js';\n\nexport {\n RedisConversationStore,\n redisConversationStore,\n} from './redisConversationStore.js';\nexport type {\n RedisConversationLikeClient,\n RedisConversationStoreConfig,\n RedisConversationTurn,\n} from './redisConversationStore.js';\n\nexport {\n RedisVectorEmbeddingStore,\n redisVectorEmbeddingStore,\n vectorToBytes,\n bytesToVector,\n} from './redisVectorEmbeddingStore.js';\nexport type { RedisVectorEmbeddingStoreConfig } from './redisVectorEmbeddingStore.js';\n\nexport {\n RedisInferredEdgeStore,\n redisInferredEdgeStore,\n} from './redisInferredEdgeStore.js';\nexport type { RedisInferredEdgeStoreConfig } from './redisInferredEdgeStore.js';\n\nexport { provisionRedisVectorIndex } from './provisionRedisVectorIndex.js';\nexport type { ProvisionRedisVectorIndexConfig } from './provisionRedisVectorIndex.js';\n","import type { CacheProvider } from '@inferagraph/core';\nimport { parseTTL } from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisCacheConfig, RedisLikeClient } from './types.js';\n\nconst DEFAULT_PREFIX = 'infera:cache:';\n\n/**\n * Redis-backed `CacheProvider` (core 0.9.0+ wider shape).\n *\n * Honors the same defaults as the in-memory `lruCache`:\n * - Both `maxEntries` and `ttl` unset -> `(500, '24h')`.\n * - Only one set -> unset bound treated as no-limit.\n * - Both set -> both bounds enforced.\n * - `-1` / `'-1'` disables the corresponding bound.\n *\n * The class is exposed for direct use; most consumers should prefer the\n * {@link redisCacheProvider} factory which constructs the underlying redis\n * client when a `url` is supplied.\n */\nexport class RedisCacheProvider implements CacheProvider {\n private readonly prefix: string;\n private readonly indexKey: string;\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly client: RedisLikeClient;\n private readonly maskedUrl: string;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisCacheConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisCacheProvider: either `url` or `client` must be provided');\n }\n\n this.prefix = config.prefix ?? DEFAULT_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n\n const hasMaxEntries = config.maxEntries !== undefined;\n const hasTtl = config.ttl !== undefined;\n\n if (!hasMaxEntries && !hasTtl) {\n this.maxEntries = 500;\n this.defaultTtlMs = parseTTL('24h');\n } else {\n this.maxEntries = hasMaxEntries ? normalizeMaxEntries(config.maxEntries!) : -1;\n this.defaultTtlMs = hasTtl ? parseTTL(config.ttl!) : -1;\n }\n\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.maskedUrl = config.url ? maskUrl(config.url) : '<pre-built client>';\n }\n\n async get(key: string): Promise<string | undefined> {\n await this.ensureConnected();\n const v = await this.client.get(this.dataKey(key));\n return v === null ? undefined : v;\n }\n\n async set(\n key: string,\n value: string,\n opts?: { ttlSeconds?: number },\n ): Promise<void> {\n await this.ensureConnected();\n const k = this.dataKey(key);\n\n const setOptions = this.resolveSetTtlOptions(opts);\n\n if (setOptions) {\n await this.client.set(k, value, setOptions);\n } else {\n await this.client.set(k, value);\n }\n\n if (this.maxEntries !== -1) {\n // Track insertion order in a ZSET; evict oldest when over capacity.\n await this.client.zAdd(this.indexKey, { score: Date.now(), value: key });\n const size = await this.client.zCard(this.indexKey);\n if (size > this.maxEntries) {\n const evictCount = size - this.maxEntries;\n const oldest = await this.client.zRange(this.indexKey, 0, evictCount - 1);\n if (oldest.length > 0) {\n await this.client.zRem(this.indexKey, oldest);\n await this.client.del(oldest.map((cacheKey) => this.dataKey(cacheKey)));\n }\n }\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del(this.dataKey(key));\n if (this.maxEntries !== -1) {\n await this.client.zRem(this.indexKey, key);\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const toDelete: string[] = [];\n // SCAN with MATCH is safe on large datasets; KEYS would block the server.\n // Deliberately scoped to `${prefix}*` so we never touch keys outside our\n // namespace (NOT FLUSHDB).\n for await (const k of this.client.scanIterator({ MATCH: `${this.prefix}*`, COUNT: 100 })) {\n toDelete.push(k);\n }\n if (toDelete.length > 0) {\n await this.client.del(toDelete);\n }\n }\n\n private dataKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n /**\n * Decide which TTL (per-call vs construction default) applies to a SET.\n * Per-call wins. Returns `undefined` when no TTL applies (no EX/PX).\n */\n private resolveSetTtlOptions(\n opts: { ttlSeconds?: number } | undefined,\n ): { EX: number } | { PX: number } | undefined {\n if (opts?.ttlSeconds !== undefined) {\n // Per-call TTL is always seconds-precision per the contract.\n if (opts.ttlSeconds <= 0) return undefined;\n return { EX: Math.floor(opts.ttlSeconds) };\n }\n if (this.defaultTtlMs === -1) return undefined;\n return this.defaultTtlMs % 1000 === 0\n ? { EX: Math.floor(this.defaultTtlMs / 1000) }\n : { PX: this.defaultTtlMs };\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n // Reset so a future operation can retry. Re-throw so the caller sees it.\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisCacheProvider] failed to connect to ${this.maskedUrl}: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisCacheProvider}. Accepts either a pre-built `client`\n * or a `url`; when only `url` is provided, the factory builds the underlying\n * `redis` client internally so consumers don't import the SDK.\n */\nexport function redisCacheProvider(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\n/**\n * @deprecated Use {@link redisCacheProvider} (or `RedisCacheProvider`)\n * directly. Kept temporarily as an alias to ease migration off the old\n * package name `@inferagraph/redis-cache-provider`.\n */\nexport function redisCache(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\nfunction normalizeMaxEntries(value: number): number {\n if (value === -1) return -1;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new Error(\n `RedisCacheProvider: invalid maxEntries ${value}; expected a non-negative integer or -1`,\n );\n }\n return value;\n}\n\nfunction maskUrl(url: string): string {\n // Mask password component (between `:` and `@`) in a connection URL.\n // E.g. redis://default:hunter2@host:6379 -> redis://default:***@host:6379\n return url.replace(/(:\\/\\/[^:]+:)[^@]+(@)/, '$1***$2');\n}\n","import type { ConversationStore, ConversationTurn } from '@inferagraph/core';\nimport { createClient } from 'redis';\n\n/**\n * Subset of node-redis v4 the conversation store needs (LIST + EXPIRE + DEL).\n * Re-exported for tests so a stub can target only the conversation surface.\n */\nexport interface RedisConversationLikeClient {\n isOpen?: boolean;\n connect(): Promise<unknown>;\n lPush(key: string, element: string | string[]): Promise<number>;\n lTrim(key: string, start: number, stop: number): Promise<string>;\n lRange(key: string, start: number, stop: number): Promise<string[]>;\n expire(key: string, seconds: number): Promise<number>;\n del(keys: string | string[]): Promise<number>;\n}\n\nexport interface RedisConversationStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisConversationLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /** Key prefix for conversation lists. Default `inferagraph:conversation`. */\n keyPrefix?: string;\n /** TTL refreshed on every appendTurn. Default `86400` (24h). */\n ttlSeconds?: number;\n}\n\nconst DEFAULT_CONVERSATION_PREFIX = 'inferagraph:conversation';\nconst DEFAULT_CONVERSATION_TTL_SECONDS = 86_400;\n/** Hard cap on stored turns per conversation. LTRIM 0 (MAX-1). */\nconst MAX_TURNS_PER_CONVERSATION = 1000;\nconst ALLOWED_ROLES = new Set(['user', 'assistant']);\n\n/**\n * Redis-backed `ConversationStore` for `@inferagraph/core@^0.9.0`'s `AIEngine`.\n *\n * Storage layout: one Redis LIST per conversation, keyed by\n * `<keyPrefix>:<conversationId>`. Each element is a JSON-serialized turn.\n * Newest turns sit at the head (LPUSH); reads pull the most-recent N via\n * LRANGE 0 N-1 and reverse so callers see oldest -> newest, matching LLM\n * conversation order. Each `appendTurn` refreshes a TTL via EXPIRE so\n * inactive conversations age out without manual cleanup.\n *\n * Defensive: malformed entries (corrupt JSON, wrong shape) are skipped with\n * a `console.warn` rather than thrown — one bad write must not poison the\n * entire conversation history.\n */\nexport class RedisConversationStore implements ConversationStore {\n private readonly client: RedisConversationLikeClient;\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisConversationStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisConversationStore: either `url` or `client` must be provided');\n }\n this.client =\n config.client ??\n (createClient({ url: config.url! }) as unknown as RedisConversationLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_CONVERSATION_PREFIX;\n this.ttlSeconds = config.ttlSeconds ?? DEFAULT_CONVERSATION_TTL_SECONDS;\n }\n\n async appendTurn(conversationId: string, turn: ConversationTurn): Promise<void> {\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const payload = JSON.stringify(turn);\n await this.client.lPush(key, payload);\n await this.client.lTrim(key, 0, MAX_TURNS_PER_CONVERSATION - 1);\n await this.client.expire(key, this.ttlSeconds);\n }\n\n async getTurns(conversationId: string, limit: number): Promise<ConversationTurn[]> {\n if (limit <= 0) return [];\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const raw = await this.client.lRange(key, 0, limit - 1);\n if (raw.length === 0) return [];\n const parsed: ConversationTurn[] = [];\n for (const entry of raw) {\n const turn = safeParseTurn(entry);\n if (turn) parsed.push(turn);\n }\n // LPUSH puts newest at head. Reverse so callers see oldest -> newest.\n parsed.reverse();\n return parsed;\n }\n\n async clear(conversationId: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del([this.dataKey(conversationId)]);\n }\n\n private dataKey(conversationId: string): string {\n return `${this.keyPrefix}:${conversationId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisConversationStore}. Accepts a pre-built client OR\n * a `url`; when only `url` is provided, the factory constructs the underlying\n * redis client internally so consumers never import the SDK.\n */\nexport function redisConversationStore(\n config: RedisConversationStoreConfig,\n): ConversationStore {\n return new RedisConversationStore(config);\n}\n\nfunction safeParseTurn(raw: string): ConversationTurn | undefined {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] dropping malformed JSON turn: ${(err as Error).message}`,\n );\n return undefined;\n }\n if (!isConversationTurn(value)) {\n // eslint-disable-next-line no-console\n console.warn('[RedisConversationStore] dropping turn with unexpected shape');\n return undefined;\n }\n return value;\n}\n\nfunction isConversationTurn(value: unknown): value is ConversationTurn {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.role !== 'string' || !ALLOWED_ROLES.has(v.role)) return false;\n if (typeof v.content !== 'string') return false;\n if (typeof v.timestamp !== 'number' || !Number.isFinite(v.timestamp)) return false;\n if (v.retrievedNodeIds !== undefined) {\n if (!Array.isArray(v.retrievedNodeIds)) return false;\n if (!v.retrievedNodeIds.every((id) => typeof id === 'string')) return false;\n }\n return true;\n}\n\n// Keep the public type re-exports for the legacy import site (RedisConversationTurn).\n/**\n * @deprecated Re-export of the core `ConversationTurn` type. Use\n * `import type { ConversationTurn } from '@inferagraph/core';` instead.\n */\nexport type RedisConversationTurn = ConversationTurn;\n","import type {\n EmbeddingRecord,\n EmbeddingStore,\n NodeId,\n SearchVectorHit,\n SimilarHit,\n Vector,\n} from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link RedisVectorEmbeddingStore}. Targets RediSearch\n * (Redis Stack); the store is provider-agnostic — vector dimensionality and\n * key naming are constructor options with neutral defaults so hosts can swap\n * embedding providers (OpenAI, Voyage, etc.) without forking this package.\n */\nexport interface RedisVectorEmbeddingStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /**\n * Logical key namespace. Embedding hashes are stored at\n * `<keyPrefix>:embedding:<nodeId>`; the index name defaults to\n * `<keyPrefix>:embeddings:idx`. Defaults to `'inferagraph'`.\n */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:embeddings:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072` (matches `text-embedding-3-large`). */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link EmbeddingStore} backed by Redis Stack with a RediSearch\n * vector index over hash-stored embeddings.\n *\n * Storage layout:\n * - `<keyPrefix>:embedding:<nodeId>` — HASH containing\n * `nodeId`, `embedding` (binary Float32Array), `embeddingHash`,\n * `embeddingModel`, `embeddingVersion`, `embeddingGeneratedAt`.\n * - `<keyPrefix>:embeddings:idx` — RediSearch index over the hashes.\n *\n * The store also satisfies the optional `searchVector(queryVec, {top, container?})`\n * surface on `EmbeddingStore` so the engine's hybrid retrieval can call it\n * without a tier check.\n */\nexport class RedisVectorEmbeddingStore implements EmbeddingStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} (single source of truth) — the read +\n * write paths don't need to know the dimension explicitly because\n * {@link vectorToBytes} packs whatever length the caller passes.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisVectorEmbeddingStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisVectorEmbeddingStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:embeddings:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n /** Lookup keyed by `(nodeId, model, modelVersion, contentHash)`. */\n async get(\n nodeId: NodeId,\n model: string,\n modelVersion: string,\n contentHash: string,\n ): Promise<EmbeddingRecord | undefined> {\n await this.ensureConnected();\n if (!this.client.hGetAll) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hGetAll');\n }\n const key = this.embeddingKey(nodeId);\n const hash = await this.client.hGetAll(key);\n if (!hash || Object.keys(hash).length === 0) return undefined;\n\n const storedModel = bufferToString(hash.embeddingModel);\n const storedVersion = bufferToString(hash.embeddingVersion);\n const storedHash = bufferToString(hash.embeddingHash);\n if (storedModel !== model) return undefined;\n if (storedVersion !== modelVersion) return undefined;\n if (storedHash !== contentHash) return undefined;\n\n const vectorBytes = hash.embedding;\n if (vectorBytes === undefined) return undefined;\n return {\n nodeId,\n vector: bytesToVector(vectorBytes),\n meta: {\n model: storedModel,\n modelVersion: storedVersion,\n contentHash: storedHash,\n generatedAt: bufferToString(hash.embeddingGeneratedAt) ?? '',\n },\n };\n }\n\n /**\n * Persist an embedding as a HASH; the RediSearch index automatically picks\n * it up because the index is configured with `PREFIX 1 <keyPrefix>:embedding:`.\n */\n async set(record: EmbeddingRecord): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hSet');\n }\n const key = this.embeddingKey(record.nodeId);\n await this.client.hSet(key, {\n nodeId: record.nodeId,\n embedding: vectorToBytes(record.vector),\n embeddingModel: record.meta.model,\n embeddingVersion: record.meta.modelVersion,\n embeddingHash: record.meta.contentHash,\n embeddingGeneratedAt: record.meta.generatedAt,\n });\n }\n\n /**\n * Linear-scan-style similarity is satisfied by delegating to\n * {@link searchVector}, which uses the vector index. Model + version scope\n * filters are intentionally honored at write time (each entry stores its\n * own metadata), so this method ignores them.\n */\n async similar(\n queryVector: Vector,\n k: number,\n _model?: string,\n _modelVersion?: string,\n ): Promise<SimilarHit[]> {\n void _model;\n void _modelVersion;\n const hits = await this.searchVector(queryVector, { top: k });\n return hits.map((h) => ({ nodeId: h.nodeId, score: h.score }));\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:embedding:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n /**\n * Vector-native top-K via RediSearch KNN.\n *\n * Issues `FT.SEARCH <idx> \"*=>[KNN $top @embedding $vec AS score]\"\n * PARAMS 4 vec <bytes> top <K> SORTBY score ASC RETURN 1 nodeId DIALECT 2`.\n * RediSearch returns the COSINE distance (lower = more similar); we convert\n * to similarity (`1 - distance`) so the contract's \"higher = more similar\"\n * holds.\n */\n async searchVector(\n queryEmbedding: Vector,\n opts: { top: number; container?: 'units' | 'inferred_edges' },\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisVectorEmbeddingStore: client does not support ft.search');\n }\n // The default index covers units; inferred-edges callers should use the\n // dedicated InferredEdgeStore. Honor the `container` option only as a\n // forward-compatibility hint — when set to 'inferred_edges' the caller\n // would normally route to RedisInferredEdgeStore.searchInferredEdges.\n void opts.container;\n\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top: opts.top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['nodeId', 'score'],\n DIALECT: 2,\n },\n );\n\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n return {\n nodeId: bufferToString(value.nodeId) ?? doc.id,\n score: similarity,\n };\n });\n }\n\n private embeddingKey(nodeId: NodeId): string {\n return `${this.keyPrefix}:embedding:${nodeId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisVectorEmbeddingStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisVectorEmbeddingStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the underlying redis\n * client internally so consumers don't import the SDK directly.\n */\nexport function redisVectorEmbeddingStore(\n config: RedisVectorEmbeddingStoreConfig,\n): EmbeddingStore {\n return new RedisVectorEmbeddingStore(config);\n}\n\n/**\n * Convert a `Vector` to a Float32Array packed into a Node `Buffer` — the\n * binary form RediSearch expects for a `VECTOR FLOAT32` field.\n */\nexport function vectorToBytes(vector: Vector): Buffer {\n const arr = new Float32Array(vector.length);\n for (let i = 0; i < vector.length; i++) arr[i] = vector[i];\n return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/**\n * Decode the binary `embedding` field of a RediSearch hash back into a\n * plain `Vector` (`number[]`). Accepts either a `Buffer` (real client) or a\n * `string` (some serializers; treated as latin1 bytes).\n */\nexport function bytesToVector(value: string | Buffer): Vector {\n const buf = typeof value === 'string' ? Buffer.from(value, 'binary') : value;\n const view = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n Math.floor(buf.byteLength / 4),\n );\n const out: number[] = new Array(view.length);\n for (let i = 0; i < view.length; i++) out[i] = view[i];\n return out;\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import type {\n InferredEdge,\n InferredEdgeSource,\n InferredEdgeStore,\n NodeId,\n SearchVectorHit,\n Vector,\n} from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\nimport { vectorToBytes } from './redisVectorEmbeddingStore.js';\n\n/**\n * Configuration for {@link RedisInferredEdgeStore}. Provider-agnostic — the\n * vector dimensionality and key naming are constructor options with neutral\n * defaults.\n */\nexport interface RedisInferredEdgeStoreConfig {\n /** Pre-built client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. Factory builds the client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:inferred_edges:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link InferredEdgeStore} backed by a separate RediSearch index\n * over edge HASHes.\n *\n * Storage layout:\n * - `<keyPrefix>:inferred_edge:<sourceId>:<targetId>:<type>` — HASH with\n * `id`, `sourceId`, `targetId`, `type`, `score`, `sources`, `reasoning`,\n * `embedding` (optional, Float32 binary).\n * - `<keyPrefix>:inferred_edges:idx` — RediSearch index with TAG indexes on\n * `sourceId` and `targetId` plus a HNSW vector index on `embedding`.\n */\nexport class RedisInferredEdgeStore implements InferredEdgeStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} — read + write paths use whatever\n * length the caller passes via {@link vectorToBytes}.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisInferredEdgeStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisInferredEdgeStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:inferred_edges:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n async get(sourceId: NodeId, targetId: NodeId): Promise<InferredEdge | undefined> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `@sourceId:{${escapeTag(sourceId)}} @targetId:{${escapeTag(targetId)}}`,\n { LIMIT: { from: 0, size: 1 }, DIALECT: 2 },\n );\n if (!reply || (reply.total ?? 0) === 0 || reply.documents.length === 0) {\n return undefined;\n }\n return docToEdge(reply.documents[0].value);\n }\n\n async getAllForNode(nodeId: NodeId): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const tag = escapeTag(nodeId);\n const reply = await this.client.ft.search(\n this.indexName,\n `(@sourceId:{${tag}}) | (@targetId:{${tag}})`,\n { LIMIT: { from: 0, size: 10_000 }, DIALECT: 2 },\n );\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n async getAll(): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(this.indexName, '*', {\n LIMIT: { from: 0, size: 10_000 },\n DIALECT: 2,\n });\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n /**\n * Bulk-replace the entire stored set. SCAN existing edge keys, DEL them in\n * one batch, then HSET the new entries. Idempotent on `(sourceId,\n * targetId, type)` collisions because the key encodes those three values\n * (last write wins, per the contract).\n */\n async set(edges: ReadonlyArray<InferredEdge>): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisInferredEdgeStore: client does not support hSet');\n }\n await this.deleteAllEdgeKeys();\n for (const edge of edges) {\n const key = this.edgeKey(edge.sourceId, edge.targetId, edge.type);\n await this.client.hSet(key, edgeToHashFields(edge));\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n await this.deleteAllEdgeKeys();\n }\n\n /**\n * Vector-native top-K against the inferred-edges index. Uses the same\n * `KNN` syntax as {@link RedisVectorEmbeddingStore.searchVector}; converts\n * the returned distance to similarity (`1 - distance`) so the contract's\n * \"higher = more similar\" holds.\n */\n async searchInferredEdges(\n queryEmbedding: Vector,\n top: number,\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['id', 'score'],\n DIALECT: 2,\n },\n );\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n const id = bufferToString(value.id) ?? doc.id;\n return { nodeId: id, score: similarity };\n });\n }\n\n private edgeKey(sourceId: NodeId, targetId: NodeId, type: string): string {\n return `${this.keyPrefix}:inferred_edge:${sourceId}:${targetId}:${type}`;\n }\n\n private async deleteAllEdgeKeys(): Promise<void> {\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:inferred_edge:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisInferredEdgeStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisInferredEdgeStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the redis client\n * internally.\n */\nexport function redisInferredEdgeStore(\n config: RedisInferredEdgeStoreConfig,\n): InferredEdgeStore {\n return new RedisInferredEdgeStore(config);\n}\n\nfunction edgeToHashFields(edge: InferredEdge): Record<string, string | number | Buffer> {\n const fields: Record<string, string | number | Buffer> = {\n id: edgeId(edge),\n sourceId: edge.sourceId,\n targetId: edge.targetId,\n type: edge.type,\n score: edge.score,\n sources: edge.sources.join(','),\n };\n if (edge.reasoning !== undefined) fields.reasoning = edge.reasoning;\n if (edge.perSource !== undefined) {\n fields.perSource = JSON.stringify(edge.perSource);\n }\n return fields;\n}\n\nfunction edgeId(edge: InferredEdge): string {\n return `${edge.sourceId}-${edge.targetId}-${edge.type}`;\n}\n\nfunction docToEdge(value: Record<string, unknown>): InferredEdge {\n const sources = parseSources(bufferToString(value.sources));\n const perSourceRaw = bufferToString(value.perSource);\n let perSource: InferredEdge['perSource'];\n if (perSourceRaw) {\n try {\n perSource = JSON.parse(perSourceRaw) as InferredEdge['perSource'];\n } catch {\n perSource = undefined;\n }\n }\n const reasoning = bufferToString(value.reasoning);\n return {\n sourceId: bufferToString(value.sourceId) ?? '',\n targetId: bufferToString(value.targetId) ?? '',\n type: bufferToString(value.type) ?? '',\n score: parseFloat(String(value.score ?? '0')),\n sources,\n reasoning,\n perSource,\n };\n}\n\nfunction parseSources(raw: string | undefined): InferredEdgeSource[] {\n if (!raw) return [];\n return raw\n .split(',')\n .filter((s) => s.length > 0)\n .filter(\n (s): s is InferredEdgeSource => s === 'graph' || s === 'embedding' || s === 'llm',\n );\n}\n\nfunction escapeTag(value: string): string {\n // RediSearch TAG values escape `,` `.` `<` `>` `{` `}` `[` `]` `\\\"` `'` `:` `;` `!` `@` `#` `$` `%` `^` `&` `*` `(` `)` `-` `+` `=` `~` `|` `\\` and whitespace.\n return value.replace(/([,.<>{}[\\]\"':;!@#$%^&*()\\-+=~|\\\\\\s])/g, '\\\\$1');\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link provisionRedisVectorIndex}. Provider-agnostic:\n * dimensions and key naming are constructor options. The default is to\n * provision BOTH the units (embeddings) index and the inferred-edges index;\n * pass `alsoProvisionInferredEdges: false` to skip the latter.\n */\nexport interface ProvisionRedisVectorIndexConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The function builds a client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n /**\n * Whether to also provision the separate inferred-edges index. Defaults to\n * `true` so most consumers get both indexes from a single call.\n */\n alsoProvisionInferredEdges?: boolean;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Idempotent setup for the RediSearch vector indexes used by\n * {@link RedisVectorEmbeddingStore} and {@link RedisInferredEdgeStore}.\n *\n * Creates `<keyPrefix>:embeddings:idx` and (by default)\n * `<keyPrefix>:inferred_edges:idx`. If an index already exists, the function\n * catches the \"index already exists\" error and treats it as a no-op so this\n * call is safe to run on every deploy.\n *\n * Hosts call this once at deploy time before wiring the stores.\n */\nexport async function provisionRedisVectorIndex(\n config: ProvisionRedisVectorIndexConfig,\n): Promise<void> {\n if (!config.client && !config.url) {\n throw new Error(\n 'provisionRedisVectorIndex: either `url` or `client` must be provided',\n );\n }\n const client: RedisLikeClient =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n const keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n const dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n const alsoEdges = config.alsoProvisionInferredEdges ?? true;\n\n if (!client.isOpen) {\n await client.connect();\n }\n if (!client.ft) {\n throw new Error('provisionRedisVectorIndex: client does not support ft.create');\n }\n\n await safeCreateIndex(client, `${keyPrefix}:embeddings:idx`, {\n nodeId: { type: 'TAG' },\n embeddingHash: { type: 'TAG' },\n embeddingModel: { type: 'TAG' },\n embeddingVersion: { type: 'TAG' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:embedding:` },\n });\n\n if (alsoEdges) {\n await safeCreateIndex(client, `${keyPrefix}:inferred_edges:idx`, {\n id: { type: 'TAG' },\n sourceId: { type: 'TAG' },\n targetId: { type: 'TAG' },\n type: { type: 'TAG' },\n score: { type: 'NUMERIC' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:inferred_edge:` },\n });\n }\n}\n\nasync function safeCreateIndex(\n client: RedisLikeClient,\n indexName: string,\n schema: Record<string, unknown>,\n options: Record<string, unknown>,\n): Promise<void> {\n try {\n await client.ft!.create(indexName, schema, options);\n } catch (err) {\n if (isIndexAlreadyExistsError(err)) return;\n throw err;\n }\n}\n\nfunction isIndexAlreadyExistsError(err: unknown): boolean {\n if (!err) return false;\n const msg =\n err instanceof Error\n ? err.message\n : typeof err === 'string'\n ? err\n : String((err as { message?: unknown })?.message ?? err);\n // RediSearch returns \"Index already exists\" (case varies by version).\n return /index\\s+already\\s+exists/i.test(msg);\n}\n"],"mappings":"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","names":["import_redis","import_redis","import_redis","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS","bufferToString","import_redis","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS"]} | ||
| {"version":3,"sources":["../src/index.ts","../src/redisCacheProvider.ts","../src/redisConversationStore.ts","../src/redisVectorEmbeddingStore.ts","../src/redisInferredEdgeStore.ts","../src/provisionRedisVectorIndex.ts"],"sourcesContent":["export type {\n RedisLikeClient,\n RedisCacheConfig,\n RedisFtCommands,\n RedisFtSearchReply,\n RedisHashCommands,\n} from './types.js';\n\nexport { RedisCacheProvider, redisCacheProvider, redisCache } from './redisCacheProvider.js';\n\nexport {\n RedisConversationStore,\n redisConversationStore,\n} from './redisConversationStore.js';\nexport type {\n RedisConversationLikeClient,\n RedisConversationStoreConfig,\n RedisConversationTurn,\n} from './redisConversationStore.js';\n\nexport {\n RedisVectorEmbeddingStore,\n redisVectorEmbeddingStore,\n vectorToBytes,\n bytesToVector,\n} from './redisVectorEmbeddingStore.js';\nexport type { RedisVectorEmbeddingStoreConfig } from './redisVectorEmbeddingStore.js';\n\nexport {\n RedisInferredEdgeStore,\n redisInferredEdgeStore,\n} from './redisInferredEdgeStore.js';\nexport type { RedisInferredEdgeStoreConfig } from './redisInferredEdgeStore.js';\n\nexport { provisionRedisVectorIndex } from './provisionRedisVectorIndex.js';\nexport type { ProvisionRedisVectorIndexConfig } from './provisionRedisVectorIndex.js';\n","import type { CacheProvider } from '@inferagraph/core/data';\nimport { parseTTL } from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisCacheConfig, RedisLikeClient } from './types.js';\n\nconst DEFAULT_PREFIX = 'infera:cache:';\n\n/**\n * Redis-backed `CacheProvider` (core 0.9.0+ wider shape).\n *\n * Honors the same defaults as the in-memory `lruCache`:\n * - Both `maxEntries` and `ttl` unset -> `(500, '24h')`.\n * - Only one set -> unset bound treated as no-limit.\n * - Both set -> both bounds enforced.\n * - `-1` / `'-1'` disables the corresponding bound.\n *\n * The class is exposed for direct use; most consumers should prefer the\n * {@link redisCacheProvider} factory which constructs the underlying redis\n * client when a `url` is supplied.\n */\nexport class RedisCacheProvider implements CacheProvider {\n private readonly prefix: string;\n private readonly indexKey: string;\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly client: RedisLikeClient;\n private readonly maskedUrl: string;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisCacheConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisCacheProvider: either `url` or `client` must be provided');\n }\n\n this.prefix = config.prefix ?? DEFAULT_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n\n const hasMaxEntries = config.maxEntries !== undefined;\n const hasTtl = config.ttl !== undefined;\n\n if (!hasMaxEntries && !hasTtl) {\n this.maxEntries = 500;\n this.defaultTtlMs = parseTTL('24h');\n } else {\n this.maxEntries = hasMaxEntries ? normalizeMaxEntries(config.maxEntries!) : -1;\n this.defaultTtlMs = hasTtl ? parseTTL(config.ttl!) : -1;\n }\n\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.maskedUrl = config.url ? maskUrl(config.url) : '<pre-built client>';\n }\n\n async get(key: string): Promise<string | undefined> {\n await this.ensureConnected();\n const v = await this.client.get(this.dataKey(key));\n return v === null ? undefined : v;\n }\n\n async set(\n key: string,\n value: string,\n opts?: { ttlSeconds?: number },\n ): Promise<void> {\n await this.ensureConnected();\n const k = this.dataKey(key);\n\n const setOptions = this.resolveSetTtlOptions(opts);\n\n if (setOptions) {\n await this.client.set(k, value, setOptions);\n } else {\n await this.client.set(k, value);\n }\n\n if (this.maxEntries !== -1) {\n // Track insertion order in a ZSET; evict oldest when over capacity.\n await this.client.zAdd(this.indexKey, { score: Date.now(), value: key });\n const size = await this.client.zCard(this.indexKey);\n if (size > this.maxEntries) {\n const evictCount = size - this.maxEntries;\n const oldest = await this.client.zRange(this.indexKey, 0, evictCount - 1);\n if (oldest.length > 0) {\n await this.client.zRem(this.indexKey, oldest);\n await this.client.del(oldest.map((cacheKey) => this.dataKey(cacheKey)));\n }\n }\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del(this.dataKey(key));\n if (this.maxEntries !== -1) {\n await this.client.zRem(this.indexKey, key);\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const toDelete: string[] = [];\n // SCAN with MATCH is safe on large datasets; KEYS would block the server.\n // Deliberately scoped to `${prefix}*` so we never touch keys outside our\n // namespace (NOT FLUSHDB).\n for await (const k of this.client.scanIterator({ MATCH: `${this.prefix}*`, COUNT: 100 })) {\n toDelete.push(k);\n }\n if (toDelete.length > 0) {\n await this.client.del(toDelete);\n }\n }\n\n private dataKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n /**\n * Decide which TTL (per-call vs construction default) applies to a SET.\n * Per-call wins. Returns `undefined` when no TTL applies (no EX/PX).\n */\n private resolveSetTtlOptions(\n opts: { ttlSeconds?: number } | undefined,\n ): { EX: number } | { PX: number } | undefined {\n if (opts?.ttlSeconds !== undefined) {\n // Per-call TTL is always seconds-precision per the contract.\n if (opts.ttlSeconds <= 0) return undefined;\n return { EX: Math.floor(opts.ttlSeconds) };\n }\n if (this.defaultTtlMs === -1) return undefined;\n return this.defaultTtlMs % 1000 === 0\n ? { EX: Math.floor(this.defaultTtlMs / 1000) }\n : { PX: this.defaultTtlMs };\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n // Reset so a future operation can retry. Re-throw so the caller sees it.\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisCacheProvider] failed to connect to ${this.maskedUrl}: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisCacheProvider}. Accepts either a pre-built `client`\n * or a `url`; when only `url` is provided, the factory builds the underlying\n * `redis` client internally so consumers don't import the SDK.\n */\nexport function redisCacheProvider(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\n/**\n * @deprecated Use {@link redisCacheProvider} (or `RedisCacheProvider`)\n * directly. Kept temporarily as an alias to ease migration off the old\n * package name `@inferagraph/redis-cache-provider`.\n */\nexport function redisCache(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\nfunction normalizeMaxEntries(value: number): number {\n if (value === -1) return -1;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new Error(\n `RedisCacheProvider: invalid maxEntries ${value}; expected a non-negative integer or -1`,\n );\n }\n return value;\n}\n\nfunction maskUrl(url: string): string {\n // Mask password component (between `:` and `@`) in a connection URL.\n // E.g. redis://default:hunter2@host:6379 -> redis://default:***@host:6379\n return url.replace(/(:\\/\\/[^:]+:)[^@]+(@)/, '$1***$2');\n}\n","import type { ConversationStore, ConversationTurn } from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\n/**\n * Subset of node-redis v4 the conversation store needs (LIST + EXPIRE + DEL).\n * Re-exported for tests so a stub can target only the conversation surface.\n */\nexport interface RedisConversationLikeClient {\n isOpen?: boolean;\n connect(): Promise<unknown>;\n lPush(key: string, element: string | string[]): Promise<number>;\n lTrim(key: string, start: number, stop: number): Promise<string>;\n lRange(key: string, start: number, stop: number): Promise<string[]>;\n expire(key: string, seconds: number): Promise<number>;\n del(keys: string | string[]): Promise<number>;\n}\n\nexport interface RedisConversationStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisConversationLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /** Key prefix for conversation lists. Default `inferagraph:conversation`. */\n keyPrefix?: string;\n /** TTL refreshed on every appendTurn. Default `86400` (24h). */\n ttlSeconds?: number;\n}\n\nconst DEFAULT_CONVERSATION_PREFIX = 'inferagraph:conversation';\nconst DEFAULT_CONVERSATION_TTL_SECONDS = 86_400;\n/** Hard cap on stored turns per conversation. LTRIM 0 (MAX-1). */\nconst MAX_TURNS_PER_CONVERSATION = 1000;\nconst ALLOWED_ROLES = new Set(['user', 'assistant']);\n\n/**\n * Redis-backed `ConversationStore` for `@inferagraph/core@^0.9.0`'s `AIEngine`.\n *\n * Storage layout: one Redis LIST per conversation, keyed by\n * `<keyPrefix>:<conversationId>`. Each element is a JSON-serialized turn.\n * Newest turns sit at the head (LPUSH); reads pull the most-recent N via\n * LRANGE 0 N-1 and reverse so callers see oldest -> newest, matching LLM\n * conversation order. Each `appendTurn` refreshes a TTL via EXPIRE so\n * inactive conversations age out without manual cleanup.\n *\n * Defensive: malformed entries (corrupt JSON, wrong shape) are skipped with\n * a `console.warn` rather than thrown — one bad write must not poison the\n * entire conversation history.\n */\nexport class RedisConversationStore implements ConversationStore {\n private readonly client: RedisConversationLikeClient;\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisConversationStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisConversationStore: either `url` or `client` must be provided');\n }\n this.client =\n config.client ??\n (createClient({ url: config.url! }) as unknown as RedisConversationLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_CONVERSATION_PREFIX;\n this.ttlSeconds = config.ttlSeconds ?? DEFAULT_CONVERSATION_TTL_SECONDS;\n }\n\n async appendTurn(conversationId: string, turn: ConversationTurn): Promise<void> {\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const payload = JSON.stringify(turn);\n await this.client.lPush(key, payload);\n await this.client.lTrim(key, 0, MAX_TURNS_PER_CONVERSATION - 1);\n await this.client.expire(key, this.ttlSeconds);\n }\n\n async getTurns(conversationId: string, limit: number): Promise<ConversationTurn[]> {\n if (limit <= 0) return [];\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const raw = await this.client.lRange(key, 0, limit - 1);\n if (raw.length === 0) return [];\n const parsed: ConversationTurn[] = [];\n for (const entry of raw) {\n const turn = safeParseTurn(entry);\n if (turn) parsed.push(turn);\n }\n // LPUSH puts newest at head. Reverse so callers see oldest -> newest.\n parsed.reverse();\n return parsed;\n }\n\n async clear(conversationId: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del([this.dataKey(conversationId)]);\n }\n\n private dataKey(conversationId: string): string {\n return `${this.keyPrefix}:${conversationId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisConversationStore}. Accepts a pre-built client OR\n * a `url`; when only `url` is provided, the factory constructs the underlying\n * redis client internally so consumers never import the SDK.\n */\nexport function redisConversationStore(\n config: RedisConversationStoreConfig,\n): ConversationStore {\n return new RedisConversationStore(config);\n}\n\nfunction safeParseTurn(raw: string): ConversationTurn | undefined {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] dropping malformed JSON turn: ${(err as Error).message}`,\n );\n return undefined;\n }\n if (!isConversationTurn(value)) {\n // eslint-disable-next-line no-console\n console.warn('[RedisConversationStore] dropping turn with unexpected shape');\n return undefined;\n }\n return value;\n}\n\nfunction isConversationTurn(value: unknown): value is ConversationTurn {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.role !== 'string' || !ALLOWED_ROLES.has(v.role)) return false;\n if (typeof v.content !== 'string') return false;\n if (typeof v.timestamp !== 'number' || !Number.isFinite(v.timestamp)) return false;\n if (v.retrievedNodeIds !== undefined) {\n if (!Array.isArray(v.retrievedNodeIds)) return false;\n if (!v.retrievedNodeIds.every((id) => typeof id === 'string')) return false;\n }\n return true;\n}\n\n// Keep the public type re-exports for the legacy import site (RedisConversationTurn).\n/**\n * @deprecated Re-export of the core `ConversationTurn` type. Use\n * `import type { ConversationTurn } from '@inferagraph/core/data';` instead.\n */\nexport type RedisConversationTurn = ConversationTurn;\n","import type {\n EmbeddingRecord,\n EmbeddingStore,\n NodeId,\n SearchVectorHit,\n SimilarHit,\n Vector,\n} from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link RedisVectorEmbeddingStore}. Targets RediSearch\n * (Redis Stack); the store is provider-agnostic — vector dimensionality and\n * key naming are constructor options with neutral defaults so hosts can swap\n * embedding providers (OpenAI, Voyage, etc.) without forking this package.\n */\nexport interface RedisVectorEmbeddingStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /**\n * Logical key namespace. Embedding hashes are stored at\n * `<keyPrefix>:embedding:<nodeId>`; the index name defaults to\n * `<keyPrefix>:embeddings:idx`. Defaults to `'inferagraph'`.\n */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:embeddings:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072` (matches `text-embedding-3-large`). */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link EmbeddingStore} backed by Redis Stack with a RediSearch\n * vector index over hash-stored embeddings.\n *\n * Storage layout:\n * - `<keyPrefix>:embedding:<nodeId>` — HASH containing\n * `nodeId`, `embedding` (binary Float32Array), `embeddingHash`,\n * `embeddingModel`, `embeddingVersion`, `embeddingGeneratedAt`.\n * - `<keyPrefix>:embeddings:idx` — RediSearch index over the hashes.\n *\n * The store also satisfies the optional `searchVector(queryVec, {top, container?})`\n * surface on `EmbeddingStore` so the engine's hybrid retrieval can call it\n * without a tier check.\n */\nexport class RedisVectorEmbeddingStore implements EmbeddingStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} (single source of truth) — the read +\n * write paths don't need to know the dimension explicitly because\n * {@link vectorToBytes} packs whatever length the caller passes.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisVectorEmbeddingStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisVectorEmbeddingStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:embeddings:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n /** Lookup keyed by `(nodeId, model, modelVersion, contentHash)`. */\n async get(\n nodeId: NodeId,\n model: string,\n modelVersion: string,\n contentHash: string,\n ): Promise<EmbeddingRecord | undefined> {\n await this.ensureConnected();\n if (!this.client.hGetAll) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hGetAll');\n }\n const key = this.embeddingKey(nodeId);\n const hash = await this.client.hGetAll(key);\n if (!hash || Object.keys(hash).length === 0) return undefined;\n\n const storedModel = bufferToString(hash.embeddingModel);\n const storedVersion = bufferToString(hash.embeddingVersion);\n const storedHash = bufferToString(hash.embeddingHash);\n if (storedModel !== model) return undefined;\n if (storedVersion !== modelVersion) return undefined;\n if (storedHash !== contentHash) return undefined;\n\n const vectorBytes = hash.embedding;\n if (vectorBytes === undefined) return undefined;\n return {\n nodeId,\n vector: bytesToVector(vectorBytes),\n meta: {\n model: storedModel,\n modelVersion: storedVersion,\n contentHash: storedHash,\n generatedAt: bufferToString(hash.embeddingGeneratedAt) ?? '',\n },\n };\n }\n\n /**\n * Persist an embedding as a HASH; the RediSearch index automatically picks\n * it up because the index is configured with `PREFIX 1 <keyPrefix>:embedding:`.\n */\n async set(record: EmbeddingRecord): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hSet');\n }\n const key = this.embeddingKey(record.nodeId);\n await this.client.hSet(key, {\n nodeId: record.nodeId,\n embedding: vectorToBytes(record.vector),\n embeddingModel: record.meta.model,\n embeddingVersion: record.meta.modelVersion,\n embeddingHash: record.meta.contentHash,\n embeddingGeneratedAt: record.meta.generatedAt,\n });\n }\n\n /**\n * Linear-scan-style similarity is satisfied by delegating to\n * {@link searchVector}, which uses the vector index. Model + version scope\n * filters are intentionally honored at write time (each entry stores its\n * own metadata), so this method ignores them.\n */\n async similar(\n queryVector: Vector,\n k: number,\n _model?: string,\n _modelVersion?: string,\n ): Promise<SimilarHit[]> {\n void _model;\n void _modelVersion;\n const hits = await this.searchVector(queryVector, { top: k });\n return hits.map((h) => ({ nodeId: h.nodeId, score: h.score }));\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:embedding:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n /**\n * Vector-native top-K via RediSearch KNN.\n *\n * Issues `FT.SEARCH <idx> \"*=>[KNN $top @embedding $vec AS score]\"\n * PARAMS 4 vec <bytes> top <K> SORTBY score ASC RETURN 1 nodeId DIALECT 2`.\n * RediSearch returns the COSINE distance (lower = more similar); we convert\n * to similarity (`1 - distance`) so the contract's \"higher = more similar\"\n * holds.\n */\n async searchVector(\n queryEmbedding: Vector,\n opts: { top: number; container?: 'units' | 'inferred_edges' },\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisVectorEmbeddingStore: client does not support ft.search');\n }\n // The default index covers units; inferred-edges callers should use the\n // dedicated InferredEdgeStore. Honor the `container` option only as a\n // forward-compatibility hint — when set to 'inferred_edges' the caller\n // would normally route to RedisInferredEdgeStore.searchInferredEdges.\n void opts.container;\n\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top: opts.top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['nodeId', 'score'],\n DIALECT: 2,\n },\n );\n\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n return {\n nodeId: bufferToString(value.nodeId) ?? doc.id,\n score: similarity,\n };\n });\n }\n\n private embeddingKey(nodeId: NodeId): string {\n return `${this.keyPrefix}:embedding:${nodeId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisVectorEmbeddingStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisVectorEmbeddingStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the underlying redis\n * client internally so consumers don't import the SDK directly.\n */\nexport function redisVectorEmbeddingStore(\n config: RedisVectorEmbeddingStoreConfig,\n): EmbeddingStore {\n return new RedisVectorEmbeddingStore(config);\n}\n\n/**\n * Convert a `Vector` to a Float32Array packed into a Node `Buffer` — the\n * binary form RediSearch expects for a `VECTOR FLOAT32` field.\n */\nexport function vectorToBytes(vector: Vector): Buffer {\n const arr = new Float32Array(vector.length);\n for (let i = 0; i < vector.length; i++) arr[i] = vector[i];\n return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/**\n * Decode the binary `embedding` field of a RediSearch hash back into a\n * plain `Vector` (`number[]`). Accepts either a `Buffer` (real client) or a\n * `string` (some serializers; treated as latin1 bytes).\n */\nexport function bytesToVector(value: string | Buffer): Vector {\n const buf = typeof value === 'string' ? Buffer.from(value, 'binary') : value;\n const view = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n Math.floor(buf.byteLength / 4),\n );\n const out: number[] = new Array(view.length);\n for (let i = 0; i < view.length; i++) out[i] = view[i];\n return out;\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import type {\n InferredEdge,\n InferredEdgeSource,\n InferredEdgeStore,\n NodeId,\n SearchVectorHit,\n Vector,\n} from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\nimport { vectorToBytes } from './redisVectorEmbeddingStore.js';\n\n/**\n * Configuration for {@link RedisInferredEdgeStore}. Provider-agnostic — the\n * vector dimensionality and key naming are constructor options with neutral\n * defaults.\n */\nexport interface RedisInferredEdgeStoreConfig {\n /** Pre-built client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. Factory builds the client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:inferred_edges:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link InferredEdgeStore} backed by a separate RediSearch index\n * over edge HASHes.\n *\n * Storage layout:\n * - `<keyPrefix>:inferred_edge:<sourceId>:<targetId>:<type>` — HASH with\n * `id`, `sourceId`, `targetId`, `type`, `score`, `sources`, `reasoning`,\n * `embedding` (optional, Float32 binary).\n * - `<keyPrefix>:inferred_edges:idx` — RediSearch index with TAG indexes on\n * `sourceId` and `targetId` plus a HNSW vector index on `embedding`.\n */\nexport class RedisInferredEdgeStore implements InferredEdgeStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} — read + write paths use whatever\n * length the caller passes via {@link vectorToBytes}.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisInferredEdgeStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisInferredEdgeStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:inferred_edges:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n async get(sourceId: NodeId, targetId: NodeId): Promise<InferredEdge | undefined> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `@sourceId:{${escapeTag(sourceId)}} @targetId:{${escapeTag(targetId)}}`,\n { LIMIT: { from: 0, size: 1 }, DIALECT: 2 },\n );\n if (!reply || (reply.total ?? 0) === 0 || reply.documents.length === 0) {\n return undefined;\n }\n return docToEdge(reply.documents[0].value);\n }\n\n async getAllForNode(nodeId: NodeId): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const tag = escapeTag(nodeId);\n const reply = await this.client.ft.search(\n this.indexName,\n `(@sourceId:{${tag}}) | (@targetId:{${tag}})`,\n { LIMIT: { from: 0, size: 10_000 }, DIALECT: 2 },\n );\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n async getAll(): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(this.indexName, '*', {\n LIMIT: { from: 0, size: 10_000 },\n DIALECT: 2,\n });\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n /**\n * Bulk-replace the entire stored set. SCAN existing edge keys, DEL them in\n * one batch, then HSET the new entries. Idempotent on `(sourceId,\n * targetId, type)` collisions because the key encodes those three values\n * (last write wins, per the contract).\n */\n async set(edges: ReadonlyArray<InferredEdge>): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisInferredEdgeStore: client does not support hSet');\n }\n await this.deleteAllEdgeKeys();\n for (const edge of edges) {\n const key = this.edgeKey(edge.sourceId, edge.targetId, edge.type);\n await this.client.hSet(key, edgeToHashFields(edge));\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n await this.deleteAllEdgeKeys();\n }\n\n /**\n * Vector-native top-K against the inferred-edges index. Uses the same\n * `KNN` syntax as {@link RedisVectorEmbeddingStore.searchVector}; converts\n * the returned distance to similarity (`1 - distance`) so the contract's\n * \"higher = more similar\" holds.\n */\n async searchInferredEdges(\n queryEmbedding: Vector,\n top: number,\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['id', 'score'],\n DIALECT: 2,\n },\n );\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n const id = bufferToString(value.id) ?? doc.id;\n return { nodeId: id, score: similarity };\n });\n }\n\n private edgeKey(sourceId: NodeId, targetId: NodeId, type: string): string {\n return `${this.keyPrefix}:inferred_edge:${sourceId}:${targetId}:${type}`;\n }\n\n private async deleteAllEdgeKeys(): Promise<void> {\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:inferred_edge:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisInferredEdgeStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisInferredEdgeStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the redis client\n * internally.\n */\nexport function redisInferredEdgeStore(\n config: RedisInferredEdgeStoreConfig,\n): InferredEdgeStore {\n return new RedisInferredEdgeStore(config);\n}\n\nfunction edgeToHashFields(edge: InferredEdge): Record<string, string | number | Buffer> {\n const fields: Record<string, string | number | Buffer> = {\n id: edgeId(edge),\n sourceId: edge.sourceId,\n targetId: edge.targetId,\n type: edge.type,\n score: edge.score,\n sources: edge.sources.join(','),\n };\n if (edge.reasoning !== undefined) fields.reasoning = edge.reasoning;\n if (edge.perSource !== undefined) {\n fields.perSource = JSON.stringify(edge.perSource);\n }\n return fields;\n}\n\nfunction edgeId(edge: InferredEdge): string {\n return `${edge.sourceId}-${edge.targetId}-${edge.type}`;\n}\n\nfunction docToEdge(value: Record<string, unknown>): InferredEdge {\n const sources = parseSources(bufferToString(value.sources));\n const perSourceRaw = bufferToString(value.perSource);\n let perSource: InferredEdge['perSource'];\n if (perSourceRaw) {\n try {\n perSource = JSON.parse(perSourceRaw) as InferredEdge['perSource'];\n } catch {\n perSource = undefined;\n }\n }\n const reasoning = bufferToString(value.reasoning);\n return {\n sourceId: bufferToString(value.sourceId) ?? '',\n targetId: bufferToString(value.targetId) ?? '',\n type: bufferToString(value.type) ?? '',\n score: parseFloat(String(value.score ?? '0')),\n sources,\n reasoning,\n perSource,\n };\n}\n\nfunction parseSources(raw: string | undefined): InferredEdgeSource[] {\n if (!raw) return [];\n return raw\n .split(',')\n .filter((s) => s.length > 0)\n .filter(\n (s): s is InferredEdgeSource => s === 'graph' || s === 'embedding' || s === 'llm',\n );\n}\n\nfunction escapeTag(value: string): string {\n // RediSearch TAG values escape `,` `.` `<` `>` `{` `}` `[` `]` `\\\"` `'` `:` `;` `!` `@` `#` `$` `%` `^` `&` `*` `(` `)` `-` `+` `=` `~` `|` `\\` and whitespace.\n return value.replace(/([,.<>{}[\\]\"':;!@#$%^&*()\\-+=~|\\\\\\s])/g, '\\\\$1');\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link provisionRedisVectorIndex}. Provider-agnostic:\n * dimensions and key naming are constructor options. The default is to\n * provision BOTH the units (embeddings) index and the inferred-edges index;\n * pass `alsoProvisionInferredEdges: false` to skip the latter.\n */\nexport interface ProvisionRedisVectorIndexConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The function builds a client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n /**\n * Whether to also provision the separate inferred-edges index. Defaults to\n * `true` so most consumers get both indexes from a single call.\n */\n alsoProvisionInferredEdges?: boolean;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Idempotent setup for the RediSearch vector indexes used by\n * {@link RedisVectorEmbeddingStore} and {@link RedisInferredEdgeStore}.\n *\n * Creates `<keyPrefix>:embeddings:idx` and (by default)\n * `<keyPrefix>:inferred_edges:idx`. If an index already exists, the function\n * catches the \"index already exists\" error and treats it as a no-op so this\n * call is safe to run on every deploy.\n *\n * Hosts call this once at deploy time before wiring the stores.\n */\nexport async function provisionRedisVectorIndex(\n config: ProvisionRedisVectorIndexConfig,\n): Promise<void> {\n if (!config.client && !config.url) {\n throw new Error(\n 'provisionRedisVectorIndex: either `url` or `client` must be provided',\n );\n }\n const client: RedisLikeClient =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n const keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n const dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n const alsoEdges = config.alsoProvisionInferredEdges ?? true;\n\n if (!client.isOpen) {\n await client.connect();\n }\n if (!client.ft) {\n throw new Error('provisionRedisVectorIndex: client does not support ft.create');\n }\n\n await safeCreateIndex(client, `${keyPrefix}:embeddings:idx`, {\n nodeId: { type: 'TAG' },\n embeddingHash: { type: 'TAG' },\n embeddingModel: { type: 'TAG' },\n embeddingVersion: { type: 'TAG' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:embedding:` },\n });\n\n if (alsoEdges) {\n await safeCreateIndex(client, `${keyPrefix}:inferred_edges:idx`, {\n id: { type: 'TAG' },\n sourceId: { type: 'TAG' },\n targetId: { type: 'TAG' },\n type: { type: 'TAG' },\n score: { type: 'NUMERIC' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:inferred_edge:` },\n });\n }\n}\n\nasync function safeCreateIndex(\n client: RedisLikeClient,\n indexName: string,\n schema: Record<string, unknown>,\n options: Record<string, unknown>,\n): Promise<void> {\n try {\n await client.ft!.create(indexName, schema, options);\n } catch (err) {\n if (isIndexAlreadyExistsError(err)) return;\n throw err;\n }\n}\n\nfunction isIndexAlreadyExistsError(err: unknown): boolean {\n if (!err) return false;\n const msg =\n err instanceof Error\n ? err.message\n : typeof err === 'string'\n ? err\n : String((err as { message?: unknown })?.message ?? err);\n // RediSearch returns \"Index already exists\" (case varies by version).\n return /index\\s+already\\s+exists/i.test(msg);\n}\n"],"mappings":"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","names":["import_redis","import_redis","import_redis","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS","bufferToString","import_redis","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS"]} |
+2
-2
@@ -1,2 +0,2 @@ | ||
| import { CacheConfig, CacheProvider, ConversationStore, ConversationTurn, EmbeddingStore, NodeId, EmbeddingRecord, Vector, SimilarHit, SearchVectorHit, InferredEdgeStore, InferredEdge } from '@inferagraph/core'; | ||
| import { CacheConfig, CacheProvider, ConversationStore, ConversationTurn, EmbeddingStore, NodeId, EmbeddingRecord, Vector, SimilarHit, SearchVectorHit, InferredEdgeStore, InferredEdge } from '@inferagraph/core/data'; | ||
@@ -197,3 +197,3 @@ /** | ||
| * @deprecated Re-export of the core `ConversationTurn` type. Use | ||
| * `import type { ConversationTurn } from '@inferagraph/core';` instead. | ||
| * `import type { ConversationTurn } from '@inferagraph/core/data';` instead. | ||
| */ | ||
@@ -200,0 +200,0 @@ type RedisConversationTurn = ConversationTurn; |
+2
-2
@@ -1,2 +0,2 @@ | ||
| import { CacheConfig, CacheProvider, ConversationStore, ConversationTurn, EmbeddingStore, NodeId, EmbeddingRecord, Vector, SimilarHit, SearchVectorHit, InferredEdgeStore, InferredEdge } from '@inferagraph/core'; | ||
| import { CacheConfig, CacheProvider, ConversationStore, ConversationTurn, EmbeddingStore, NodeId, EmbeddingRecord, Vector, SimilarHit, SearchVectorHit, InferredEdgeStore, InferredEdge } from '@inferagraph/core/data'; | ||
@@ -197,3 +197,3 @@ /** | ||
| * @deprecated Re-export of the core `ConversationTurn` type. Use | ||
| * `import type { ConversationTurn } from '@inferagraph/core';` instead. | ||
| * `import type { ConversationTurn } from '@inferagraph/core/data';` instead. | ||
| */ | ||
@@ -200,0 +200,0 @@ type RedisConversationTurn = ConversationTurn; |
+1
-1
| // src/redisCacheProvider.ts | ||
| import { parseTTL } from "@inferagraph/core"; | ||
| import { parseTTL } from "@inferagraph/core/data"; | ||
| import { createClient } from "redis"; | ||
@@ -4,0 +4,0 @@ var DEFAULT_PREFIX = "infera:cache:"; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/redisCacheProvider.ts","../src/redisConversationStore.ts","../src/redisVectorEmbeddingStore.ts","../src/redisInferredEdgeStore.ts","../src/provisionRedisVectorIndex.ts"],"sourcesContent":["import type { CacheProvider } from '@inferagraph/core';\nimport { parseTTL } from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisCacheConfig, RedisLikeClient } from './types.js';\n\nconst DEFAULT_PREFIX = 'infera:cache:';\n\n/**\n * Redis-backed `CacheProvider` (core 0.9.0+ wider shape).\n *\n * Honors the same defaults as the in-memory `lruCache`:\n * - Both `maxEntries` and `ttl` unset -> `(500, '24h')`.\n * - Only one set -> unset bound treated as no-limit.\n * - Both set -> both bounds enforced.\n * - `-1` / `'-1'` disables the corresponding bound.\n *\n * The class is exposed for direct use; most consumers should prefer the\n * {@link redisCacheProvider} factory which constructs the underlying redis\n * client when a `url` is supplied.\n */\nexport class RedisCacheProvider implements CacheProvider {\n private readonly prefix: string;\n private readonly indexKey: string;\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly client: RedisLikeClient;\n private readonly maskedUrl: string;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisCacheConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisCacheProvider: either `url` or `client` must be provided');\n }\n\n this.prefix = config.prefix ?? DEFAULT_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n\n const hasMaxEntries = config.maxEntries !== undefined;\n const hasTtl = config.ttl !== undefined;\n\n if (!hasMaxEntries && !hasTtl) {\n this.maxEntries = 500;\n this.defaultTtlMs = parseTTL('24h');\n } else {\n this.maxEntries = hasMaxEntries ? normalizeMaxEntries(config.maxEntries!) : -1;\n this.defaultTtlMs = hasTtl ? parseTTL(config.ttl!) : -1;\n }\n\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.maskedUrl = config.url ? maskUrl(config.url) : '<pre-built client>';\n }\n\n async get(key: string): Promise<string | undefined> {\n await this.ensureConnected();\n const v = await this.client.get(this.dataKey(key));\n return v === null ? undefined : v;\n }\n\n async set(\n key: string,\n value: string,\n opts?: { ttlSeconds?: number },\n ): Promise<void> {\n await this.ensureConnected();\n const k = this.dataKey(key);\n\n const setOptions = this.resolveSetTtlOptions(opts);\n\n if (setOptions) {\n await this.client.set(k, value, setOptions);\n } else {\n await this.client.set(k, value);\n }\n\n if (this.maxEntries !== -1) {\n // Track insertion order in a ZSET; evict oldest when over capacity.\n await this.client.zAdd(this.indexKey, { score: Date.now(), value: key });\n const size = await this.client.zCard(this.indexKey);\n if (size > this.maxEntries) {\n const evictCount = size - this.maxEntries;\n const oldest = await this.client.zRange(this.indexKey, 0, evictCount - 1);\n if (oldest.length > 0) {\n await this.client.zRem(this.indexKey, oldest);\n await this.client.del(oldest.map((cacheKey) => this.dataKey(cacheKey)));\n }\n }\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del(this.dataKey(key));\n if (this.maxEntries !== -1) {\n await this.client.zRem(this.indexKey, key);\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const toDelete: string[] = [];\n // SCAN with MATCH is safe on large datasets; KEYS would block the server.\n // Deliberately scoped to `${prefix}*` so we never touch keys outside our\n // namespace (NOT FLUSHDB).\n for await (const k of this.client.scanIterator({ MATCH: `${this.prefix}*`, COUNT: 100 })) {\n toDelete.push(k);\n }\n if (toDelete.length > 0) {\n await this.client.del(toDelete);\n }\n }\n\n private dataKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n /**\n * Decide which TTL (per-call vs construction default) applies to a SET.\n * Per-call wins. Returns `undefined` when no TTL applies (no EX/PX).\n */\n private resolveSetTtlOptions(\n opts: { ttlSeconds?: number } | undefined,\n ): { EX: number } | { PX: number } | undefined {\n if (opts?.ttlSeconds !== undefined) {\n // Per-call TTL is always seconds-precision per the contract.\n if (opts.ttlSeconds <= 0) return undefined;\n return { EX: Math.floor(opts.ttlSeconds) };\n }\n if (this.defaultTtlMs === -1) return undefined;\n return this.defaultTtlMs % 1000 === 0\n ? { EX: Math.floor(this.defaultTtlMs / 1000) }\n : { PX: this.defaultTtlMs };\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n // Reset so a future operation can retry. Re-throw so the caller sees it.\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisCacheProvider] failed to connect to ${this.maskedUrl}: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisCacheProvider}. Accepts either a pre-built `client`\n * or a `url`; when only `url` is provided, the factory builds the underlying\n * `redis` client internally so consumers don't import the SDK.\n */\nexport function redisCacheProvider(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\n/**\n * @deprecated Use {@link redisCacheProvider} (or `RedisCacheProvider`)\n * directly. Kept temporarily as an alias to ease migration off the old\n * package name `@inferagraph/redis-cache-provider`.\n */\nexport function redisCache(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\nfunction normalizeMaxEntries(value: number): number {\n if (value === -1) return -1;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new Error(\n `RedisCacheProvider: invalid maxEntries ${value}; expected a non-negative integer or -1`,\n );\n }\n return value;\n}\n\nfunction maskUrl(url: string): string {\n // Mask password component (between `:` and `@`) in a connection URL.\n // E.g. redis://default:hunter2@host:6379 -> redis://default:***@host:6379\n return url.replace(/(:\\/\\/[^:]+:)[^@]+(@)/, '$1***$2');\n}\n","import type { ConversationStore, ConversationTurn } from '@inferagraph/core';\nimport { createClient } from 'redis';\n\n/**\n * Subset of node-redis v4 the conversation store needs (LIST + EXPIRE + DEL).\n * Re-exported for tests so a stub can target only the conversation surface.\n */\nexport interface RedisConversationLikeClient {\n isOpen?: boolean;\n connect(): Promise<unknown>;\n lPush(key: string, element: string | string[]): Promise<number>;\n lTrim(key: string, start: number, stop: number): Promise<string>;\n lRange(key: string, start: number, stop: number): Promise<string[]>;\n expire(key: string, seconds: number): Promise<number>;\n del(keys: string | string[]): Promise<number>;\n}\n\nexport interface RedisConversationStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisConversationLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /** Key prefix for conversation lists. Default `inferagraph:conversation`. */\n keyPrefix?: string;\n /** TTL refreshed on every appendTurn. Default `86400` (24h). */\n ttlSeconds?: number;\n}\n\nconst DEFAULT_CONVERSATION_PREFIX = 'inferagraph:conversation';\nconst DEFAULT_CONVERSATION_TTL_SECONDS = 86_400;\n/** Hard cap on stored turns per conversation. LTRIM 0 (MAX-1). */\nconst MAX_TURNS_PER_CONVERSATION = 1000;\nconst ALLOWED_ROLES = new Set(['user', 'assistant']);\n\n/**\n * Redis-backed `ConversationStore` for `@inferagraph/core@^0.9.0`'s `AIEngine`.\n *\n * Storage layout: one Redis LIST per conversation, keyed by\n * `<keyPrefix>:<conversationId>`. Each element is a JSON-serialized turn.\n * Newest turns sit at the head (LPUSH); reads pull the most-recent N via\n * LRANGE 0 N-1 and reverse so callers see oldest -> newest, matching LLM\n * conversation order. Each `appendTurn` refreshes a TTL via EXPIRE so\n * inactive conversations age out without manual cleanup.\n *\n * Defensive: malformed entries (corrupt JSON, wrong shape) are skipped with\n * a `console.warn` rather than thrown — one bad write must not poison the\n * entire conversation history.\n */\nexport class RedisConversationStore implements ConversationStore {\n private readonly client: RedisConversationLikeClient;\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisConversationStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisConversationStore: either `url` or `client` must be provided');\n }\n this.client =\n config.client ??\n (createClient({ url: config.url! }) as unknown as RedisConversationLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_CONVERSATION_PREFIX;\n this.ttlSeconds = config.ttlSeconds ?? DEFAULT_CONVERSATION_TTL_SECONDS;\n }\n\n async appendTurn(conversationId: string, turn: ConversationTurn): Promise<void> {\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const payload = JSON.stringify(turn);\n await this.client.lPush(key, payload);\n await this.client.lTrim(key, 0, MAX_TURNS_PER_CONVERSATION - 1);\n await this.client.expire(key, this.ttlSeconds);\n }\n\n async getTurns(conversationId: string, limit: number): Promise<ConversationTurn[]> {\n if (limit <= 0) return [];\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const raw = await this.client.lRange(key, 0, limit - 1);\n if (raw.length === 0) return [];\n const parsed: ConversationTurn[] = [];\n for (const entry of raw) {\n const turn = safeParseTurn(entry);\n if (turn) parsed.push(turn);\n }\n // LPUSH puts newest at head. Reverse so callers see oldest -> newest.\n parsed.reverse();\n return parsed;\n }\n\n async clear(conversationId: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del([this.dataKey(conversationId)]);\n }\n\n private dataKey(conversationId: string): string {\n return `${this.keyPrefix}:${conversationId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisConversationStore}. Accepts a pre-built client OR\n * a `url`; when only `url` is provided, the factory constructs the underlying\n * redis client internally so consumers never import the SDK.\n */\nexport function redisConversationStore(\n config: RedisConversationStoreConfig,\n): ConversationStore {\n return new RedisConversationStore(config);\n}\n\nfunction safeParseTurn(raw: string): ConversationTurn | undefined {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] dropping malformed JSON turn: ${(err as Error).message}`,\n );\n return undefined;\n }\n if (!isConversationTurn(value)) {\n // eslint-disable-next-line no-console\n console.warn('[RedisConversationStore] dropping turn with unexpected shape');\n return undefined;\n }\n return value;\n}\n\nfunction isConversationTurn(value: unknown): value is ConversationTurn {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.role !== 'string' || !ALLOWED_ROLES.has(v.role)) return false;\n if (typeof v.content !== 'string') return false;\n if (typeof v.timestamp !== 'number' || !Number.isFinite(v.timestamp)) return false;\n if (v.retrievedNodeIds !== undefined) {\n if (!Array.isArray(v.retrievedNodeIds)) return false;\n if (!v.retrievedNodeIds.every((id) => typeof id === 'string')) return false;\n }\n return true;\n}\n\n// Keep the public type re-exports for the legacy import site (RedisConversationTurn).\n/**\n * @deprecated Re-export of the core `ConversationTurn` type. Use\n * `import type { ConversationTurn } from '@inferagraph/core';` instead.\n */\nexport type RedisConversationTurn = ConversationTurn;\n","import type {\n EmbeddingRecord,\n EmbeddingStore,\n NodeId,\n SearchVectorHit,\n SimilarHit,\n Vector,\n} from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link RedisVectorEmbeddingStore}. Targets RediSearch\n * (Redis Stack); the store is provider-agnostic — vector dimensionality and\n * key naming are constructor options with neutral defaults so hosts can swap\n * embedding providers (OpenAI, Voyage, etc.) without forking this package.\n */\nexport interface RedisVectorEmbeddingStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /**\n * Logical key namespace. Embedding hashes are stored at\n * `<keyPrefix>:embedding:<nodeId>`; the index name defaults to\n * `<keyPrefix>:embeddings:idx`. Defaults to `'inferagraph'`.\n */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:embeddings:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072` (matches `text-embedding-3-large`). */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link EmbeddingStore} backed by Redis Stack with a RediSearch\n * vector index over hash-stored embeddings.\n *\n * Storage layout:\n * - `<keyPrefix>:embedding:<nodeId>` — HASH containing\n * `nodeId`, `embedding` (binary Float32Array), `embeddingHash`,\n * `embeddingModel`, `embeddingVersion`, `embeddingGeneratedAt`.\n * - `<keyPrefix>:embeddings:idx` — RediSearch index over the hashes.\n *\n * The store also satisfies the optional `searchVector(queryVec, {top, container?})`\n * surface on `EmbeddingStore` so the engine's hybrid retrieval can call it\n * without a tier check.\n */\nexport class RedisVectorEmbeddingStore implements EmbeddingStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} (single source of truth) — the read +\n * write paths don't need to know the dimension explicitly because\n * {@link vectorToBytes} packs whatever length the caller passes.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisVectorEmbeddingStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisVectorEmbeddingStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:embeddings:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n /** Lookup keyed by `(nodeId, model, modelVersion, contentHash)`. */\n async get(\n nodeId: NodeId,\n model: string,\n modelVersion: string,\n contentHash: string,\n ): Promise<EmbeddingRecord | undefined> {\n await this.ensureConnected();\n if (!this.client.hGetAll) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hGetAll');\n }\n const key = this.embeddingKey(nodeId);\n const hash = await this.client.hGetAll(key);\n if (!hash || Object.keys(hash).length === 0) return undefined;\n\n const storedModel = bufferToString(hash.embeddingModel);\n const storedVersion = bufferToString(hash.embeddingVersion);\n const storedHash = bufferToString(hash.embeddingHash);\n if (storedModel !== model) return undefined;\n if (storedVersion !== modelVersion) return undefined;\n if (storedHash !== contentHash) return undefined;\n\n const vectorBytes = hash.embedding;\n if (vectorBytes === undefined) return undefined;\n return {\n nodeId,\n vector: bytesToVector(vectorBytes),\n meta: {\n model: storedModel,\n modelVersion: storedVersion,\n contentHash: storedHash,\n generatedAt: bufferToString(hash.embeddingGeneratedAt) ?? '',\n },\n };\n }\n\n /**\n * Persist an embedding as a HASH; the RediSearch index automatically picks\n * it up because the index is configured with `PREFIX 1 <keyPrefix>:embedding:`.\n */\n async set(record: EmbeddingRecord): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hSet');\n }\n const key = this.embeddingKey(record.nodeId);\n await this.client.hSet(key, {\n nodeId: record.nodeId,\n embedding: vectorToBytes(record.vector),\n embeddingModel: record.meta.model,\n embeddingVersion: record.meta.modelVersion,\n embeddingHash: record.meta.contentHash,\n embeddingGeneratedAt: record.meta.generatedAt,\n });\n }\n\n /**\n * Linear-scan-style similarity is satisfied by delegating to\n * {@link searchVector}, which uses the vector index. Model + version scope\n * filters are intentionally honored at write time (each entry stores its\n * own metadata), so this method ignores them.\n */\n async similar(\n queryVector: Vector,\n k: number,\n _model?: string,\n _modelVersion?: string,\n ): Promise<SimilarHit[]> {\n void _model;\n void _modelVersion;\n const hits = await this.searchVector(queryVector, { top: k });\n return hits.map((h) => ({ nodeId: h.nodeId, score: h.score }));\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:embedding:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n /**\n * Vector-native top-K via RediSearch KNN.\n *\n * Issues `FT.SEARCH <idx> \"*=>[KNN $top @embedding $vec AS score]\"\n * PARAMS 4 vec <bytes> top <K> SORTBY score ASC RETURN 1 nodeId DIALECT 2`.\n * RediSearch returns the COSINE distance (lower = more similar); we convert\n * to similarity (`1 - distance`) so the contract's \"higher = more similar\"\n * holds.\n */\n async searchVector(\n queryEmbedding: Vector,\n opts: { top: number; container?: 'units' | 'inferred_edges' },\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisVectorEmbeddingStore: client does not support ft.search');\n }\n // The default index covers units; inferred-edges callers should use the\n // dedicated InferredEdgeStore. Honor the `container` option only as a\n // forward-compatibility hint — when set to 'inferred_edges' the caller\n // would normally route to RedisInferredEdgeStore.searchInferredEdges.\n void opts.container;\n\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top: opts.top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['nodeId', 'score'],\n DIALECT: 2,\n },\n );\n\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n return {\n nodeId: bufferToString(value.nodeId) ?? doc.id,\n score: similarity,\n };\n });\n }\n\n private embeddingKey(nodeId: NodeId): string {\n return `${this.keyPrefix}:embedding:${nodeId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisVectorEmbeddingStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisVectorEmbeddingStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the underlying redis\n * client internally so consumers don't import the SDK directly.\n */\nexport function redisVectorEmbeddingStore(\n config: RedisVectorEmbeddingStoreConfig,\n): EmbeddingStore {\n return new RedisVectorEmbeddingStore(config);\n}\n\n/**\n * Convert a `Vector` to a Float32Array packed into a Node `Buffer` — the\n * binary form RediSearch expects for a `VECTOR FLOAT32` field.\n */\nexport function vectorToBytes(vector: Vector): Buffer {\n const arr = new Float32Array(vector.length);\n for (let i = 0; i < vector.length; i++) arr[i] = vector[i];\n return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/**\n * Decode the binary `embedding` field of a RediSearch hash back into a\n * plain `Vector` (`number[]`). Accepts either a `Buffer` (real client) or a\n * `string` (some serializers; treated as latin1 bytes).\n */\nexport function bytesToVector(value: string | Buffer): Vector {\n const buf = typeof value === 'string' ? Buffer.from(value, 'binary') : value;\n const view = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n Math.floor(buf.byteLength / 4),\n );\n const out: number[] = new Array(view.length);\n for (let i = 0; i < view.length; i++) out[i] = view[i];\n return out;\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import type {\n InferredEdge,\n InferredEdgeSource,\n InferredEdgeStore,\n NodeId,\n SearchVectorHit,\n Vector,\n} from '@inferagraph/core';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\nimport { vectorToBytes } from './redisVectorEmbeddingStore.js';\n\n/**\n * Configuration for {@link RedisInferredEdgeStore}. Provider-agnostic — the\n * vector dimensionality and key naming are constructor options with neutral\n * defaults.\n */\nexport interface RedisInferredEdgeStoreConfig {\n /** Pre-built client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. Factory builds the client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:inferred_edges:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link InferredEdgeStore} backed by a separate RediSearch index\n * over edge HASHes.\n *\n * Storage layout:\n * - `<keyPrefix>:inferred_edge:<sourceId>:<targetId>:<type>` — HASH with\n * `id`, `sourceId`, `targetId`, `type`, `score`, `sources`, `reasoning`,\n * `embedding` (optional, Float32 binary).\n * - `<keyPrefix>:inferred_edges:idx` — RediSearch index with TAG indexes on\n * `sourceId` and `targetId` plus a HNSW vector index on `embedding`.\n */\nexport class RedisInferredEdgeStore implements InferredEdgeStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} — read + write paths use whatever\n * length the caller passes via {@link vectorToBytes}.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisInferredEdgeStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisInferredEdgeStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:inferred_edges:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n async get(sourceId: NodeId, targetId: NodeId): Promise<InferredEdge | undefined> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `@sourceId:{${escapeTag(sourceId)}} @targetId:{${escapeTag(targetId)}}`,\n { LIMIT: { from: 0, size: 1 }, DIALECT: 2 },\n );\n if (!reply || (reply.total ?? 0) === 0 || reply.documents.length === 0) {\n return undefined;\n }\n return docToEdge(reply.documents[0].value);\n }\n\n async getAllForNode(nodeId: NodeId): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const tag = escapeTag(nodeId);\n const reply = await this.client.ft.search(\n this.indexName,\n `(@sourceId:{${tag}}) | (@targetId:{${tag}})`,\n { LIMIT: { from: 0, size: 10_000 }, DIALECT: 2 },\n );\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n async getAll(): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(this.indexName, '*', {\n LIMIT: { from: 0, size: 10_000 },\n DIALECT: 2,\n });\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n /**\n * Bulk-replace the entire stored set. SCAN existing edge keys, DEL them in\n * one batch, then HSET the new entries. Idempotent on `(sourceId,\n * targetId, type)` collisions because the key encodes those three values\n * (last write wins, per the contract).\n */\n async set(edges: ReadonlyArray<InferredEdge>): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisInferredEdgeStore: client does not support hSet');\n }\n await this.deleteAllEdgeKeys();\n for (const edge of edges) {\n const key = this.edgeKey(edge.sourceId, edge.targetId, edge.type);\n await this.client.hSet(key, edgeToHashFields(edge));\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n await this.deleteAllEdgeKeys();\n }\n\n /**\n * Vector-native top-K against the inferred-edges index. Uses the same\n * `KNN` syntax as {@link RedisVectorEmbeddingStore.searchVector}; converts\n * the returned distance to similarity (`1 - distance`) so the contract's\n * \"higher = more similar\" holds.\n */\n async searchInferredEdges(\n queryEmbedding: Vector,\n top: number,\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['id', 'score'],\n DIALECT: 2,\n },\n );\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n const id = bufferToString(value.id) ?? doc.id;\n return { nodeId: id, score: similarity };\n });\n }\n\n private edgeKey(sourceId: NodeId, targetId: NodeId, type: string): string {\n return `${this.keyPrefix}:inferred_edge:${sourceId}:${targetId}:${type}`;\n }\n\n private async deleteAllEdgeKeys(): Promise<void> {\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:inferred_edge:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisInferredEdgeStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisInferredEdgeStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the redis client\n * internally.\n */\nexport function redisInferredEdgeStore(\n config: RedisInferredEdgeStoreConfig,\n): InferredEdgeStore {\n return new RedisInferredEdgeStore(config);\n}\n\nfunction edgeToHashFields(edge: InferredEdge): Record<string, string | number | Buffer> {\n const fields: Record<string, string | number | Buffer> = {\n id: edgeId(edge),\n sourceId: edge.sourceId,\n targetId: edge.targetId,\n type: edge.type,\n score: edge.score,\n sources: edge.sources.join(','),\n };\n if (edge.reasoning !== undefined) fields.reasoning = edge.reasoning;\n if (edge.perSource !== undefined) {\n fields.perSource = JSON.stringify(edge.perSource);\n }\n return fields;\n}\n\nfunction edgeId(edge: InferredEdge): string {\n return `${edge.sourceId}-${edge.targetId}-${edge.type}`;\n}\n\nfunction docToEdge(value: Record<string, unknown>): InferredEdge {\n const sources = parseSources(bufferToString(value.sources));\n const perSourceRaw = bufferToString(value.perSource);\n let perSource: InferredEdge['perSource'];\n if (perSourceRaw) {\n try {\n perSource = JSON.parse(perSourceRaw) as InferredEdge['perSource'];\n } catch {\n perSource = undefined;\n }\n }\n const reasoning = bufferToString(value.reasoning);\n return {\n sourceId: bufferToString(value.sourceId) ?? '',\n targetId: bufferToString(value.targetId) ?? '',\n type: bufferToString(value.type) ?? '',\n score: parseFloat(String(value.score ?? '0')),\n sources,\n reasoning,\n perSource,\n };\n}\n\nfunction parseSources(raw: string | undefined): InferredEdgeSource[] {\n if (!raw) return [];\n return raw\n .split(',')\n .filter((s) => s.length > 0)\n .filter(\n (s): s is InferredEdgeSource => s === 'graph' || s === 'embedding' || s === 'llm',\n );\n}\n\nfunction escapeTag(value: string): string {\n // RediSearch TAG values escape `,` `.` `<` `>` `{` `}` `[` `]` `\\\"` `'` `:` `;` `!` `@` `#` `$` `%` `^` `&` `*` `(` `)` `-` `+` `=` `~` `|` `\\` and whitespace.\n return value.replace(/([,.<>{}[\\]\"':;!@#$%^&*()\\-+=~|\\\\\\s])/g, '\\\\$1');\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link provisionRedisVectorIndex}. Provider-agnostic:\n * dimensions and key naming are constructor options. The default is to\n * provision BOTH the units (embeddings) index and the inferred-edges index;\n * pass `alsoProvisionInferredEdges: false` to skip the latter.\n */\nexport interface ProvisionRedisVectorIndexConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The function builds a client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n /**\n * Whether to also provision the separate inferred-edges index. Defaults to\n * `true` so most consumers get both indexes from a single call.\n */\n alsoProvisionInferredEdges?: boolean;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Idempotent setup for the RediSearch vector indexes used by\n * {@link RedisVectorEmbeddingStore} and {@link RedisInferredEdgeStore}.\n *\n * Creates `<keyPrefix>:embeddings:idx` and (by default)\n * `<keyPrefix>:inferred_edges:idx`. If an index already exists, the function\n * catches the \"index already exists\" error and treats it as a no-op so this\n * call is safe to run on every deploy.\n *\n * Hosts call this once at deploy time before wiring the stores.\n */\nexport async function provisionRedisVectorIndex(\n config: ProvisionRedisVectorIndexConfig,\n): Promise<void> {\n if (!config.client && !config.url) {\n throw new Error(\n 'provisionRedisVectorIndex: either `url` or `client` must be provided',\n );\n }\n const client: RedisLikeClient =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n const keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n const dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n const alsoEdges = config.alsoProvisionInferredEdges ?? true;\n\n if (!client.isOpen) {\n await client.connect();\n }\n if (!client.ft) {\n throw new Error('provisionRedisVectorIndex: client does not support ft.create');\n }\n\n await safeCreateIndex(client, `${keyPrefix}:embeddings:idx`, {\n nodeId: { type: 'TAG' },\n embeddingHash: { type: 'TAG' },\n embeddingModel: { type: 'TAG' },\n embeddingVersion: { type: 'TAG' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:embedding:` },\n });\n\n if (alsoEdges) {\n await safeCreateIndex(client, `${keyPrefix}:inferred_edges:idx`, {\n id: { type: 'TAG' },\n sourceId: { type: 'TAG' },\n targetId: { type: 'TAG' },\n type: { type: 'TAG' },\n score: { type: 'NUMERIC' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:inferred_edge:` },\n });\n }\n}\n\nasync function safeCreateIndex(\n client: RedisLikeClient,\n indexName: string,\n schema: Record<string, unknown>,\n options: Record<string, unknown>,\n): Promise<void> {\n try {\n await client.ft!.create(indexName, schema, options);\n } catch (err) {\n if (isIndexAlreadyExistsError(err)) return;\n throw err;\n }\n}\n\nfunction isIndexAlreadyExistsError(err: unknown): boolean {\n if (!err) return false;\n const msg =\n err instanceof Error\n ? err.message\n : typeof err === 'string'\n ? err\n : String((err as { message?: unknown })?.message ?? err);\n // RediSearch returns \"Index already exists\" (case varies by version).\n return /index\\s+already\\s+exists/i.test(msg);\n}\n"],"mappings":"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","names":["createClient","createClient","createClient","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS","createClient","bufferToString","createClient","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS"]} | ||
| {"version":3,"sources":["../src/redisCacheProvider.ts","../src/redisConversationStore.ts","../src/redisVectorEmbeddingStore.ts","../src/redisInferredEdgeStore.ts","../src/provisionRedisVectorIndex.ts"],"sourcesContent":["import type { CacheProvider } from '@inferagraph/core/data';\nimport { parseTTL } from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisCacheConfig, RedisLikeClient } from './types.js';\n\nconst DEFAULT_PREFIX = 'infera:cache:';\n\n/**\n * Redis-backed `CacheProvider` (core 0.9.0+ wider shape).\n *\n * Honors the same defaults as the in-memory `lruCache`:\n * - Both `maxEntries` and `ttl` unset -> `(500, '24h')`.\n * - Only one set -> unset bound treated as no-limit.\n * - Both set -> both bounds enforced.\n * - `-1` / `'-1'` disables the corresponding bound.\n *\n * The class is exposed for direct use; most consumers should prefer the\n * {@link redisCacheProvider} factory which constructs the underlying redis\n * client when a `url` is supplied.\n */\nexport class RedisCacheProvider implements CacheProvider {\n private readonly prefix: string;\n private readonly indexKey: string;\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly client: RedisLikeClient;\n private readonly maskedUrl: string;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisCacheConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisCacheProvider: either `url` or `client` must be provided');\n }\n\n this.prefix = config.prefix ?? DEFAULT_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n\n const hasMaxEntries = config.maxEntries !== undefined;\n const hasTtl = config.ttl !== undefined;\n\n if (!hasMaxEntries && !hasTtl) {\n this.maxEntries = 500;\n this.defaultTtlMs = parseTTL('24h');\n } else {\n this.maxEntries = hasMaxEntries ? normalizeMaxEntries(config.maxEntries!) : -1;\n this.defaultTtlMs = hasTtl ? parseTTL(config.ttl!) : -1;\n }\n\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.maskedUrl = config.url ? maskUrl(config.url) : '<pre-built client>';\n }\n\n async get(key: string): Promise<string | undefined> {\n await this.ensureConnected();\n const v = await this.client.get(this.dataKey(key));\n return v === null ? undefined : v;\n }\n\n async set(\n key: string,\n value: string,\n opts?: { ttlSeconds?: number },\n ): Promise<void> {\n await this.ensureConnected();\n const k = this.dataKey(key);\n\n const setOptions = this.resolveSetTtlOptions(opts);\n\n if (setOptions) {\n await this.client.set(k, value, setOptions);\n } else {\n await this.client.set(k, value);\n }\n\n if (this.maxEntries !== -1) {\n // Track insertion order in a ZSET; evict oldest when over capacity.\n await this.client.zAdd(this.indexKey, { score: Date.now(), value: key });\n const size = await this.client.zCard(this.indexKey);\n if (size > this.maxEntries) {\n const evictCount = size - this.maxEntries;\n const oldest = await this.client.zRange(this.indexKey, 0, evictCount - 1);\n if (oldest.length > 0) {\n await this.client.zRem(this.indexKey, oldest);\n await this.client.del(oldest.map((cacheKey) => this.dataKey(cacheKey)));\n }\n }\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del(this.dataKey(key));\n if (this.maxEntries !== -1) {\n await this.client.zRem(this.indexKey, key);\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const toDelete: string[] = [];\n // SCAN with MATCH is safe on large datasets; KEYS would block the server.\n // Deliberately scoped to `${prefix}*` so we never touch keys outside our\n // namespace (NOT FLUSHDB).\n for await (const k of this.client.scanIterator({ MATCH: `${this.prefix}*`, COUNT: 100 })) {\n toDelete.push(k);\n }\n if (toDelete.length > 0) {\n await this.client.del(toDelete);\n }\n }\n\n private dataKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n /**\n * Decide which TTL (per-call vs construction default) applies to a SET.\n * Per-call wins. Returns `undefined` when no TTL applies (no EX/PX).\n */\n private resolveSetTtlOptions(\n opts: { ttlSeconds?: number } | undefined,\n ): { EX: number } | { PX: number } | undefined {\n if (opts?.ttlSeconds !== undefined) {\n // Per-call TTL is always seconds-precision per the contract.\n if (opts.ttlSeconds <= 0) return undefined;\n return { EX: Math.floor(opts.ttlSeconds) };\n }\n if (this.defaultTtlMs === -1) return undefined;\n return this.defaultTtlMs % 1000 === 0\n ? { EX: Math.floor(this.defaultTtlMs / 1000) }\n : { PX: this.defaultTtlMs };\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n // Reset so a future operation can retry. Re-throw so the caller sees it.\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisCacheProvider] failed to connect to ${this.maskedUrl}: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisCacheProvider}. Accepts either a pre-built `client`\n * or a `url`; when only `url` is provided, the factory builds the underlying\n * `redis` client internally so consumers don't import the SDK.\n */\nexport function redisCacheProvider(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\n/**\n * @deprecated Use {@link redisCacheProvider} (or `RedisCacheProvider`)\n * directly. Kept temporarily as an alias to ease migration off the old\n * package name `@inferagraph/redis-cache-provider`.\n */\nexport function redisCache(config: RedisCacheConfig): CacheProvider {\n return new RedisCacheProvider(config);\n}\n\nfunction normalizeMaxEntries(value: number): number {\n if (value === -1) return -1;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new Error(\n `RedisCacheProvider: invalid maxEntries ${value}; expected a non-negative integer or -1`,\n );\n }\n return value;\n}\n\nfunction maskUrl(url: string): string {\n // Mask password component (between `:` and `@`) in a connection URL.\n // E.g. redis://default:hunter2@host:6379 -> redis://default:***@host:6379\n return url.replace(/(:\\/\\/[^:]+:)[^@]+(@)/, '$1***$2');\n}\n","import type { ConversationStore, ConversationTurn } from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\n/**\n * Subset of node-redis v4 the conversation store needs (LIST + EXPIRE + DEL).\n * Re-exported for tests so a stub can target only the conversation surface.\n */\nexport interface RedisConversationLikeClient {\n isOpen?: boolean;\n connect(): Promise<unknown>;\n lPush(key: string, element: string | string[]): Promise<number>;\n lTrim(key: string, start: number, stop: number): Promise<string>;\n lRange(key: string, start: number, stop: number): Promise<string[]>;\n expire(key: string, seconds: number): Promise<number>;\n del(keys: string | string[]): Promise<number>;\n}\n\nexport interface RedisConversationStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisConversationLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /** Key prefix for conversation lists. Default `inferagraph:conversation`. */\n keyPrefix?: string;\n /** TTL refreshed on every appendTurn. Default `86400` (24h). */\n ttlSeconds?: number;\n}\n\nconst DEFAULT_CONVERSATION_PREFIX = 'inferagraph:conversation';\nconst DEFAULT_CONVERSATION_TTL_SECONDS = 86_400;\n/** Hard cap on stored turns per conversation. LTRIM 0 (MAX-1). */\nconst MAX_TURNS_PER_CONVERSATION = 1000;\nconst ALLOWED_ROLES = new Set(['user', 'assistant']);\n\n/**\n * Redis-backed `ConversationStore` for `@inferagraph/core@^0.9.0`'s `AIEngine`.\n *\n * Storage layout: one Redis LIST per conversation, keyed by\n * `<keyPrefix>:<conversationId>`. Each element is a JSON-serialized turn.\n * Newest turns sit at the head (LPUSH); reads pull the most-recent N via\n * LRANGE 0 N-1 and reverse so callers see oldest -> newest, matching LLM\n * conversation order. Each `appendTurn` refreshes a TTL via EXPIRE so\n * inactive conversations age out without manual cleanup.\n *\n * Defensive: malformed entries (corrupt JSON, wrong shape) are skipped with\n * a `console.warn` rather than thrown — one bad write must not poison the\n * entire conversation history.\n */\nexport class RedisConversationStore implements ConversationStore {\n private readonly client: RedisConversationLikeClient;\n private readonly keyPrefix: string;\n private readonly ttlSeconds: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisConversationStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error('RedisConversationStore: either `url` or `client` must be provided');\n }\n this.client =\n config.client ??\n (createClient({ url: config.url! }) as unknown as RedisConversationLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_CONVERSATION_PREFIX;\n this.ttlSeconds = config.ttlSeconds ?? DEFAULT_CONVERSATION_TTL_SECONDS;\n }\n\n async appendTurn(conversationId: string, turn: ConversationTurn): Promise<void> {\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const payload = JSON.stringify(turn);\n await this.client.lPush(key, payload);\n await this.client.lTrim(key, 0, MAX_TURNS_PER_CONVERSATION - 1);\n await this.client.expire(key, this.ttlSeconds);\n }\n\n async getTurns(conversationId: string, limit: number): Promise<ConversationTurn[]> {\n if (limit <= 0) return [];\n await this.ensureConnected();\n const key = this.dataKey(conversationId);\n const raw = await this.client.lRange(key, 0, limit - 1);\n if (raw.length === 0) return [];\n const parsed: ConversationTurn[] = [];\n for (const entry of raw) {\n const turn = safeParseTurn(entry);\n if (turn) parsed.push(turn);\n }\n // LPUSH puts newest at head. Reverse so callers see oldest -> newest.\n parsed.reverse();\n return parsed;\n }\n\n async clear(conversationId: string): Promise<void> {\n await this.ensureConnected();\n await this.client.del([this.dataKey(conversationId)]);\n }\n\n private dataKey(conversationId: string): string {\n return `${this.keyPrefix}:${conversationId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisConversationStore}. Accepts a pre-built client OR\n * a `url`; when only `url` is provided, the factory constructs the underlying\n * redis client internally so consumers never import the SDK.\n */\nexport function redisConversationStore(\n config: RedisConversationStoreConfig,\n): ConversationStore {\n return new RedisConversationStore(config);\n}\n\nfunction safeParseTurn(raw: string): ConversationTurn | undefined {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisConversationStore] dropping malformed JSON turn: ${(err as Error).message}`,\n );\n return undefined;\n }\n if (!isConversationTurn(value)) {\n // eslint-disable-next-line no-console\n console.warn('[RedisConversationStore] dropping turn with unexpected shape');\n return undefined;\n }\n return value;\n}\n\nfunction isConversationTurn(value: unknown): value is ConversationTurn {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.role !== 'string' || !ALLOWED_ROLES.has(v.role)) return false;\n if (typeof v.content !== 'string') return false;\n if (typeof v.timestamp !== 'number' || !Number.isFinite(v.timestamp)) return false;\n if (v.retrievedNodeIds !== undefined) {\n if (!Array.isArray(v.retrievedNodeIds)) return false;\n if (!v.retrievedNodeIds.every((id) => typeof id === 'string')) return false;\n }\n return true;\n}\n\n// Keep the public type re-exports for the legacy import site (RedisConversationTurn).\n/**\n * @deprecated Re-export of the core `ConversationTurn` type. Use\n * `import type { ConversationTurn } from '@inferagraph/core/data';` instead.\n */\nexport type RedisConversationTurn = ConversationTurn;\n","import type {\n EmbeddingRecord,\n EmbeddingStore,\n NodeId,\n SearchVectorHit,\n SimilarHit,\n Vector,\n} from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link RedisVectorEmbeddingStore}. Targets RediSearch\n * (Redis Stack); the store is provider-agnostic — vector dimensionality and\n * key naming are constructor options with neutral defaults so hosts can swap\n * embedding providers (OpenAI, Voyage, etc.) without forking this package.\n */\nexport interface RedisVectorEmbeddingStoreConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The factory builds a client internally when supplied. */\n url?: string;\n /**\n * Logical key namespace. Embedding hashes are stored at\n * `<keyPrefix>:embedding:<nodeId>`; the index name defaults to\n * `<keyPrefix>:embeddings:idx`. Defaults to `'inferagraph'`.\n */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:embeddings:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072` (matches `text-embedding-3-large`). */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link EmbeddingStore} backed by Redis Stack with a RediSearch\n * vector index over hash-stored embeddings.\n *\n * Storage layout:\n * - `<keyPrefix>:embedding:<nodeId>` — HASH containing\n * `nodeId`, `embedding` (binary Float32Array), `embeddingHash`,\n * `embeddingModel`, `embeddingVersion`, `embeddingGeneratedAt`.\n * - `<keyPrefix>:embeddings:idx` — RediSearch index over the hashes.\n *\n * The store also satisfies the optional `searchVector(queryVec, {top, container?})`\n * surface on `EmbeddingStore` so the engine's hybrid retrieval can call it\n * without a tier check.\n */\nexport class RedisVectorEmbeddingStore implements EmbeddingStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} (single source of truth) — the read +\n * write paths don't need to know the dimension explicitly because\n * {@link vectorToBytes} packs whatever length the caller passes.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisVectorEmbeddingStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisVectorEmbeddingStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:embeddings:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n /** Lookup keyed by `(nodeId, model, modelVersion, contentHash)`. */\n async get(\n nodeId: NodeId,\n model: string,\n modelVersion: string,\n contentHash: string,\n ): Promise<EmbeddingRecord | undefined> {\n await this.ensureConnected();\n if (!this.client.hGetAll) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hGetAll');\n }\n const key = this.embeddingKey(nodeId);\n const hash = await this.client.hGetAll(key);\n if (!hash || Object.keys(hash).length === 0) return undefined;\n\n const storedModel = bufferToString(hash.embeddingModel);\n const storedVersion = bufferToString(hash.embeddingVersion);\n const storedHash = bufferToString(hash.embeddingHash);\n if (storedModel !== model) return undefined;\n if (storedVersion !== modelVersion) return undefined;\n if (storedHash !== contentHash) return undefined;\n\n const vectorBytes = hash.embedding;\n if (vectorBytes === undefined) return undefined;\n return {\n nodeId,\n vector: bytesToVector(vectorBytes),\n meta: {\n model: storedModel,\n modelVersion: storedVersion,\n contentHash: storedHash,\n generatedAt: bufferToString(hash.embeddingGeneratedAt) ?? '',\n },\n };\n }\n\n /**\n * Persist an embedding as a HASH; the RediSearch index automatically picks\n * it up because the index is configured with `PREFIX 1 <keyPrefix>:embedding:`.\n */\n async set(record: EmbeddingRecord): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisVectorEmbeddingStore: client does not support hSet');\n }\n const key = this.embeddingKey(record.nodeId);\n await this.client.hSet(key, {\n nodeId: record.nodeId,\n embedding: vectorToBytes(record.vector),\n embeddingModel: record.meta.model,\n embeddingVersion: record.meta.modelVersion,\n embeddingHash: record.meta.contentHash,\n embeddingGeneratedAt: record.meta.generatedAt,\n });\n }\n\n /**\n * Linear-scan-style similarity is satisfied by delegating to\n * {@link searchVector}, which uses the vector index. Model + version scope\n * filters are intentionally honored at write time (each entry stores its\n * own metadata), so this method ignores them.\n */\n async similar(\n queryVector: Vector,\n k: number,\n _model?: string,\n _modelVersion?: string,\n ): Promise<SimilarHit[]> {\n void _model;\n void _modelVersion;\n const hits = await this.searchVector(queryVector, { top: k });\n return hits.map((h) => ({ nodeId: h.nodeId, score: h.score }));\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:embedding:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n /**\n * Vector-native top-K via RediSearch KNN.\n *\n * Issues `FT.SEARCH <idx> \"*=>[KNN $top @embedding $vec AS score]\"\n * PARAMS 4 vec <bytes> top <K> SORTBY score ASC RETURN 1 nodeId DIALECT 2`.\n * RediSearch returns the COSINE distance (lower = more similar); we convert\n * to similarity (`1 - distance`) so the contract's \"higher = more similar\"\n * holds.\n */\n async searchVector(\n queryEmbedding: Vector,\n opts: { top: number; container?: 'units' | 'inferred_edges' },\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisVectorEmbeddingStore: client does not support ft.search');\n }\n // The default index covers units; inferred-edges callers should use the\n // dedicated InferredEdgeStore. Honor the `container` option only as a\n // forward-compatibility hint — when set to 'inferred_edges' the caller\n // would normally route to RedisInferredEdgeStore.searchInferredEdges.\n void opts.container;\n\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top: opts.top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['nodeId', 'score'],\n DIALECT: 2,\n },\n );\n\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n return {\n nodeId: bufferToString(value.nodeId) ?? doc.id,\n score: similarity,\n };\n });\n }\n\n private embeddingKey(nodeId: NodeId): string {\n return `${this.keyPrefix}:embedding:${nodeId}`;\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisVectorEmbeddingStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisVectorEmbeddingStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the underlying redis\n * client internally so consumers don't import the SDK directly.\n */\nexport function redisVectorEmbeddingStore(\n config: RedisVectorEmbeddingStoreConfig,\n): EmbeddingStore {\n return new RedisVectorEmbeddingStore(config);\n}\n\n/**\n * Convert a `Vector` to a Float32Array packed into a Node `Buffer` — the\n * binary form RediSearch expects for a `VECTOR FLOAT32` field.\n */\nexport function vectorToBytes(vector: Vector): Buffer {\n const arr = new Float32Array(vector.length);\n for (let i = 0; i < vector.length; i++) arr[i] = vector[i];\n return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);\n}\n\n/**\n * Decode the binary `embedding` field of a RediSearch hash back into a\n * plain `Vector` (`number[]`). Accepts either a `Buffer` (real client) or a\n * `string` (some serializers; treated as latin1 bytes).\n */\nexport function bytesToVector(value: string | Buffer): Vector {\n const buf = typeof value === 'string' ? Buffer.from(value, 'binary') : value;\n const view = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n Math.floor(buf.byteLength / 4),\n );\n const out: number[] = new Array(view.length);\n for (let i = 0; i < view.length; i++) out[i] = view[i];\n return out;\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import type {\n InferredEdge,\n InferredEdgeSource,\n InferredEdgeStore,\n NodeId,\n SearchVectorHit,\n Vector,\n} from '@inferagraph/core/data';\nimport { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\nimport { vectorToBytes } from './redisVectorEmbeddingStore.js';\n\n/**\n * Configuration for {@link RedisInferredEdgeStore}. Provider-agnostic — the\n * vector dimensionality and key naming are constructor options with neutral\n * defaults.\n */\nexport interface RedisInferredEdgeStoreConfig {\n /** Pre-built client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. Factory builds the client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** RediSearch index name. Defaults to `<keyPrefix>:inferred_edges:idx`. */\n indexName?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Persistent {@link InferredEdgeStore} backed by a separate RediSearch index\n * over edge HASHes.\n *\n * Storage layout:\n * - `<keyPrefix>:inferred_edge:<sourceId>:<targetId>:<type>` — HASH with\n * `id`, `sourceId`, `targetId`, `type`, `score`, `sources`, `reasoning`,\n * `embedding` (optional, Float32 binary).\n * - `<keyPrefix>:inferred_edges:idx` — RediSearch index with TAG indexes on\n * `sourceId` and `targetId` plus a HNSW vector index on `embedding`.\n */\nexport class RedisInferredEdgeStore implements InferredEdgeStore {\n private readonly client: RedisLikeClient;\n private readonly keyPrefix: string;\n private readonly indexName: string;\n /**\n * Vector dimensionality. Held on the instance for symmetry with\n * {@link provisionRedisVectorIndex} — read + write paths use whatever\n * length the caller passes via {@link vectorToBytes}.\n */\n readonly dimensions: number;\n private connectPromise: Promise<void> | undefined;\n\n constructor(config: RedisInferredEdgeStoreConfig) {\n if (!config.client && !config.url) {\n throw new Error(\n 'RedisInferredEdgeStore: either `url` or `client` must be provided',\n );\n }\n this.client =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n this.indexName = config.indexName ?? `${this.keyPrefix}:inferred_edges:idx`;\n this.dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n }\n\n async get(sourceId: NodeId, targetId: NodeId): Promise<InferredEdge | undefined> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `@sourceId:{${escapeTag(sourceId)}} @targetId:{${escapeTag(targetId)}}`,\n { LIMIT: { from: 0, size: 1 }, DIALECT: 2 },\n );\n if (!reply || (reply.total ?? 0) === 0 || reply.documents.length === 0) {\n return undefined;\n }\n return docToEdge(reply.documents[0].value);\n }\n\n async getAllForNode(nodeId: NodeId): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const tag = escapeTag(nodeId);\n const reply = await this.client.ft.search(\n this.indexName,\n `(@sourceId:{${tag}}) | (@targetId:{${tag}})`,\n { LIMIT: { from: 0, size: 10_000 }, DIALECT: 2 },\n );\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n async getAll(): Promise<InferredEdge[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(this.indexName, '*', {\n LIMIT: { from: 0, size: 10_000 },\n DIALECT: 2,\n });\n return (reply?.documents ?? []).map((d) => docToEdge(d.value));\n }\n\n /**\n * Bulk-replace the entire stored set. SCAN existing edge keys, DEL them in\n * one batch, then HSET the new entries. Idempotent on `(sourceId,\n * targetId, type)` collisions because the key encodes those three values\n * (last write wins, per the contract).\n */\n async set(edges: ReadonlyArray<InferredEdge>): Promise<void> {\n await this.ensureConnected();\n if (!this.client.hSet) {\n throw new Error('RedisInferredEdgeStore: client does not support hSet');\n }\n await this.deleteAllEdgeKeys();\n for (const edge of edges) {\n const key = this.edgeKey(edge.sourceId, edge.targetId, edge.type);\n await this.client.hSet(key, edgeToHashFields(edge));\n }\n }\n\n async clear(): Promise<void> {\n await this.ensureConnected();\n await this.deleteAllEdgeKeys();\n }\n\n /**\n * Vector-native top-K against the inferred-edges index. Uses the same\n * `KNN` syntax as {@link RedisVectorEmbeddingStore.searchVector}; converts\n * the returned distance to similarity (`1 - distance`) so the contract's\n * \"higher = more similar\" holds.\n */\n async searchInferredEdges(\n queryEmbedding: Vector,\n top: number,\n ): Promise<SearchVectorHit[]> {\n await this.ensureConnected();\n if (!this.client.ft) {\n throw new Error('RedisInferredEdgeStore: client does not support ft.search');\n }\n const reply = await this.client.ft.search(\n this.indexName,\n `*=>[KNN $top @embedding $vec AS score]`,\n {\n PARAMS: {\n vec: vectorToBytes(queryEmbedding),\n top,\n },\n SORTBY: { BY: 'score', DIRECTION: 'ASC' },\n RETURN: ['id', 'score'],\n DIALECT: 2,\n },\n );\n return (reply?.documents ?? []).map((doc) => {\n const value = doc.value as Record<string, unknown>;\n const distance = parseFloat(String(value.score ?? '0'));\n const similarity = Number.isFinite(distance) ? 1 - distance : 0;\n const id = bufferToString(value.id) ?? doc.id;\n return { nodeId: id, score: similarity };\n });\n }\n\n private edgeKey(sourceId: NodeId, targetId: NodeId, type: string): string {\n return `${this.keyPrefix}:inferred_edge:${sourceId}:${targetId}:${type}`;\n }\n\n private async deleteAllEdgeKeys(): Promise<void> {\n const keys: string[] = [];\n for await (const k of this.client.scanIterator({\n MATCH: `${this.keyPrefix}:inferred_edge:*`,\n COUNT: 100,\n })) {\n keys.push(k);\n }\n if (keys.length > 0) {\n await this.client.del(keys);\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (this.client.isOpen) return;\n if (!this.connectPromise) {\n this.connectPromise = (async () => {\n try {\n await this.client.connect();\n } catch (err) {\n this.connectPromise = undefined;\n // eslint-disable-next-line no-console\n console.warn(\n `[RedisInferredEdgeStore] failed to connect: ${(err as Error).message}`,\n );\n throw err;\n }\n })();\n }\n return this.connectPromise;\n }\n}\n\n/**\n * Construct a {@link RedisInferredEdgeStore}. Accepts a pre-built `client`\n * OR a `url`; in the latter case the factory builds the redis client\n * internally.\n */\nexport function redisInferredEdgeStore(\n config: RedisInferredEdgeStoreConfig,\n): InferredEdgeStore {\n return new RedisInferredEdgeStore(config);\n}\n\nfunction edgeToHashFields(edge: InferredEdge): Record<string, string | number | Buffer> {\n const fields: Record<string, string | number | Buffer> = {\n id: edgeId(edge),\n sourceId: edge.sourceId,\n targetId: edge.targetId,\n type: edge.type,\n score: edge.score,\n sources: edge.sources.join(','),\n };\n if (edge.reasoning !== undefined) fields.reasoning = edge.reasoning;\n if (edge.perSource !== undefined) {\n fields.perSource = JSON.stringify(edge.perSource);\n }\n return fields;\n}\n\nfunction edgeId(edge: InferredEdge): string {\n return `${edge.sourceId}-${edge.targetId}-${edge.type}`;\n}\n\nfunction docToEdge(value: Record<string, unknown>): InferredEdge {\n const sources = parseSources(bufferToString(value.sources));\n const perSourceRaw = bufferToString(value.perSource);\n let perSource: InferredEdge['perSource'];\n if (perSourceRaw) {\n try {\n perSource = JSON.parse(perSourceRaw) as InferredEdge['perSource'];\n } catch {\n perSource = undefined;\n }\n }\n const reasoning = bufferToString(value.reasoning);\n return {\n sourceId: bufferToString(value.sourceId) ?? '',\n targetId: bufferToString(value.targetId) ?? '',\n type: bufferToString(value.type) ?? '',\n score: parseFloat(String(value.score ?? '0')),\n sources,\n reasoning,\n perSource,\n };\n}\n\nfunction parseSources(raw: string | undefined): InferredEdgeSource[] {\n if (!raw) return [];\n return raw\n .split(',')\n .filter((s) => s.length > 0)\n .filter(\n (s): s is InferredEdgeSource => s === 'graph' || s === 'embedding' || s === 'llm',\n );\n}\n\nfunction escapeTag(value: string): string {\n // RediSearch TAG values escape `,` `.` `<` `>` `{` `}` `[` `]` `\\\"` `'` `:` `;` `!` `@` `#` `$` `%` `^` `&` `*` `(` `)` `-` `+` `=` `~` `|` `\\` and whitespace.\n return value.replace(/([,.<>{}[\\]\"':;!@#$%^&*()\\-+=~|\\\\\\s])/g, '\\\\$1');\n}\n\nfunction bufferToString(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (value instanceof Buffer) return value.toString('utf8');\n return String(value);\n}\n","import { createClient } from 'redis';\n\nimport type { RedisLikeClient } from './types.js';\n\n/**\n * Configuration for {@link provisionRedisVectorIndex}. Provider-agnostic:\n * dimensions and key naming are constructor options. The default is to\n * provision BOTH the units (embeddings) index and the inferred-edges index;\n * pass `alsoProvisionInferredEdges: false` to skip the latter.\n */\nexport interface ProvisionRedisVectorIndexConfig {\n /** Pre-built node-redis client. One of `client` / `url` is required. */\n client?: RedisLikeClient;\n /** Redis connection URL. The function builds a client internally when supplied. */\n url?: string;\n /** Logical key namespace. Defaults to `'inferagraph'`. */\n keyPrefix?: string;\n /** Vector dimensionality. Defaults to `3072`. */\n embeddingDimensions?: number;\n /**\n * Whether to also provision the separate inferred-edges index. Defaults to\n * `true` so most consumers get both indexes from a single call.\n */\n alsoProvisionInferredEdges?: boolean;\n}\n\nconst DEFAULT_KEY_PREFIX = 'inferagraph';\nconst DEFAULT_DIMENSIONS = 3072;\n\n/**\n * Idempotent setup for the RediSearch vector indexes used by\n * {@link RedisVectorEmbeddingStore} and {@link RedisInferredEdgeStore}.\n *\n * Creates `<keyPrefix>:embeddings:idx` and (by default)\n * `<keyPrefix>:inferred_edges:idx`. If an index already exists, the function\n * catches the \"index already exists\" error and treats it as a no-op so this\n * call is safe to run on every deploy.\n *\n * Hosts call this once at deploy time before wiring the stores.\n */\nexport async function provisionRedisVectorIndex(\n config: ProvisionRedisVectorIndexConfig,\n): Promise<void> {\n if (!config.client && !config.url) {\n throw new Error(\n 'provisionRedisVectorIndex: either `url` or `client` must be provided',\n );\n }\n const client: RedisLikeClient =\n config.client ?? (createClient({ url: config.url! }) as unknown as RedisLikeClient);\n const keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;\n const dimensions = config.embeddingDimensions ?? DEFAULT_DIMENSIONS;\n const alsoEdges = config.alsoProvisionInferredEdges ?? true;\n\n if (!client.isOpen) {\n await client.connect();\n }\n if (!client.ft) {\n throw new Error('provisionRedisVectorIndex: client does not support ft.create');\n }\n\n await safeCreateIndex(client, `${keyPrefix}:embeddings:idx`, {\n nodeId: { type: 'TAG' },\n embeddingHash: { type: 'TAG' },\n embeddingModel: { type: 'TAG' },\n embeddingVersion: { type: 'TAG' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:embedding:` },\n });\n\n if (alsoEdges) {\n await safeCreateIndex(client, `${keyPrefix}:inferred_edges:idx`, {\n id: { type: 'TAG' },\n sourceId: { type: 'TAG' },\n targetId: { type: 'TAG' },\n type: { type: 'TAG' },\n score: { type: 'NUMERIC' },\n embedding: {\n type: 'VECTOR',\n ALGORITHM: 'HNSW',\n TYPE: 'FLOAT32',\n DIM: dimensions,\n DISTANCE_METRIC: 'COSINE',\n },\n }, {\n ON: 'HASH',\n PREFIX: { count: 1, prefix: `${keyPrefix}:inferred_edge:` },\n });\n }\n}\n\nasync function safeCreateIndex(\n client: RedisLikeClient,\n indexName: string,\n schema: Record<string, unknown>,\n options: Record<string, unknown>,\n): Promise<void> {\n try {\n await client.ft!.create(indexName, schema, options);\n } catch (err) {\n if (isIndexAlreadyExistsError(err)) return;\n throw err;\n }\n}\n\nfunction isIndexAlreadyExistsError(err: unknown): boolean {\n if (!err) return false;\n const msg =\n err instanceof Error\n ? err.message\n : typeof err === 'string'\n ? err\n : String((err as { message?: unknown })?.message ?? err);\n // RediSearch returns \"Index already exists\" (case varies by version).\n return /index\\s+already\\s+exists/i.test(msg);\n}\n"],"mappings":"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","names":["createClient","createClient","createClient","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS","createClient","bufferToString","createClient","DEFAULT_KEY_PREFIX","DEFAULT_DIMENSIONS"]} |
+2
-2
| { | ||
| "name": "@inferagraph/redis", | ||
| "version": "0.3.0", | ||
| "version": "0.3.1", | ||
| "description": "Redis-backed cache, conversation, embedding, and inferred-edge stores for @inferagraph/core.", | ||
@@ -56,3 +56,3 @@ "repository": { | ||
| "devDependencies": { | ||
| "@inferagraph/core": "link:../inferagraph", | ||
| "@inferagraph/core": "^0.9.1", | ||
| "@types/node": "^25.6.0", | ||
@@ -59,0 +59,0 @@ "@vitest/coverage-v8": "^3.1.1", |
199210
0.04%