@langchain/langgraph-checkpoint-redis 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":["uuidv4"],"sources":["../src/store.ts"],"sourcesContent":["import { createClient, createCluster } from \"redis\";\n\n/** A conventional Redis connection. */\nexport type RedisClientConnection = ReturnType<typeof createClient>;\n\n/** A clustered Redis connection. */\nexport type RedisClusterConnection = ReturnType<typeof createCluster>;\n\n/** A Redis connection, clustered or conventional. */\nexport type RedisConnection = RedisClientConnection | RedisClusterConnection;\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n type GetOperation,\n InvalidNamespaceError,\n type ListNamespacesOperation,\n type Operation,\n type PutOperation,\n type SearchOperation,\n} from \"@langchain/langgraph-checkpoint\";\n\nimport { escapeRediSearchTagValue } from \"./utils.js\";\n\n// Type guard functions for operations\nexport function isPutOperation(op: Operation): op is PutOperation {\n return \"value\" in op && \"namespace\" in op && \"key\" in op;\n}\n\nexport function isGetOperation(op: Operation): op is GetOperation {\n return (\n \"namespace\" in op &&\n \"key\" in op &&\n !(\"value\" in op) &&\n !(\"namespacePrefix\" in op) &&\n !(\"matchConditions\" in op)\n );\n}\n\nexport function isSearchOperation(op: Operation): op is SearchOperation {\n return \"namespacePrefix\" in op;\n}\n\nexport function isListNamespacesOperation(\n op: Operation\n): op is ListNamespacesOperation {\n return \"matchConditions\" in op;\n}\n\n// Filter types for advanced search operations\nexport interface FilterOperators {\n $eq?: any;\n $ne?: any;\n $gt?: number;\n $gte?: number;\n $lt?: number;\n $lte?: number;\n $in?: any[];\n $nin?: any[];\n $exists?: boolean;\n}\n\nexport type FilterValue = any | FilterOperators;\nexport type Filter = Record<string, FilterValue>;\n\n/**\n * Internal class for evaluating filters against documents.\n * Supports MongoDB-style query operators.\n */\nclass FilterBuilder {\n /**\n * Evaluates if a document matches the given filter criteria.\n * Supports advanced operators like $gt, $lt, $in, etc.\n */\n static matchesFilter(doc: Record<string, any>, filter: Filter): boolean {\n for (const [key, filterValue] of Object.entries(filter)) {\n if (!this.matchesFieldFilter(doc, key, filterValue)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Builds a Redis Search query string from filter criteria.\n * Note: This is limited by RediSearch capabilities and may not support all operators.\n */\n static buildRedisSearchQuery(\n filter: Filter,\n prefix?: string\n ): { query: string; useClientFilter: boolean } {\n let queryParts: string[] = [];\n let useClientFilter = false;\n\n // Add prefix filter if provided\n if (prefix) {\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n if (tokens.length > 0) {\n queryParts.push(`@prefix:(${tokens.join(\" \")})`);\n }\n }\n\n // Check if we have complex operators that require client-side filtering\n for (const [_key, value] of Object.entries(filter)) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.keys(value).some((k) => k.startsWith(\"$\"))\n ) {\n // Complex operators require client-side filtering\n useClientFilter = true;\n break;\n }\n }\n\n // If no prefix, at least search all documents\n if (queryParts.length === 0) {\n queryParts.push(\"*\");\n }\n\n return {\n query: queryParts.join(\" \"),\n useClientFilter,\n };\n }\n\n private static matchesFieldFilter(\n doc: Record<string, any>,\n key: string,\n filterValue: FilterValue\n ): boolean {\n // Handle nested keys (e.g., \"user.name\")\n const actualValue = this.getNestedValue(doc, key);\n\n // Check if it's an operator object\n if (\n typeof filterValue === \"object\" &&\n filterValue !== null &&\n !Array.isArray(filterValue) &&\n Object.keys(filterValue).some((k) => k.startsWith(\"$\"))\n ) {\n // Handle operator object\n return this.matchesOperators(actualValue, filterValue as FilterOperators);\n } else {\n // Simple equality check\n return this.isEqual(actualValue, filterValue);\n }\n }\n\n private static matchesOperators(\n actualValue: any,\n operators: FilterOperators\n ): boolean {\n for (const [operator, operatorValue] of Object.entries(operators)) {\n if (!this.matchesOperator(actualValue, operator, operatorValue)) {\n return false;\n }\n }\n return true;\n }\n\n private static matchesOperator(\n actualValue: any,\n operator: string,\n operatorValue: any\n ): boolean {\n switch (operator) {\n case \"$eq\":\n return this.isEqual(actualValue, operatorValue);\n\n case \"$ne\":\n return !this.isEqual(actualValue, operatorValue);\n\n case \"$gt\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) > Number(operatorValue)\n );\n\n case \"$gte\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) >= Number(operatorValue)\n );\n\n case \"$lt\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) < Number(operatorValue)\n );\n\n case \"$lte\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) <= Number(operatorValue)\n );\n\n case \"$in\":\n if (!Array.isArray(operatorValue)) return false;\n return operatorValue.some((val) => this.isEqual(actualValue, val));\n\n case \"$nin\":\n if (!Array.isArray(operatorValue)) return false;\n return !operatorValue.some((val) => this.isEqual(actualValue, val));\n\n case \"$exists\": {\n const exists = actualValue !== undefined;\n return operatorValue ? exists : !exists;\n }\n\n default:\n // Unknown operator, return false for safety\n return false;\n }\n }\n\n private static isEqual(a: any, b: any): boolean {\n // Handle null and undefined\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (a === undefined || b === undefined) return false;\n\n // Handle arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((val, idx) => this.isEqual(val, b[idx]));\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n // Check if non-array value exists in array\n const arr = Array.isArray(a) ? a : b;\n const val = Array.isArray(a) ? b : a;\n return arr.includes(val);\n }\n\n // Handle objects\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => this.isEqual(a[key], b[key]));\n }\n\n // Primitive comparison (with type coercion for numbers)\n return a == b;\n }\n\n private static getNestedValue(obj: any, path: string): any {\n const keys = path.split(\".\");\n let current = obj;\n\n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n current = current[key];\n }\n\n return current;\n }\n}\n\nexport interface Item {\n value: any;\n key: string;\n namespace: string[];\n created_at: Date;\n updated_at: Date;\n}\n\nexport interface SearchItem extends Item {\n score?: number;\n}\n\ninterface StoreDocument {\n key: string;\n prefix: string;\n value: any;\n created_at: number;\n updated_at: number;\n}\n\ninterface VectorDocument {\n prefix: string;\n key: string;\n field_name: string;\n embedding: number[];\n created_at: number;\n updated_at: number;\n}\n\nexport interface IndexConfig {\n dims: number;\n embed?: any;\n distanceType?: \"cosine\" | \"l2\" | \"ip\"; // cosine, L2 (Euclidean), inner product\n fields?: string[];\n vectorStorageType?: string;\n similarityThreshold?: number; // Minimum similarity score for results\n}\n\nexport interface TTLConfig {\n defaultTTL?: number;\n refreshOnRead?: boolean;\n}\n\nexport interface StoreConfig {\n index?: IndexConfig;\n ttl?: TTLConfig;\n}\n\nconst REDIS_KEY_SEPARATOR = \":\";\nconst STORE_PREFIX = \"store\";\nconst STORE_VECTOR_PREFIX = \"store_vectors\";\n\nconst SCHEMAS = [\n {\n index: \"store\",\n prefix: STORE_PREFIX + REDIS_KEY_SEPARATOR,\n schema: {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n },\n },\n {\n index: \"store_vectors\",\n prefix: STORE_VECTOR_PREFIX + REDIS_KEY_SEPARATOR,\n schema: {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.field_name\": { type: \"TAG\", AS: \"field_name\" },\n \"$.embedding\": { type: \"VECTOR\", AS: \"embedding\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n },\n },\n];\n\nexport class RedisStore {\n private readonly client: RedisConnection;\n private readonly indexConfig?: IndexConfig;\n private readonly ttlConfig?: TTLConfig;\n private readonly embeddings?: any;\n\n constructor(client: RedisConnection, config?: StoreConfig) {\n this.client = client;\n this.indexConfig = config?.index;\n this.ttlConfig = config?.ttl;\n\n if (this.indexConfig?.embed) {\n this.embeddings = this.indexConfig.embed;\n }\n }\n\n static async fromConnString(\n connString: string,\n config?: StoreConfig\n ): Promise<RedisStore> {\n const client = createClient({ url: connString });\n await client.connect();\n const store = new RedisStore(client, config);\n await store.setup();\n return store;\n }\n\n static async fromCluster(\n rootNodes: Array<{ url: string }>,\n config?: StoreConfig\n ): Promise<RedisStore> {\n const client = createCluster({ rootNodes });\n await client.connect();\n const store = new RedisStore(client, config);\n await store.setup();\n return store;\n }\n\n async setup(): Promise<void> {\n // Create store index\n try {\n await this.client.ft.create(SCHEMAS[0].index, SCHEMAS[0].schema as any, {\n ON: \"JSON\",\n PREFIX: SCHEMAS[0].prefix,\n });\n } catch (error: any) {\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\"Failed to create store index:\", error.message);\n }\n }\n\n // Create vector index if configured\n if (this.indexConfig) {\n const dims = this.indexConfig.dims;\n const distanceMetric =\n this.indexConfig.distanceType === \"cosine\"\n ? \"COSINE\"\n : this.indexConfig.distanceType === \"l2\"\n ? \"L2\"\n : this.indexConfig.distanceType === \"ip\"\n ? \"IP\"\n : \"COSINE\";\n\n // Build schema with correct vector syntax\n const vectorSchema: Record<string, any> = {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.field_name\": { type: \"TAG\", AS: \"field_name\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n };\n\n // Add vector field with correct syntax\n vectorSchema[\"$.embedding\"] = {\n type: \"VECTOR\",\n ALGORITHM: \"FLAT\",\n TYPE: \"FLOAT32\",\n DIM: dims,\n DISTANCE_METRIC: distanceMetric,\n AS: \"embedding\",\n };\n\n try {\n await this.client.ft.create(SCHEMAS[1].index, vectorSchema as any, {\n ON: \"JSON\",\n PREFIX: SCHEMAS[1].prefix,\n });\n } catch (error: any) {\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\"Failed to create vector index:\", error.message);\n }\n }\n }\n }\n\n async get(\n namespace: string[],\n key: string,\n options?: { refreshTTL?: boolean }\n ): Promise<Item | null> {\n const prefix = namespace.join(\".\");\n // For TEXT fields, we need to match all tokens (split by dots and hyphens)\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n const prefixQuery =\n tokens.length > 0 ? `@prefix:(${tokens.join(\" \")})` : \"*\";\n\n // For TAG fields in curly braces, escape special characters\n // Handle empty string as a special case\n let query: string;\n if (key === \"\") {\n // For empty keys, search by prefix and filter results\n query = prefixQuery;\n } else {\n const escapedKey = this.escapeTagValue(key);\n query = `(${prefixQuery}) (@key:{${escapedKey}})`;\n }\n\n try {\n const results = await this.client.ft.search(\"store\", query, {\n LIMIT: { from: 0, size: key === \"\" ? 100 : 1 },\n });\n\n if (!results || !results.documents || results.documents.length === 0) {\n return null;\n }\n\n // For empty key, filter to find the exact match\n if (key === \"\") {\n for (const doc of results.documents) {\n const jsonDoc = doc.value as unknown as StoreDocument;\n if (jsonDoc.key === \"\" && jsonDoc.prefix === prefix) {\n const docId = doc.id;\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(docId);\n }\n\n return {\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n };\n }\n }\n return null;\n }\n\n const doc = results.documents[0];\n const jsonDoc = doc.value as unknown as StoreDocument;\n const docId = doc.id;\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(docId);\n }\n\n return {\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n };\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return null;\n }\n throw error;\n }\n }\n\n async put(\n namespace: string[],\n key: string,\n value: any,\n options?: { ttl?: number; index?: boolean | string[] }\n ): Promise<void> {\n // Validate namespace for put operations\n this.validateNamespace(namespace);\n const prefix = namespace.join(\".\");\n const docId = uuidv4();\n // Use high-resolution time for better timestamp precision\n const now = Date.now() * 1000000 + Math.floor(performance.now() * 1000); // Microseconds + nanoseconds component\n let createdAt = now; // Will be overridden if document exists\n\n // Delete existing document if it exists\n // For TEXT fields, we need to match all tokens (split by dots and hyphens)\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n const prefixQuery =\n tokens.length > 0 ? `@prefix:(${tokens.join(\" \")})` : \"*\";\n\n // For TAG fields in curly braces, escape special characters\n const escapedKey = this.escapeTagValue(key);\n const existingQuery = `(${prefixQuery}) (@key:{${escapedKey}})`;\n try {\n const existing = await this.client.ft.search(\"store\", existingQuery, {\n LIMIT: { from: 0, size: 1 },\n });\n\n if (existing && existing.documents && existing.documents.length > 0) {\n const oldDocId = existing.documents[0].id;\n // Preserve the original created_at timestamp\n const existingDoc = await this.client.json.get(oldDocId);\n if (\n existingDoc &&\n typeof existingDoc === \"object\" &&\n \"created_at\" in existingDoc\n ) {\n createdAt = (existingDoc as any).created_at;\n }\n await this.client.del(oldDocId);\n\n // Also delete associated vector if it exists\n if (this.indexConfig) {\n const oldUuid = oldDocId.split(\":\").pop();\n const oldVectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${oldUuid}`;\n try {\n await this.client.del(oldVectorKey);\n } catch (error) {\n // Vector might not exist\n }\n }\n }\n } catch (error) {\n // Index might not exist yet\n }\n\n // Handle delete operation\n if (value === null) {\n return;\n }\n\n // Store the document\n const storeKey = `${STORE_PREFIX}${REDIS_KEY_SEPARATOR}${docId}`;\n const doc = {\n prefix,\n key,\n value,\n created_at: createdAt,\n updated_at: now,\n };\n\n await this.client.json.set(storeKey, \"$\", doc);\n\n // Handle embeddings if configured\n if (this.indexConfig && this.embeddings && options?.index !== false) {\n const fieldsToIndex =\n options && Array.isArray(options.index)\n ? options.index\n : this.indexConfig.fields || [\"text\"];\n const textsToEmbed = [];\n const fieldNames = [];\n\n for (const field of fieldsToIndex) {\n if (value[field]) {\n textsToEmbed.push(value[field]);\n fieldNames.push(field);\n }\n }\n\n if (textsToEmbed.length > 0) {\n const embeddings = await this.embeddings.embedDocuments(textsToEmbed);\n\n for (let i = 0; i < embeddings.length; i++) {\n const vectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${docId}`;\n const vectorDoc: VectorDocument = {\n prefix,\n key,\n field_name: fieldNames[i],\n embedding: embeddings[i],\n created_at: now,\n updated_at: now,\n };\n\n await this.client.json.set(vectorKey, \"$\", vectorDoc as any);\n\n // Apply TTL to vector key if configured\n const ttlMinutes = options?.ttl || this.ttlConfig?.defaultTTL;\n if (ttlMinutes) {\n const ttlSeconds = Math.floor(ttlMinutes * 60);\n await this.client.expire(vectorKey, ttlSeconds);\n }\n }\n }\n }\n\n // Apply TTL if configured\n const ttlMinutes = options?.ttl || this.ttlConfig?.defaultTTL;\n if (ttlMinutes) {\n const ttlSeconds = Math.floor(ttlMinutes * 60);\n await this.client.expire(storeKey, ttlSeconds);\n }\n }\n\n async delete(namespace: string[], key: string): Promise<void> {\n await this.put(namespace, key, null);\n }\n\n async search(\n namespacePrefix: string[],\n options?: {\n filter?: Filter;\n query?: string;\n limit?: number;\n offset?: number;\n refreshTTL?: boolean;\n similarityThreshold?: number;\n }\n ): Promise<SearchItem[]> {\n const prefix = namespacePrefix.join(\".\");\n const limit = options?.limit || 10;\n const offset = options?.offset || 0;\n\n // Handle vector search if query is provided\n if (options?.query && this.indexConfig && this.embeddings) {\n const [embedding] = await this.embeddings.embedDocuments([options.query]);\n\n // Build KNN query\n // For prefix search, use wildcard since we want to match any document starting with this prefix\n let queryStr = prefix ? `@prefix:${prefix.split(/[.-]/)[0]}*` : \"*\";\n const vectorBytes = Buffer.from(new Float32Array(embedding).buffer);\n\n try {\n // Use KNN query with proper syntax\n const results = await this.client.ft.search(\n \"store_vectors\",\n `(${queryStr})=>[KNN ${limit} @embedding $BLOB]`,\n {\n PARAMS: {\n BLOB: vectorBytes,\n },\n DIALECT: 2,\n LIMIT: { from: offset, size: limit },\n RETURN: [\"prefix\", \"key\", \"__embedding_score\"],\n }\n );\n\n // Get matching store documents\n const items: SearchItem[] = [];\n for (const doc of results.documents) {\n const docUuid = doc.id.split(\":\").pop();\n const storeKey = `${STORE_PREFIX}${REDIS_KEY_SEPARATOR}${docUuid}`;\n\n const storeDoc = (await this.client.json.get(\n storeKey\n )) as StoreDocument | null;\n if (storeDoc) {\n // Apply advanced filter if provided\n if (options.filter) {\n if (\n !FilterBuilder.matchesFilter(\n storeDoc.value || {},\n options.filter\n )\n ) {\n continue;\n }\n }\n\n // Refresh TTL if requested\n if (options.refreshTTL) {\n await this.refreshItemTTL(storeKey);\n await this.refreshItemTTL(doc.id);\n }\n\n const score = (doc.value as any)?.__embedding_score\n ? this.calculateSimilarityScore(\n parseFloat((doc.value as any).__embedding_score as string)\n )\n : 0;\n\n // Apply similarity threshold if specified\n const threshold =\n options.similarityThreshold ??\n this.indexConfig?.similarityThreshold;\n if (threshold !== undefined && score < threshold) {\n continue;\n }\n\n items.push({\n value: storeDoc.value,\n key: storeDoc.key,\n namespace: storeDoc.prefix.split(\".\"),\n created_at: new Date(storeDoc.created_at / 1000000),\n updated_at: new Date(storeDoc.updated_at / 1000000),\n score,\n });\n }\n }\n\n return items;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n // Regular search without vectors\n let queryStr = \"*\";\n if (prefix) {\n // For prefix search, we need to match all tokens from the namespace prefix\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n if (tokens.length > 0) {\n // Match all tokens to ensure we get the right prefix\n queryStr = `@prefix:(${tokens.join(\" \")})`;\n }\n }\n\n try {\n const results = await this.client.ft.search(\"store\", queryStr, {\n LIMIT: { from: offset, size: limit },\n SORTBY: { BY: \"created_at\", DIRECTION: \"DESC\" },\n });\n\n const items: SearchItem[] = [];\n for (const doc of results.documents) {\n const jsonDoc = doc.value as unknown as StoreDocument;\n\n // Apply advanced filter\n if (options?.filter) {\n if (\n !FilterBuilder.matchesFilter(jsonDoc.value || {}, options.filter)\n ) {\n continue;\n }\n }\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(doc.id);\n }\n\n items.push({\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n });\n }\n\n return items;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n async listNamespaces(options?: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n }): Promise<string[][]> {\n let query = \"*\";\n\n try {\n const results = await this.client.ft.search(\"store\", query, {\n LIMIT: { from: 0, size: 1000 }, // Get many to deduplicate\n RETURN: [\"prefix\"],\n });\n\n // Extract unique namespaces and filter\n const namespaceSet = new Set<string>();\n for (const doc of results.documents) {\n const prefix = (doc.value as unknown as StoreDocument).prefix;\n const parts = prefix.split(\".\");\n\n // Apply prefix filter if specified\n if (options?.prefix) {\n // Check if this namespace starts with the specified prefix\n if (parts.length < options.prefix.length) continue;\n\n let matches = true;\n for (let i = 0; i < options.prefix.length; i++) {\n if (parts[i] !== options.prefix[i]) {\n matches = false;\n break;\n }\n }\n if (!matches) continue;\n }\n\n // Apply suffix filter if specified\n if (options?.suffix) {\n // Check if this namespace ends with the specified suffix\n if (parts.length < options.suffix.length) continue;\n\n let matches = true;\n const startIdx = parts.length - options.suffix.length;\n for (let i = 0; i < options.suffix.length; i++) {\n if (parts[startIdx + i] !== options.suffix[i]) {\n matches = false;\n break;\n }\n }\n if (!matches) continue;\n }\n\n // Apply max depth\n if (options?.maxDepth) {\n const truncated = parts.slice(0, options.maxDepth);\n namespaceSet.add(truncated.join(\".\"));\n } else {\n namespaceSet.add(prefix);\n }\n }\n\n // Convert to array of arrays and sort\n let namespaces = Array.from(namespaceSet)\n .map((ns) => ns.split(\".\"))\n .sort((a, b) => a.join(\".\").localeCompare(b.join(\".\")));\n\n // Apply pagination\n if (options?.offset || options?.limit) {\n const offset = options.offset || 0;\n const limit = options.limit || 10;\n namespaces = namespaces.slice(offset, offset + limit);\n }\n\n return namespaces;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n async batch(ops: Operation[]): Promise<any[]> {\n const results: any[] = new Array(ops.length).fill(null);\n\n // Process operations in order to maintain dependencies\n for (let idx = 0; idx < ops.length; idx++) {\n const op = ops[idx];\n\n // Execute operation based on type guards\n if (isPutOperation(op)) {\n // TypeScript now knows op is PutOperation\n await this.put(op.namespace, op.key, op.value);\n results[idx] = null;\n } else if (isSearchOperation(op)) {\n // TypeScript now knows op is SearchOperation\n results[idx] = await this.search(op.namespacePrefix, {\n filter: op.filter,\n query: op.query,\n limit: op.limit,\n offset: op.offset,\n });\n } else if (isListNamespacesOperation(op)) {\n // TypeScript now knows op is ListNamespacesOperation\n let prefix: string[] | undefined = undefined;\n let suffix: string[] | undefined = undefined;\n\n if (op.matchConditions) {\n for (const condition of op.matchConditions) {\n if (condition.matchType === \"prefix\") {\n prefix = condition.path;\n } else if (condition.matchType === \"suffix\") {\n suffix = condition.path;\n }\n }\n }\n\n results[idx] = await this.listNamespaces({\n prefix,\n suffix,\n maxDepth: op.maxDepth,\n limit: op.limit,\n offset: op.offset,\n });\n } else if (isGetOperation(op)) {\n // TypeScript now knows op is GetOperation\n results[idx] = await this.get(op.namespace, op.key);\n } else {\n // This should never happen with proper Operation type\n throw new Error(`Unknown operation type: ${JSON.stringify(op)}`);\n }\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n await this.client.quit();\n }\n\n /**\n * Get statistics about the store.\n * Returns document counts and other metrics.\n */\n async getStatistics(): Promise<{\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n }> {\n const stats: {\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n } = {\n totalDocuments: 0,\n namespaceCount: 0,\n };\n\n try {\n // Get total document count\n const countResult = await this.client.ft.search(\"store\", \"*\", {\n LIMIT: { from: 0, size: 0 },\n });\n stats.totalDocuments = countResult.total || 0;\n\n // Get unique namespace count\n const namespaces = await this.listNamespaces({ limit: 1000 });\n stats.namespaceCount = namespaces.length;\n\n // Get vector document count if index is configured\n if (this.indexConfig) {\n try {\n const vectorResult = await this.client.ft.search(\n \"store_vectors\",\n \"*\",\n {\n LIMIT: { from: 0, size: 0 },\n }\n );\n stats.vectorDocuments = vectorResult.total || 0;\n } catch (error) {\n // Vector index might not exist\n stats.vectorDocuments = 0;\n }\n\n // Get index info\n try {\n stats.indexInfo = await this.client.ft.info(\"store\");\n } catch (error) {\n // Index info might not be available\n }\n }\n } catch (error: any) {\n if (!error.message?.includes(\"no such index\")) {\n throw error;\n }\n }\n\n return stats;\n }\n\n private validateNamespace(namespace: string[]): void {\n if (namespace.length === 0) {\n throw new InvalidNamespaceError(\"Namespace cannot be empty.\");\n }\n for (const label of namespace) {\n // Runtime check for JavaScript users (TypeScript already ensures this)\n // This check is for runtime safety when called from JavaScript\n // noinspection SuspiciousTypeOfGuard\n if (typeof label !== \"string\") {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${String(\n label\n )}' found in ${namespace}. Namespace labels must be strings.`\n );\n }\n if (label.includes(\".\")) {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`\n );\n }\n if (label === \"\") {\n throw new InvalidNamespaceError(\n `Namespace labels cannot be empty strings. Got ${label} in ${namespace}`\n );\n }\n }\n if (namespace[0] === \"langgraph\") {\n throw new InvalidNamespaceError(\n `Root label for namespace cannot be \"langgraph\". Got: ${namespace}`\n );\n }\n }\n\n private async refreshItemTTL(docId: string): Promise<void> {\n if (this.ttlConfig?.defaultTTL) {\n const ttlSeconds = Math.floor(this.ttlConfig.defaultTTL * 60);\n await this.client.expire(docId, ttlSeconds);\n\n // Also refresh vector key if it exists\n const docUuid = docId.split(\":\").pop();\n const vectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${docUuid}`;\n try {\n await this.client.expire(vectorKey, ttlSeconds);\n } catch (error) {\n // Vector key might not exist\n }\n }\n }\n\n private escapeTagValue(value: string): string {\n // Delegate to shared utility for RediSearch TAG field escaping\n return escapeRediSearchTagValue(value);\n }\n\n /**\n * Calculate similarity score based on the distance metric.\n * Converts raw distance to a normalized similarity score [0,1].\n */\n private calculateSimilarityScore(distance: number): number {\n const metric = this.indexConfig?.distanceType || \"cosine\";\n\n switch (metric) {\n case \"cosine\":\n // Cosine distance is in range [0,2], convert to similarity [0,1]\n return Math.max(0, 1 - distance / 2);\n\n case \"l2\":\n // L2 (Euclidean) distance, use exponential decay\n // Similarity = e^(-distance)\n return Math.exp(-distance);\n\n case \"ip\":\n // Inner product can be negative, use sigmoid function\n // Similarity = 1 / (1 + e^(-distance))\n return 1 / (1 + Math.exp(-distance));\n\n default:\n // Default to cosine similarity\n return Math.max(0, 1 - distance / 2);\n }\n }\n}\n\n// Export FilterBuilder for testing purposes\nexport { FilterBuilder };\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":["uuidv4"],"sources":["../src/store.ts"],"sourcesContent":["import { createClient, createCluster } from \"redis\";\n\n/** A conventional Redis connection. */\nexport type RedisClientConnection = ReturnType<typeof createClient>;\n\n/** A clustered Redis connection. */\nexport type RedisClusterConnection = ReturnType<typeof createCluster>;\n\n/** A Redis connection, clustered or conventional. */\nexport type RedisConnection = RedisClientConnection | RedisClusterConnection;\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n type GetOperation,\n InvalidNamespaceError,\n type ListNamespacesOperation,\n type Operation,\n type PutOperation,\n type SearchOperation,\n} from \"@langchain/langgraph-checkpoint\";\n\nimport { escapeRediSearchTagValue } from \"./utils.js\";\n\n// Type guard functions for operations\nexport function isPutOperation(op: Operation): op is PutOperation {\n return \"value\" in op && \"namespace\" in op && \"key\" in op;\n}\n\nexport function isGetOperation(op: Operation): op is GetOperation {\n return (\n \"namespace\" in op &&\n \"key\" in op &&\n !(\"value\" in op) &&\n !(\"namespacePrefix\" in op) &&\n !(\"matchConditions\" in op)\n );\n}\n\nexport function isSearchOperation(op: Operation): op is SearchOperation {\n return \"namespacePrefix\" in op;\n}\n\nexport function isListNamespacesOperation(\n op: Operation\n): op is ListNamespacesOperation {\n return \"matchConditions\" in op;\n}\n\n// Filter types for advanced search operations\nexport interface FilterOperators {\n $eq?: any;\n $ne?: any;\n $gt?: number;\n $gte?: number;\n $lt?: number;\n $lte?: number;\n $in?: any[];\n $nin?: any[];\n $exists?: boolean;\n}\n\nexport type FilterValue = any | FilterOperators;\nexport type Filter = Record<string, FilterValue>;\n\n/**\n * Internal class for evaluating filters against documents.\n * Supports MongoDB-style query operators.\n */\nclass FilterBuilder {\n /**\n * Evaluates if a document matches the given filter criteria.\n * Supports advanced operators like $gt, $lt, $in, etc.\n */\n static matchesFilter(doc: Record<string, any>, filter: Filter): boolean {\n for (const [key, filterValue] of Object.entries(filter)) {\n if (!this.matchesFieldFilter(doc, key, filterValue)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Builds a Redis Search query string from filter criteria.\n * Note: This is limited by RediSearch capabilities and may not support all operators.\n */\n static buildRedisSearchQuery(\n filter: Filter,\n prefix?: string\n ): { query: string; useClientFilter: boolean } {\n let queryParts: string[] = [];\n let useClientFilter = false;\n\n // Add prefix filter if provided\n if (prefix) {\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n if (tokens.length > 0) {\n queryParts.push(`@prefix:(${tokens.join(\" \")})`);\n }\n }\n\n // Check if we have complex operators that require client-side filtering\n for (const [_key, value] of Object.entries(filter)) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.keys(value).some((k) => k.startsWith(\"$\"))\n ) {\n // Complex operators require client-side filtering\n useClientFilter = true;\n break;\n }\n }\n\n // If no prefix, at least search all documents\n if (queryParts.length === 0) {\n queryParts.push(\"*\");\n }\n\n return {\n query: queryParts.join(\" \"),\n useClientFilter,\n };\n }\n\n private static matchesFieldFilter(\n doc: Record<string, any>,\n key: string,\n filterValue: FilterValue\n ): boolean {\n // Handle nested keys (e.g., \"user.name\")\n const actualValue = this.getNestedValue(doc, key);\n\n // Check if it's an operator object\n if (\n typeof filterValue === \"object\" &&\n filterValue !== null &&\n !Array.isArray(filterValue) &&\n Object.keys(filterValue).some((k) => k.startsWith(\"$\"))\n ) {\n // Handle operator object\n return this.matchesOperators(actualValue, filterValue as FilterOperators);\n } else {\n // Simple equality check\n return this.isEqual(actualValue, filterValue);\n }\n }\n\n private static matchesOperators(\n actualValue: any,\n operators: FilterOperators\n ): boolean {\n for (const [operator, operatorValue] of Object.entries(operators)) {\n if (!this.matchesOperator(actualValue, operator, operatorValue)) {\n return false;\n }\n }\n return true;\n }\n\n private static matchesOperator(\n actualValue: any,\n operator: string,\n operatorValue: any\n ): boolean {\n switch (operator) {\n case \"$eq\":\n return this.isEqual(actualValue, operatorValue);\n\n case \"$ne\":\n return !this.isEqual(actualValue, operatorValue);\n\n case \"$gt\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) > Number(operatorValue)\n );\n\n case \"$gte\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) >= Number(operatorValue)\n );\n\n case \"$lt\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) < Number(operatorValue)\n );\n\n case \"$lte\":\n return (\n actualValue !== undefined &&\n actualValue !== null &&\n Number(actualValue) <= Number(operatorValue)\n );\n\n case \"$in\":\n if (!Array.isArray(operatorValue)) return false;\n return operatorValue.some((val) => this.isEqual(actualValue, val));\n\n case \"$nin\":\n if (!Array.isArray(operatorValue)) return false;\n return !operatorValue.some((val) => this.isEqual(actualValue, val));\n\n case \"$exists\": {\n const exists = actualValue !== undefined;\n return operatorValue ? exists : !exists;\n }\n\n default:\n // Unknown operator, return false for safety\n return false;\n }\n }\n\n private static isEqual(a: any, b: any): boolean {\n // Handle null and undefined\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (a === undefined || b === undefined) return false;\n\n // Handle arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((val, idx) => this.isEqual(val, b[idx]));\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n // Check if non-array value exists in array\n const arr = Array.isArray(a) ? a : b;\n const val = Array.isArray(a) ? b : a;\n return arr.includes(val);\n }\n\n // Handle objects\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => this.isEqual(a[key], b[key]));\n }\n\n // Primitive comparison (with type coercion for numbers)\n return a == b;\n }\n\n private static getNestedValue(obj: any, path: string): any {\n const keys = path.split(\".\");\n let current = obj;\n\n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n current = current[key];\n }\n\n return current;\n }\n}\n\nexport interface Item {\n value: any;\n key: string;\n namespace: string[];\n created_at: Date;\n updated_at: Date;\n}\n\nexport interface SearchItem extends Item {\n score?: number;\n}\n\ninterface StoreDocument {\n key: string;\n prefix: string;\n value: any;\n created_at: number;\n updated_at: number;\n}\n\ninterface VectorDocument {\n prefix: string;\n key: string;\n field_name: string;\n embedding: number[];\n created_at: number;\n updated_at: number;\n}\n\nexport interface IndexConfig {\n dims: number;\n embed?: any;\n distanceType?: \"cosine\" | \"l2\" | \"ip\"; // cosine, L2 (Euclidean), inner product\n fields?: string[];\n vectorStorageType?: string;\n similarityThreshold?: number; // Minimum similarity score for results\n}\n\nexport interface TTLConfig {\n defaultTTL?: number;\n refreshOnRead?: boolean;\n}\n\nexport interface StoreConfig {\n index?: IndexConfig;\n ttl?: TTLConfig;\n}\n\nconst REDIS_KEY_SEPARATOR = \":\";\nconst STORE_PREFIX = \"store\";\nconst STORE_VECTOR_PREFIX = \"store_vectors\";\n\nconst SCHEMAS = [\n {\n index: \"store\",\n prefix: STORE_PREFIX + REDIS_KEY_SEPARATOR,\n schema: {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n },\n },\n {\n index: \"store_vectors\",\n prefix: STORE_VECTOR_PREFIX + REDIS_KEY_SEPARATOR,\n schema: {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.field_name\": { type: \"TAG\", AS: \"field_name\" },\n \"$.embedding\": { type: \"VECTOR\", AS: \"embedding\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n },\n },\n];\n\nexport class RedisStore {\n private readonly client: RedisConnection;\n private readonly indexConfig?: IndexConfig;\n private readonly ttlConfig?: TTLConfig;\n private readonly embeddings?: any;\n\n constructor(client: RedisConnection, config?: StoreConfig) {\n this.client = client;\n this.indexConfig = config?.index;\n this.ttlConfig = config?.ttl;\n\n if (this.indexConfig?.embed) {\n this.embeddings = this.indexConfig.embed;\n }\n }\n\n static async fromConnString(\n connString: string,\n config?: StoreConfig\n ): Promise<RedisStore> {\n const client = createClient({ url: connString });\n await client.connect();\n const store = new RedisStore(client, config);\n await store.setup();\n return store;\n }\n\n static async fromCluster(\n rootNodes: Array<{ url: string }>,\n config?: StoreConfig\n ): Promise<RedisStore> {\n const client = createCluster({ rootNodes });\n await client.connect();\n const store = new RedisStore(client, config);\n await store.setup();\n return store;\n }\n\n async setup(): Promise<void> {\n // Create store index\n try {\n await this.client.ft.create(SCHEMAS[0].index, SCHEMAS[0].schema as any, {\n ON: \"JSON\",\n PREFIX: SCHEMAS[0].prefix,\n });\n } catch (error: any) {\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\"Failed to create store index:\", error.message);\n }\n }\n\n // Create vector index if configured\n if (this.indexConfig) {\n const dims = this.indexConfig.dims;\n const distanceMetric =\n this.indexConfig.distanceType === \"cosine\"\n ? \"COSINE\"\n : this.indexConfig.distanceType === \"l2\"\n ? \"L2\"\n : this.indexConfig.distanceType === \"ip\"\n ? \"IP\"\n : \"COSINE\";\n\n // Build schema with correct vector syntax\n const vectorSchema: Record<string, any> = {\n \"$.prefix\": { type: \"TEXT\", AS: \"prefix\" },\n \"$.key\": { type: \"TAG\", AS: \"key\" },\n \"$.field_name\": { type: \"TAG\", AS: \"field_name\" },\n \"$.created_at\": { type: \"NUMERIC\", AS: \"created_at\" },\n \"$.updated_at\": { type: \"NUMERIC\", AS: \"updated_at\" },\n };\n\n // Add vector field with correct syntax\n vectorSchema[\"$.embedding\"] = {\n type: \"VECTOR\",\n ALGORITHM: \"FLAT\",\n TYPE: \"FLOAT32\",\n DIM: dims,\n DISTANCE_METRIC: distanceMetric,\n AS: \"embedding\",\n };\n\n try {\n await this.client.ft.create(SCHEMAS[1].index, vectorSchema as any, {\n ON: \"JSON\",\n PREFIX: SCHEMAS[1].prefix,\n });\n } catch (error: any) {\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\"Failed to create vector index:\", error.message);\n }\n }\n }\n }\n\n async get(\n namespace: string[],\n key: string,\n options?: { refreshTTL?: boolean }\n ): Promise<Item | null> {\n const prefix = namespace.join(\".\");\n // For TEXT fields, we need to match all tokens (split by dots and hyphens)\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n const prefixQuery =\n tokens.length > 0 ? `@prefix:(${tokens.join(\" \")})` : \"*\";\n\n // For TAG fields in curly braces, escape special characters\n // Handle empty string as a special case\n let query: string;\n if (key === \"\") {\n // For empty keys, search by prefix and filter results\n query = prefixQuery;\n } else {\n const escapedKey = this.escapeTagValue(key);\n query = `(${prefixQuery}) (@key:{${escapedKey}})`;\n }\n\n try {\n const results = await this.client.ft.search(\"store\", query, {\n LIMIT: { from: 0, size: key === \"\" ? 100 : 1 },\n });\n\n if (!results || !results.documents || results.documents.length === 0) {\n return null;\n }\n\n // For empty key, filter to find the exact match\n if (key === \"\") {\n for (const doc of results.documents) {\n const jsonDoc = doc.value as unknown as StoreDocument;\n if (jsonDoc.key === \"\" && jsonDoc.prefix === prefix) {\n const docId = doc.id;\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(docId);\n }\n\n return {\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n };\n }\n }\n return null;\n }\n\n const doc = results.documents[0];\n const jsonDoc = doc.value as unknown as StoreDocument;\n const docId = doc.id;\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(docId);\n }\n\n return {\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n };\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return null;\n }\n throw error;\n }\n }\n\n async put(\n namespace: string[],\n key: string,\n value: any,\n options?: { ttl?: number; index?: boolean | string[] }\n ): Promise<void> {\n // Validate namespace for put operations\n this.validateNamespace(namespace);\n const prefix = namespace.join(\".\");\n const docId = uuidv4();\n // Use high-resolution time for better timestamp precision\n const now = Date.now() * 1000000 + Math.floor(performance.now() * 1000); // Microseconds + nanoseconds component\n let createdAt = now; // Will be overridden if document exists\n\n // Delete existing document if it exists\n // For TEXT fields, we need to match all tokens (split by dots and hyphens)\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n const prefixQuery =\n tokens.length > 0 ? `@prefix:(${tokens.join(\" \")})` : \"*\";\n\n // For TAG fields in curly braces, escape special characters\n const escapedKey = this.escapeTagValue(key);\n const existingQuery = `(${prefixQuery}) (@key:{${escapedKey}})`;\n try {\n const existing = await this.client.ft.search(\"store\", existingQuery, {\n LIMIT: { from: 0, size: 1 },\n });\n\n if (existing && existing.documents && existing.documents.length > 0) {\n const oldDocId = existing.documents[0].id;\n // Preserve the original created_at timestamp\n const existingDoc = await this.client.json.get(oldDocId);\n if (\n existingDoc &&\n typeof existingDoc === \"object\" &&\n \"created_at\" in existingDoc\n ) {\n createdAt = (existingDoc as any).created_at;\n }\n await this.client.del(oldDocId);\n\n // Also delete associated vector if it exists\n if (this.indexConfig) {\n const oldUuid = oldDocId.split(\":\").pop();\n const oldVectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${oldUuid}`;\n try {\n await this.client.del(oldVectorKey);\n } catch (error) {\n // Vector might not exist\n }\n }\n }\n } catch (error) {\n // Index might not exist yet\n }\n\n // Handle delete operation\n if (value === null) {\n return;\n }\n\n // Store the document\n const storeKey = `${STORE_PREFIX}${REDIS_KEY_SEPARATOR}${docId}`;\n const doc = {\n prefix,\n key,\n value,\n created_at: createdAt,\n updated_at: now,\n };\n\n await this.client.json.set(storeKey, \"$\", doc);\n\n // Handle embeddings if configured\n if (this.indexConfig && this.embeddings && options?.index !== false) {\n const fieldsToIndex =\n options && Array.isArray(options.index)\n ? options.index\n : this.indexConfig.fields || [\"text\"];\n const textsToEmbed = [];\n const fieldNames = [];\n\n for (const field of fieldsToIndex) {\n if (value[field]) {\n textsToEmbed.push(value[field]);\n fieldNames.push(field);\n }\n }\n\n if (textsToEmbed.length > 0) {\n const embeddings = await this.embeddings.embedDocuments(textsToEmbed);\n\n for (let i = 0; i < embeddings.length; i++) {\n const vectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${docId}`;\n const vectorDoc: VectorDocument = {\n prefix,\n key,\n field_name: fieldNames[i],\n embedding: embeddings[i],\n created_at: now,\n updated_at: now,\n };\n\n await this.client.json.set(vectorKey, \"$\", vectorDoc as any);\n\n // Apply TTL to vector key if configured\n const ttlMinutes = options?.ttl || this.ttlConfig?.defaultTTL;\n if (ttlMinutes) {\n const ttlSeconds = Math.floor(ttlMinutes * 60);\n await this.client.expire(vectorKey, ttlSeconds);\n }\n }\n }\n }\n\n // Apply TTL if configured\n const ttlMinutes = options?.ttl || this.ttlConfig?.defaultTTL;\n if (ttlMinutes) {\n const ttlSeconds = Math.floor(ttlMinutes * 60);\n await this.client.expire(storeKey, ttlSeconds);\n }\n }\n\n async delete(namespace: string[], key: string): Promise<void> {\n await this.put(namespace, key, null);\n }\n\n async search(\n namespacePrefix: string[],\n options?: {\n filter?: Filter;\n query?: string;\n limit?: number;\n offset?: number;\n refreshTTL?: boolean;\n similarityThreshold?: number;\n }\n ): Promise<SearchItem[]> {\n const prefix = namespacePrefix.join(\".\");\n const limit = options?.limit || 10;\n const offset = options?.offset || 0;\n\n // Handle vector search if query is provided\n if (options?.query && this.indexConfig && this.embeddings) {\n const [embedding] = await this.embeddings.embedDocuments([options.query]);\n\n // Build KNN query\n // For prefix search, use wildcard since we want to match any document starting with this prefix\n let queryStr = prefix ? `@prefix:${prefix.split(/[.-]/)[0]}*` : \"*\";\n const vectorBytes = Buffer.from(new Float32Array(embedding).buffer);\n\n try {\n // Use KNN query with proper syntax\n const results = await this.client.ft.search(\n \"store_vectors\",\n `(${queryStr})=>[KNN ${limit} @embedding $BLOB]`,\n {\n PARAMS: {\n BLOB: vectorBytes,\n },\n DIALECT: 2,\n LIMIT: { from: offset, size: limit },\n RETURN: [\"prefix\", \"key\", \"__embedding_score\"],\n }\n );\n\n // Get matching store documents\n const items: SearchItem[] = [];\n for (const doc of results.documents) {\n const docUuid = doc.id.split(\":\").pop();\n const storeKey = `${STORE_PREFIX}${REDIS_KEY_SEPARATOR}${docUuid}`;\n\n const storeDoc = (await this.client.json.get(\n storeKey\n )) as StoreDocument | null;\n if (storeDoc) {\n // Apply advanced filter if provided\n if (options.filter) {\n if (\n !FilterBuilder.matchesFilter(\n storeDoc.value || {},\n options.filter\n )\n ) {\n continue;\n }\n }\n\n // Refresh TTL if requested\n if (options.refreshTTL) {\n await this.refreshItemTTL(storeKey);\n await this.refreshItemTTL(doc.id);\n }\n\n const score = (doc.value as any)?.__embedding_score\n ? this.calculateSimilarityScore(\n parseFloat((doc.value as any).__embedding_score as string)\n )\n : 0;\n\n // Apply similarity threshold if specified\n const threshold =\n options.similarityThreshold ??\n this.indexConfig?.similarityThreshold;\n if (threshold !== undefined && score < threshold) {\n continue;\n }\n\n items.push({\n value: storeDoc.value,\n key: storeDoc.key,\n namespace: storeDoc.prefix.split(\".\"),\n created_at: new Date(storeDoc.created_at / 1000000),\n updated_at: new Date(storeDoc.updated_at / 1000000),\n score,\n });\n }\n }\n\n return items;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n // Regular search without vectors\n let queryStr = \"*\";\n if (prefix) {\n // For prefix search, we need to match all tokens from the namespace prefix\n const tokens = prefix.split(/[.-]/).filter((t) => t.length > 0);\n if (tokens.length > 0) {\n // Match all tokens to ensure we get the right prefix\n queryStr = `@prefix:(${tokens.join(\" \")})`;\n }\n }\n\n try {\n const results = await this.client.ft.search(\"store\", queryStr, {\n LIMIT: { from: offset, size: limit },\n SORTBY: { BY: \"created_at\", DIRECTION: \"DESC\" },\n });\n\n const items: SearchItem[] = [];\n for (const doc of results.documents) {\n const jsonDoc = doc.value as unknown as StoreDocument;\n\n // Apply advanced filter\n if (options?.filter) {\n if (\n !FilterBuilder.matchesFilter(jsonDoc.value || {}, options.filter)\n ) {\n continue;\n }\n }\n\n // Refresh TTL if requested\n if (options?.refreshTTL) {\n await this.refreshItemTTL(doc.id);\n }\n\n items.push({\n value: jsonDoc.value,\n key: jsonDoc.key,\n namespace: jsonDoc.prefix.split(\".\"),\n created_at: new Date(jsonDoc.created_at / 1000000),\n updated_at: new Date(jsonDoc.updated_at / 1000000),\n });\n }\n\n return items;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n async listNamespaces(options?: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n }): Promise<string[][]> {\n let query = \"*\";\n\n try {\n const results = await this.client.ft.search(\"store\", query, {\n LIMIT: { from: 0, size: 1000 }, // Get many to deduplicate\n RETURN: [\"prefix\"],\n });\n\n // Extract unique namespaces and filter\n const namespaceSet = new Set<string>();\n for (const doc of results.documents) {\n const prefix = (doc.value as unknown as StoreDocument).prefix;\n const parts = prefix.split(\".\");\n\n // Apply prefix filter if specified\n if (options?.prefix) {\n // Check if this namespace starts with the specified prefix\n if (parts.length < options.prefix.length) continue;\n\n let matches = true;\n for (let i = 0; i < options.prefix.length; i++) {\n if (parts[i] !== options.prefix[i]) {\n matches = false;\n break;\n }\n }\n if (!matches) continue;\n }\n\n // Apply suffix filter if specified\n if (options?.suffix) {\n // Check if this namespace ends with the specified suffix\n if (parts.length < options.suffix.length) continue;\n\n let matches = true;\n const startIdx = parts.length - options.suffix.length;\n for (let i = 0; i < options.suffix.length; i++) {\n if (parts[startIdx + i] !== options.suffix[i]) {\n matches = false;\n break;\n }\n }\n if (!matches) continue;\n }\n\n // Apply max depth\n if (options?.maxDepth) {\n const truncated = parts.slice(0, options.maxDepth);\n namespaceSet.add(truncated.join(\".\"));\n } else {\n namespaceSet.add(prefix);\n }\n }\n\n // Convert to array of arrays and sort\n let namespaces = Array.from(namespaceSet)\n .map((ns) => ns.split(\".\"))\n .sort((a, b) => a.join(\".\").localeCompare(b.join(\".\")));\n\n // Apply pagination\n if (options?.offset || options?.limit) {\n const offset = options.offset || 0;\n const limit = options.limit || 10;\n namespaces = namespaces.slice(offset, offset + limit);\n }\n\n return namespaces;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n return [];\n }\n throw error;\n }\n }\n\n async batch(ops: Operation[]): Promise<any[]> {\n const results: any[] = new Array(ops.length).fill(null);\n\n // Process operations in order to maintain dependencies\n for (let idx = 0; idx < ops.length; idx++) {\n const op = ops[idx];\n\n // Execute operation based on type guards\n if (isPutOperation(op)) {\n // TypeScript now knows op is PutOperation\n await this.put(op.namespace, op.key, op.value);\n results[idx] = null;\n } else if (isSearchOperation(op)) {\n // TypeScript now knows op is SearchOperation\n results[idx] = await this.search(op.namespacePrefix, {\n filter: op.filter,\n query: op.query,\n limit: op.limit,\n offset: op.offset,\n });\n } else if (isListNamespacesOperation(op)) {\n // TypeScript now knows op is ListNamespacesOperation\n let prefix: string[] | undefined = undefined;\n let suffix: string[] | undefined = undefined;\n\n if (op.matchConditions) {\n for (const condition of op.matchConditions) {\n if (condition.matchType === \"prefix\") {\n prefix = condition.path;\n } else if (condition.matchType === \"suffix\") {\n suffix = condition.path;\n }\n }\n }\n\n results[idx] = await this.listNamespaces({\n prefix,\n suffix,\n maxDepth: op.maxDepth,\n limit: op.limit,\n offset: op.offset,\n });\n } else if (isGetOperation(op)) {\n // TypeScript now knows op is GetOperation\n results[idx] = await this.get(op.namespace, op.key);\n } else {\n // This should never happen with proper Operation type\n throw new Error(`Unknown operation type: ${JSON.stringify(op)}`);\n }\n }\n\n return results;\n }\n\n async close(): Promise<void> {\n await this.client.quit();\n }\n\n /**\n * Get statistics about the store.\n * Returns document counts and other metrics.\n */\n async getStatistics(): Promise<{\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n }> {\n const stats: {\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n } = {\n totalDocuments: 0,\n namespaceCount: 0,\n };\n\n try {\n // Get total document count\n const countResult = await this.client.ft.search(\"store\", \"*\", {\n LIMIT: { from: 0, size: 0 },\n });\n stats.totalDocuments = countResult.total || 0;\n\n // Get unique namespace count\n const namespaces = await this.listNamespaces({ limit: 1000 });\n stats.namespaceCount = namespaces.length;\n\n // Get vector document count if index is configured\n if (this.indexConfig) {\n try {\n const vectorResult = await this.client.ft.search(\n \"store_vectors\",\n \"*\",\n {\n LIMIT: { from: 0, size: 0 },\n }\n );\n stats.vectorDocuments = vectorResult.total || 0;\n } catch (error) {\n // Vector index might not exist\n stats.vectorDocuments = 0;\n }\n\n // Get index info\n try {\n stats.indexInfo = await this.client.ft.info(\"store\");\n } catch (error) {\n // Index info might not be available\n }\n }\n } catch (error: any) {\n if (!error.message?.includes(\"no such index\")) {\n throw error;\n }\n }\n\n return stats;\n }\n\n private validateNamespace(namespace: string[]): void {\n if (namespace.length === 0) {\n throw new InvalidNamespaceError(\"Namespace cannot be empty.\");\n }\n for (const label of namespace) {\n // Runtime check for JavaScript users (TypeScript already ensures this)\n // This check is for runtime safety when called from JavaScript\n // noinspection SuspiciousTypeOfGuard\n if (typeof label !== \"string\") {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${String(\n label\n )}' found in ${namespace}. Namespace labels must be strings.`\n );\n }\n if (label.includes(\".\")) {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`\n );\n }\n if (label === \"\") {\n throw new InvalidNamespaceError(\n `Namespace labels cannot be empty strings. Got ${label} in ${namespace}`\n );\n }\n }\n if (namespace[0] === \"langgraph\") {\n throw new InvalidNamespaceError(\n `Root label for namespace cannot be \"langgraph\". Got: ${namespace}`\n );\n }\n }\n\n private async refreshItemTTL(docId: string): Promise<void> {\n if (this.ttlConfig?.defaultTTL) {\n const ttlSeconds = Math.floor(this.ttlConfig.defaultTTL * 60);\n await this.client.expire(docId, ttlSeconds);\n\n // Also refresh vector key if it exists\n const docUuid = docId.split(\":\").pop();\n const vectorKey = `${STORE_VECTOR_PREFIX}${REDIS_KEY_SEPARATOR}${docUuid}`;\n try {\n await this.client.expire(vectorKey, ttlSeconds);\n } catch (error) {\n // Vector key might not exist\n }\n }\n }\n\n private escapeTagValue(value: string): string {\n // Delegate to shared utility for RediSearch TAG field escaping\n return escapeRediSearchTagValue(value);\n }\n\n /**\n * Calculate similarity score based on the distance metric.\n * Converts raw distance to a normalized similarity score [0,1].\n */\n private calculateSimilarityScore(distance: number): number {\n const metric = this.indexConfig?.distanceType || \"cosine\";\n\n switch (metric) {\n case \"cosine\":\n // Cosine distance is in range [0,2], convert to similarity [0,1]\n return Math.max(0, 1 - distance / 2);\n\n case \"l2\":\n // L2 (Euclidean) distance, use exponential decay\n // Similarity = e^(-distance)\n return Math.exp(-distance);\n\n case \"ip\":\n // Inner product can be negative, use sigmoid function\n // Similarity = 1 / (1 + e^(-distance))\n return 1 / (1 + Math.exp(-distance));\n\n default:\n // Default to cosine similarity\n return Math.max(0, 1 - distance / 2);\n }\n }\n}\n\n// Export FilterBuilder for testing purposes\nexport { FilterBuilder };\n"],"mappings":"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"}
package/dist/utils.cjs CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/utils.ts
3
2
  /**
4
3
  * Escape special characters in a string for use in RediSearch TAG field queries.
@@ -16,7 +15,7 @@ function escapeRediSearchTagValue(value) {
16
15
  if (value === "") return "__EMPTY_STRING__";
17
16
  return value.replace(/\\/g, "\\\\").replace(/[-\s,.:<>{}[\]"';!@#$%^&*()+=~|?/]/g, "\\$&");
18
17
  }
19
-
20
18
  //#endregion
21
19
  exports.escapeRediSearchTagValue = escapeRediSearchTagValue;
20
+
22
21
  //# sourceMappingURL=utils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/**\n * Escape special characters in a string for use in RediSearch TAG field queries.\n *\n * RediSearch TAG fields have special characters that need escaping when used\n * within curly braces: , . < > { } [ ] \" ' : ; ! @ # $ % ^ & * ( ) - + = ~ | \\ ? /\n *\n * This function is used to prevent RediSearch query injection attacks when\n * building queries with user-provided filter values.\n *\n * @param value - The string value to escape\n * @returns The escaped string safe for use in RediSearch TAG queries\n */\nexport function escapeRediSearchTagValue(value: string): string {\n // Handle empty string as a special case - use a placeholder\n if (value === \"\") {\n return \"__EMPTY_STRING__\";\n }\n // Escape backslashes first, then all other special characters\n return value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/[-\\s,.:<>{}[\\]\"';!@#$%^&*()+=~|?/]/g, \"\\\\$&\");\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,yBAAyB,OAAuB;AAE9D,KAAI,UAAU,GACZ,QAAO;AAGT,QAAO,MACJ,QAAQ,OAAO,OAAO,CACtB,QAAQ,uCAAuC,OAAO"}
1
+ {"version":3,"file":"utils.cjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/**\n * Escape special characters in a string for use in RediSearch TAG field queries.\n *\n * RediSearch TAG fields have special characters that need escaping when used\n * within curly braces: , . < > { } [ ] \" ' : ; ! @ # $ % ^ & * ( ) - + = ~ | \\ ? /\n *\n * This function is used to prevent RediSearch query injection attacks when\n * building queries with user-provided filter values.\n *\n * @param value - The string value to escape\n * @returns The escaped string safe for use in RediSearch TAG queries\n */\nexport function escapeRediSearchTagValue(value: string): string {\n // Handle empty string as a special case - use a placeholder\n if (value === \"\") {\n return \"__EMPTY_STRING__\";\n }\n // Escape backslashes first, then all other special characters\n return value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/[-\\s,.:<>{}[\\]\"';!@#$%^&*()+=~|?/]/g, \"\\\\$&\");\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,yBAAyB,OAAuB;AAE9D,KAAI,UAAU,GACZ,QAAO;AAGT,QAAO,MACJ,QAAQ,OAAO,OAAO,CACtB,QAAQ,uCAAuC,OAAO"}
package/dist/utils.js CHANGED
@@ -15,7 +15,7 @@ function escapeRediSearchTagValue(value) {
15
15
  if (value === "") return "__EMPTY_STRING__";
16
16
  return value.replace(/\\/g, "\\\\").replace(/[-\s,.:<>{}[\]"';!@#$%^&*()+=~|?/]/g, "\\$&");
17
17
  }
18
-
19
18
  //#endregion
20
19
  export { escapeRediSearchTagValue };
20
+
21
21
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-checkpoint-redis",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Redis checkpoint and store implementation for LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
19
  "redis": "^4.7.0",
20
- "ulid": "^2.3.0"
20
+ "uuid": "^13.0.0"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "@langchain/core": "^1.0.1",
@@ -27,7 +27,7 @@
27
27
  "@langchain/scripts": ">=0.1.2 <0.2.0",
28
28
  "@tsconfig/recommended": "^1.0.3",
29
29
  "@types/node": "^20",
30
- "testcontainers": "^10.0.0",
30
+ "testcontainers": "^11.0.0",
31
31
  "@typescript-eslint/eslint-plugin": "^6.12.0",
32
32
  "@typescript-eslint/parser": "^6.12.0",
33
33
  "dotenv": "^16.3.1",
@@ -43,7 +43,7 @@
43
43
  "tsx": "^4.19.3",
44
44
  "typescript": "^4.9.5 || ^5.4.5",
45
45
  "vitest": "^3.2.4",
46
- "@langchain/langgraph-checkpoint": "1.0.0"
46
+ "@langchain/langgraph-checkpoint": "1.0.1"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public",