@langchain/langgraph-checkpoint-redis 1.0.2 → 1.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"shallow.js","names":[],"sources":["../src/shallow.ts"],"sourcesContent":["import {\n BaseCheckpointSaver,\n ChannelVersions,\n Checkpoint,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n PendingWrite,\n uuid6,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { createClient } from \"redis\";\nimport { escapeRediSearchTagValue } from \"./utils.js\";\n\nexport interface TTLConfig {\n defaultTTL?: number; // TTL in minutes\n refreshOnRead?: boolean; // Whether to refresh TTL when reading\n}\n\n// Helper function for deterministic object comparison\nfunction deterministicStringify(obj: any): string {\n if (obj === null || typeof obj !== \"object\") {\n return JSON.stringify(obj);\n }\n if (Array.isArray(obj)) {\n return JSON.stringify(obj.map((item) => deterministicStringify(item)));\n }\n const sortedObj: Record<string, any> = {};\n const sortedKeys = Object.keys(obj).sort();\n for (const key of sortedKeys) {\n sortedObj[key] = obj[key];\n }\n return JSON.stringify(sortedObj, (_, value) => {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const sorted: Record<string, any> = {};\n const keys = Object.keys(value).sort();\n for (const k of keys) {\n sorted[k] = value[k];\n }\n return sorted;\n }\n return value;\n });\n}\n\nconst SCHEMAS = [\n {\n index: \"checkpoints\",\n prefix: \"checkpoint:\",\n schema: {\n \"$.thread_id\": { type: \"TAG\", AS: \"thread_id\" },\n \"$.checkpoint_ns\": { type: \"TAG\", AS: \"checkpoint_ns\" },\n \"$.checkpoint_id\": { type: \"TAG\", AS: \"checkpoint_id\" },\n \"$.parent_checkpoint_id\": { type: \"TAG\", AS: \"parent_checkpoint_id\" },\n \"$.checkpoint_ts\": { type: \"NUMERIC\", AS: \"checkpoint_ts\" },\n \"$.has_writes\": { type: \"TAG\", AS: \"has_writes\" },\n \"$.source\": { type: \"TAG\", AS: \"source\" },\n \"$.step\": { type: \"NUMERIC\", AS: \"step\" },\n },\n },\n {\n index: \"checkpoint_writes\",\n prefix: \"checkpoint_write:\",\n schema: {\n \"$.thread_id\": { type: \"TAG\", AS: \"thread_id\" },\n \"$.checkpoint_ns\": { type: \"TAG\", AS: \"checkpoint_ns\" },\n \"$.checkpoint_id\": { type: \"TAG\", AS: \"checkpoint_id\" },\n \"$.task_id\": { type: \"TAG\", AS: \"task_id\" },\n \"$.idx\": { type: \"NUMERIC\", AS: \"idx\" },\n \"$.channel\": { type: \"TAG\", AS: \"channel\" },\n \"$.type\": { type: \"TAG\", AS: \"type\" },\n },\n },\n];\n\n/**\n * ShallowRedisSaver - A Redis checkpoint saver that only keeps the latest checkpoint per thread.\n *\n * This is a memory-optimized variant that:\n * - Only stores the most recent checkpoint for each thread\n * - Stores channel values inline (no separate blob storage)\n * - Automatically cleans up old checkpoints and writes when new ones are added\n * - Reduces storage usage for applications that don't need checkpoint history\n */\nexport class ShallowRedisSaver extends BaseCheckpointSaver {\n private client: any;\n private ttlConfig?: TTLConfig;\n\n constructor(client: any, ttlConfig?: TTLConfig) {\n super();\n this.client = client;\n this.ttlConfig = ttlConfig;\n }\n\n static async fromUrl(\n url: string,\n ttlConfig?: TTLConfig\n ): Promise<ShallowRedisSaver> {\n const client = createClient({ url });\n await client.connect();\n const saver = new ShallowRedisSaver(client, ttlConfig);\n await saver.ensureIndexes();\n return saver;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const tuple = await this.getTuple(config);\n return tuple?.checkpoint;\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n _newVersions: ChannelVersions\n ): Promise<RunnableConfig> {\n await this.ensureIndexes();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const parentCheckpointId = config.configurable?.checkpoint_id;\n\n if (!threadId) {\n throw new Error(\"thread_id is required\");\n }\n\n const checkpointId = checkpoint.id || uuid6(0);\n\n // In shallow mode, we use a single key per thread (no checkpoint_id in key)\n const key = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n\n // Get the previous checkpoint to know what to clean up\n let prevCheckpointData: any = null;\n let prevCheckpointId: string | null = null;\n try {\n prevCheckpointData = await this.client.json.get(key);\n if (prevCheckpointData && typeof prevCheckpointData === \"object\") {\n prevCheckpointId = prevCheckpointData.checkpoint_id;\n }\n } catch (error) {\n // Key doesn't exist yet, that's fine\n }\n\n // Clean up old checkpoint and related data if it exists\n if (prevCheckpointId && prevCheckpointId !== checkpointId) {\n await this.cleanupOldCheckpoint(threadId, checkpointNs, prevCheckpointId);\n }\n\n // Store channel values inline - no blob storage in shallow mode\n const checkpointCopy = {\n ...checkpoint,\n channel_values: checkpoint.channel_values || {},\n // Remove channel_blobs if present\n channel_blobs: undefined,\n };\n\n // Structure matching Python implementation\n const jsonDoc: any = {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n parent_checkpoint_id: parentCheckpointId || null,\n checkpoint: checkpointCopy,\n metadata: this.sanitizeMetadata(metadata),\n checkpoint_ts: Date.now(),\n has_writes: \"false\",\n };\n\n // Store metadata fields at top-level for searching\n this.addSearchableMetadataFields(jsonDoc, metadata);\n\n // Use Redis JSON commands\n await this.client.json.set(key, \"$\", jsonDoc);\n\n // Apply TTL if configured\n if (this.ttlConfig?.defaultTTL) {\n await this.applyTTL(key);\n }\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n },\n };\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = config.configurable?.checkpoint_id;\n\n if (!threadId) {\n return undefined;\n }\n\n // In shallow mode, we use a single key per thread\n const key = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n const jsonDoc = await this.client.json.get(key);\n\n if (!jsonDoc) {\n return undefined;\n }\n\n // If a specific checkpoint_id was requested, check if it matches\n if (checkpointId && jsonDoc.checkpoint_id !== checkpointId) {\n return undefined;\n }\n\n // Refresh TTL if configured\n if (this.ttlConfig?.refreshOnRead && this.ttlConfig?.defaultTTL) {\n await this.applyTTL(key);\n }\n\n // Deserialize checkpoint using serde to restore LangChain objects\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n // Load pending writes if they exist\n let pendingWrites: Array<[string, string, any]> | undefined;\n if (jsonDoc.has_writes === \"true\") {\n pendingWrites = await this.loadPendingWrites(\n jsonDoc.thread_id,\n jsonDoc.checkpoint_ns,\n jsonDoc.checkpoint_id\n );\n }\n\n return await this.createCheckpointTuple(jsonDoc, checkpoint, pendingWrites);\n }\n\n async *list(\n config: RunnableConfig | null,\n options?: CheckpointListOptions & { filter?: CheckpointMetadata }\n ): AsyncGenerator<CheckpointTuple> {\n await this.ensureIndexes();\n\n // In shallow mode, we only return the latest checkpoint per thread\n if (config?.configurable?.thread_id) {\n // Single thread case\n const tuple = await this.getTuple(config);\n if (tuple) {\n // Apply filter if provided\n if (options?.filter) {\n if (this.checkMetadataFilterMatch(tuple.metadata, options.filter)) {\n yield tuple;\n }\n } else {\n yield tuple;\n }\n }\n } else {\n // All threads case - use search\n const queryParts: string[] = [];\n\n // Add metadata filters\n if (options?.filter) {\n for (const [key, value] of Object.entries(options.filter)) {\n if (value === undefined) {\n // Skip undefined filters\n } else if (value === null) {\n // Skip null values for RediSearch query, will handle in post-processing\n } else if (typeof value === \"string\") {\n // Escape both key and value to prevent RediSearch query injection\n const escapedKey = escapeRediSearchTagValue(key);\n const escapedValue = escapeRediSearchTagValue(value);\n queryParts.push(`(@${escapedKey}:{${escapedValue}})`);\n } else if (typeof value === \"number\") {\n // Escape key to prevent injection; numbers don't need value escaping\n const escapedKey = escapeRediSearchTagValue(key);\n queryParts.push(`(@${escapedKey}:[${value} ${value}])`);\n }\n }\n }\n\n if (queryParts.length === 0) {\n queryParts.push(\"*\");\n }\n\n const query = queryParts.join(\" \");\n const limit = options?.limit ?? 10;\n\n try {\n const results = await this.client.ft.search(\"checkpoints\", query, {\n LIMIT: { from: 0, size: limit * 2 }, // Get more since we'll deduplicate\n SORTBY: { BY: \"checkpoint_ts\", DIRECTION: \"DESC\" },\n });\n\n // In shallow mode, deduplicate by thread_id\n const seenThreads = new Set<string>();\n let yieldCount = 0;\n\n for (const doc of results.documents) {\n if (yieldCount >= limit) break;\n\n const jsonDoc = doc.value;\n const threadKey = `${jsonDoc.thread_id}:${jsonDoc.checkpoint_ns}`;\n\n // Skip if we've already seen this thread\n if (seenThreads.has(threadKey)) {\n continue;\n }\n seenThreads.add(threadKey);\n\n // Check null filters manually if needed\n if (options?.filter) {\n if (\n !this.checkMetadataFilterMatch(jsonDoc.metadata, options.filter)\n ) {\n continue;\n }\n }\n\n // Channel values are inline in shallow mode\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n yield await this.createCheckpointTuple(jsonDoc, checkpoint);\n yieldCount++;\n }\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n // Index doesn't exist yet, fall back to scanning all shallow checkpoints\n const pattern = `checkpoint:*:*:shallow`;\n const keys = await this.client.keys(pattern);\n\n if (keys.length === 0) {\n return;\n }\n\n // Sort keys to have consistent ordering\n keys.sort().reverse();\n\n // Get unique threads\n const seenThreads = new Set<string>();\n let yieldCount = 0;\n const limit = options?.limit ?? 10;\n\n for (const key of keys) {\n if (yieldCount >= limit) break;\n\n const jsonDoc = await this.client.json.get(key);\n if (!jsonDoc) continue;\n\n const threadKey = `${jsonDoc.thread_id}:${jsonDoc.checkpoint_ns}`;\n\n // Skip if we've already seen this thread\n if (seenThreads.has(threadKey)) {\n continue;\n }\n seenThreads.add(threadKey);\n\n // Check filter if provided\n if (options?.filter) {\n if (\n !this.checkMetadataFilterMatch(jsonDoc.metadata, options.filter)\n ) {\n continue;\n }\n }\n\n // Channel values are inline in shallow mode\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n yield await this.createCheckpointTuple(jsonDoc, checkpoint);\n yieldCount++;\n }\n return;\n }\n throw error;\n }\n }\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void> {\n await this.ensureIndexes();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = config.configurable?.checkpoint_id;\n\n if (!threadId || !checkpointId) {\n throw new Error(\"thread_id and checkpoint_id are required\");\n }\n\n // In shallow mode, we overwrite all writes for the task\n // First, clean up old writes for this task\n const writePattern = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:*`;\n const oldWriteKeys = await this.client.keys(writePattern);\n if (oldWriteKeys.length > 0) {\n await this.client.del(oldWriteKeys);\n }\n\n // Store new writes\n const writeKeys: string[] = [];\n for (let idx = 0; idx < writes.length; idx++) {\n const [channel, value] = writes[idx];\n const writeKey = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:${idx}`;\n writeKeys.push(writeKey);\n\n const writeDoc = {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n task_id: taskId,\n idx: idx,\n channel: channel,\n type: typeof value === \"object\" ? \"json\" : \"string\",\n value: value,\n };\n\n await this.client.json.set(writeKey, \"$\", writeDoc);\n }\n\n // Register write keys in sorted set for efficient retrieval\n if (writeKeys.length > 0) {\n const zsetKey = `write_keys_zset:${threadId}:${checkpointNs}:${checkpointId}`;\n\n // Clear existing entries for this task and add new ones\n const zaddArgs: Record<string, number> = {};\n writeKeys.forEach((key, idx) => {\n zaddArgs[key] = idx;\n });\n await this.client.zAdd(\n zsetKey,\n Object.entries(zaddArgs).map(([key, score]) => ({ score, value: key }))\n );\n\n // Apply TTL to write keys and zset if configured\n if (this.ttlConfig?.defaultTTL) {\n await this.applyTTL(...writeKeys, zsetKey);\n }\n }\n\n // Update checkpoint to indicate it has writes\n const checkpointKey = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n const checkpointExists = await this.client.exists(checkpointKey);\n if (checkpointExists) {\n const currentDoc = await this.client.json.get(checkpointKey);\n if (currentDoc) {\n currentDoc.has_writes = \"true\";\n await this.client.json.set(checkpointKey, \"$\", currentDoc);\n }\n }\n }\n\n async deleteThread(threadId: string): Promise<void> {\n // Delete shallow checkpoints\n const checkpointPattern = `checkpoint:${threadId}:*:shallow`;\n const checkpointKeys = await this.client.keys(checkpointPattern);\n\n if (checkpointKeys.length > 0) {\n await this.client.del(checkpointKeys);\n }\n\n // Delete writes\n const writesPattern = `checkpoint_write:${threadId}:*`;\n const writesKeys = await this.client.keys(writesPattern);\n\n if (writesKeys.length > 0) {\n await this.client.del(writesKeys);\n }\n\n // Delete write registries\n const zsetPattern = `write_keys_zset:${threadId}:*`;\n const zsetKeys = await this.client.keys(zsetPattern);\n\n if (zsetKeys.length > 0) {\n await this.client.del(zsetKeys);\n }\n }\n\n async end(): Promise<void> {\n await this.client.quit();\n }\n\n // Helper method to add searchable metadata fields\n private addSearchableMetadataFields(\n jsonDoc: any,\n metadata?: CheckpointMetadata\n ): void {\n if (!metadata) return;\n\n // Add common searchable fields at top level\n if (\"source\" in metadata) {\n jsonDoc.source = metadata.source;\n }\n if (\"step\" in metadata) {\n jsonDoc.step = metadata.step;\n }\n if (\"writes\" in metadata) {\n // Writes field needs to be JSON stringified for TAG search\n jsonDoc.writes =\n typeof metadata.writes === \"object\"\n ? JSON.stringify(metadata.writes)\n : metadata.writes;\n }\n if (\"score\" in metadata) {\n jsonDoc.score = metadata.score;\n }\n }\n\n // Helper method to create checkpoint tuple from json document\n private async createCheckpointTuple(\n jsonDoc: any,\n checkpoint: Checkpoint,\n pendingWrites?: Array<[string, string, any]>\n ): Promise<CheckpointTuple> {\n // Deserialize metadata using serde\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.metadata)\n )) as CheckpointMetadata;\n\n return {\n config: {\n configurable: {\n thread_id: jsonDoc.thread_id,\n checkpoint_ns: jsonDoc.checkpoint_ns,\n checkpoint_id: jsonDoc.checkpoint_id,\n },\n },\n checkpoint,\n metadata,\n parentConfig: jsonDoc.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: jsonDoc.thread_id,\n checkpoint_ns: jsonDoc.checkpoint_ns,\n checkpoint_id: jsonDoc.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n\n // Helper method to apply TTL to keys\n private async applyTTL(...keys: string[]): Promise<void> {\n if (!this.ttlConfig?.defaultTTL) return;\n\n const ttlSeconds = Math.floor(this.ttlConfig.defaultTTL * 60);\n const results = await Promise.allSettled(\n keys.map((key) => this.client.expire(key, ttlSeconds))\n );\n\n // Log any failures but don't throw - TTL is best effort\n for (let i = 0; i < results.length; i++) {\n if (results[i].status === \"rejected\") {\n console.warn(\n `Failed to set TTL for key ${keys[i]}:`,\n (results[i] as PromiseRejectedResult).reason\n );\n }\n }\n }\n\n // Helper method to load pending writes\n private async loadPendingWrites(\n threadId: string,\n checkpointNs: string,\n checkpointId: string\n ): Promise<Array<[string, string, any]> | undefined> {\n const zsetKey = `write_keys_zset:${threadId}:${checkpointNs}:${checkpointId}`;\n const writeKeys = await this.client.zRange(zsetKey, 0, -1);\n\n if (writeKeys.length === 0) {\n return undefined;\n }\n\n const pendingWrites: Array<[string, string, any]> = [];\n for (const writeKey of writeKeys) {\n const writeDoc = await this.client.json.get(writeKey);\n if (writeDoc) {\n // Deserialize write value using serde to restore LangChain objects\n const deserializedValue = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(writeDoc.value)\n );\n pendingWrites.push([\n writeDoc.task_id,\n writeDoc.channel,\n deserializedValue,\n ]);\n }\n }\n\n return pendingWrites;\n }\n\n // Helper method to check metadata filter matches\n private checkMetadataFilterMatch(\n metadata: any,\n filter: CheckpointMetadata\n ): boolean {\n for (const [key, value] of Object.entries(filter)) {\n const metadataValue = metadata?.[key];\n if (value === null) {\n if (!(key in (metadata || {})) || metadataValue !== null) {\n return false;\n }\n } else if (typeof value === \"object\" && !Array.isArray(value)) {\n // Deep comparison for objects with deterministic key ordering\n if (typeof metadataValue !== \"object\" || metadataValue === null) {\n return false;\n }\n if (\n deterministicStringify(value) !==\n deterministicStringify(metadataValue)\n ) {\n return false;\n }\n } else if (metadataValue !== value) {\n return false;\n }\n }\n return true;\n }\n\n private async cleanupOldCheckpoint(\n threadId: string,\n checkpointNs: string,\n oldCheckpointId: string\n ): Promise<void> {\n // Clean up old writes\n const writePattern = `checkpoint_write:${threadId}:${checkpointNs}:${oldCheckpointId}:*`;\n const oldWriteKeys = await this.client.keys(writePattern);\n if (oldWriteKeys.length > 0) {\n await this.client.del(oldWriteKeys);\n }\n\n // Clean up write registry\n const zsetKey = `write_keys_zset:${threadId}:${checkpointNs}:${oldCheckpointId}`;\n await this.client.del(zsetKey);\n\n // Note: We don't clean up blob keys in shallow mode since we store inline\n // But for completeness, clean up any legacy blob keys if they exist\n const blobPattern = `checkpoint_blob:${threadId}:${checkpointNs}:${oldCheckpointId}:*`;\n const oldBlobKeys = await this.client.keys(blobPattern);\n if (oldBlobKeys.length > 0) {\n await this.client.del(oldBlobKeys);\n }\n }\n\n private sanitizeMetadata(metadata: CheckpointMetadata): CheckpointMetadata {\n if (!metadata) return {} as CheckpointMetadata;\n\n const sanitized: any = {};\n for (const [key, value] of Object.entries(metadata)) {\n // Remove null characters from keys and string values\n // eslint-disable-next-line no-control-regex\n const sanitizedKey = key.replace(/\\x00/g, \"\");\n sanitized[sanitizedKey] =\n // eslint-disable-next-line no-control-regex\n typeof value === \"string\" ? value.replace(/\\x00/g, \"\") : value;\n }\n return sanitized as CheckpointMetadata;\n }\n\n private async ensureIndexes(): Promise<void> {\n for (const schema of SCHEMAS) {\n try {\n // Try to create the index\n await this.client.ft.create(schema.index, schema.schema, {\n ON: \"JSON\",\n PREFIX: schema.prefix,\n });\n } catch (error: any) {\n // Ignore if index already exists\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\n `Failed to create index ${schema.index}:`,\n error.message\n );\n }\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shallow.js","names":[],"sources":["../src/shallow.ts"],"sourcesContent":["import {\n BaseCheckpointSaver,\n ChannelVersions,\n Checkpoint,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n PendingWrite,\n uuid6,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { createClient } from \"redis\";\nimport { escapeRediSearchTagValue } from \"./utils.js\";\nimport { WRITE_KEYS_ZSET_PREFIX } from \"./constants.js\";\n\nexport interface TTLConfig {\n defaultTTL?: number; // TTL in minutes\n refreshOnRead?: boolean; // Whether to refresh TTL when reading\n}\n\n// Helper function for deterministic object comparison\nfunction deterministicStringify(obj: any): string {\n if (obj === null || typeof obj !== \"object\") {\n return JSON.stringify(obj);\n }\n if (Array.isArray(obj)) {\n return JSON.stringify(obj.map((item) => deterministicStringify(item)));\n }\n const sortedObj: Record<string, any> = {};\n const sortedKeys = Object.keys(obj).sort();\n for (const key of sortedKeys) {\n sortedObj[key] = obj[key];\n }\n return JSON.stringify(sortedObj, (_, value) => {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n const sorted: Record<string, any> = {};\n const keys = Object.keys(value).sort();\n for (const k of keys) {\n sorted[k] = value[k];\n }\n return sorted;\n }\n return value;\n });\n}\n\nconst SCHEMAS = [\n {\n index: \"checkpoints\",\n prefix: \"checkpoint:\",\n schema: {\n \"$.thread_id\": { type: \"TAG\", AS: \"thread_id\" },\n \"$.checkpoint_ns\": { type: \"TAG\", AS: \"checkpoint_ns\" },\n \"$.checkpoint_id\": { type: \"TAG\", AS: \"checkpoint_id\" },\n \"$.parent_checkpoint_id\": { type: \"TAG\", AS: \"parent_checkpoint_id\" },\n \"$.checkpoint_ts\": { type: \"NUMERIC\", AS: \"checkpoint_ts\" },\n \"$.has_writes\": { type: \"TAG\", AS: \"has_writes\" },\n \"$.source\": { type: \"TAG\", AS: \"source\" },\n \"$.step\": { type: \"NUMERIC\", AS: \"step\" },\n },\n },\n {\n index: \"checkpoint_writes\",\n prefix: \"checkpoint_write:\",\n schema: {\n \"$.thread_id\": { type: \"TAG\", AS: \"thread_id\" },\n \"$.checkpoint_ns\": { type: \"TAG\", AS: \"checkpoint_ns\" },\n \"$.checkpoint_id\": { type: \"TAG\", AS: \"checkpoint_id\" },\n \"$.task_id\": { type: \"TAG\", AS: \"task_id\" },\n \"$.idx\": { type: \"NUMERIC\", AS: \"idx\" },\n \"$.channel\": { type: \"TAG\", AS: \"channel\" },\n \"$.type\": { type: \"TAG\", AS: \"type\" },\n },\n },\n];\n\n/**\n * ShallowRedisSaver - A Redis checkpoint saver that only keeps the latest checkpoint per thread.\n *\n * This is a memory-optimized variant that:\n * - Only stores the most recent checkpoint for each thread\n * - Stores channel values inline (no separate blob storage)\n * - Automatically cleans up old checkpoints and writes when new ones are added\n * - Reduces storage usage for applications that don't need checkpoint history\n */\nexport class ShallowRedisSaver extends BaseCheckpointSaver {\n private client: any;\n private ttlConfig?: TTLConfig;\n\n constructor(client: any, ttlConfig?: TTLConfig) {\n super();\n this.client = client;\n this.ttlConfig = ttlConfig;\n }\n\n static async fromUrl(\n url: string,\n ttlConfig?: TTLConfig\n ): Promise<ShallowRedisSaver> {\n const client = createClient({ url });\n await client.connect();\n const saver = new ShallowRedisSaver(client, ttlConfig);\n await saver.ensureIndexes();\n return saver;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const tuple = await this.getTuple(config);\n return tuple?.checkpoint;\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n _newVersions: ChannelVersions\n ): Promise<RunnableConfig> {\n await this.ensureIndexes();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const parentCheckpointId = config.configurable?.checkpoint_id;\n\n if (!threadId) {\n throw new Error(\"thread_id is required\");\n }\n\n const checkpointId = checkpoint.id || uuid6(0);\n\n // In shallow mode, we use a single key per thread (no checkpoint_id in key)\n const key = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n\n // Get the previous checkpoint to know what to clean up\n let prevCheckpointData: any = null;\n let prevCheckpointId: string | null = null;\n try {\n prevCheckpointData = await this.client.json.get(key);\n if (prevCheckpointData && typeof prevCheckpointData === \"object\") {\n prevCheckpointId = prevCheckpointData.checkpoint_id;\n }\n } catch (error) {\n // Key doesn't exist yet, that's fine\n }\n\n // Clean up old checkpoint and related data if it exists\n if (prevCheckpointId && prevCheckpointId !== checkpointId) {\n await this.cleanupOldCheckpoint(threadId, checkpointNs, prevCheckpointId);\n }\n\n // Store channel values inline - no blob storage in shallow mode\n const checkpointCopy = {\n ...checkpoint,\n channel_values: checkpoint.channel_values || {},\n // Remove channel_blobs if present\n channel_blobs: undefined,\n };\n\n // Check if writes already exist for this checkpoint (handles putWrites-before-put ordering)\n const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;\n const writesExist = await this.client.exists(zsetKey);\n\n // Structure matching Python implementation\n const jsonDoc: any = {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n parent_checkpoint_id: parentCheckpointId || null,\n checkpoint: checkpointCopy,\n metadata: this.sanitizeMetadata(metadata),\n checkpoint_ts: Date.now(),\n has_writes: writesExist ? \"true\" : \"false\",\n };\n\n // Store metadata fields at top-level for searching\n this.addSearchableMetadataFields(jsonDoc, metadata);\n\n // Use Redis JSON commands\n await this.client.json.set(key, \"$\", jsonDoc);\n\n // Apply TTL if configured\n if (this.ttlConfig?.defaultTTL) {\n await this.applyTTL(key);\n }\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n },\n };\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = config.configurable?.checkpoint_id;\n\n if (!threadId) {\n return undefined;\n }\n\n // In shallow mode, we use a single key per thread\n const key = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n const jsonDoc = await this.client.json.get(key);\n\n if (!jsonDoc) {\n return undefined;\n }\n\n // If a specific checkpoint_id was requested, check if it matches\n if (checkpointId && jsonDoc.checkpoint_id !== checkpointId) {\n return undefined;\n }\n\n // Refresh TTL if configured\n if (this.ttlConfig?.refreshOnRead && this.ttlConfig?.defaultTTL) {\n await this.applyTTL(key);\n }\n\n // Deserialize checkpoint using serde to restore LangChain objects\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n // Load pending writes if they exist\n let pendingWrites: Array<[string, string, any]> | undefined;\n if (jsonDoc.has_writes === \"true\") {\n pendingWrites = await this.loadPendingWrites(\n jsonDoc.thread_id,\n jsonDoc.checkpoint_ns,\n jsonDoc.checkpoint_id\n );\n }\n\n return await this.createCheckpointTuple(jsonDoc, checkpoint, pendingWrites);\n }\n\n async *list(\n config: RunnableConfig | null,\n options?: CheckpointListOptions & { filter?: CheckpointMetadata }\n ): AsyncGenerator<CheckpointTuple> {\n await this.ensureIndexes();\n\n // In shallow mode, we only return the latest checkpoint per thread\n if (config?.configurable?.thread_id) {\n // Single thread case\n const tuple = await this.getTuple(config);\n if (tuple) {\n // Apply filter if provided\n if (options?.filter) {\n if (this.checkMetadataFilterMatch(tuple.metadata, options.filter)) {\n yield tuple;\n }\n } else {\n yield tuple;\n }\n }\n } else {\n // All threads case - use search\n const queryParts: string[] = [];\n\n // Add metadata filters\n if (options?.filter) {\n for (const [key, value] of Object.entries(options.filter)) {\n if (value === undefined) {\n // Skip undefined filters\n } else if (value === null) {\n // Skip null values for RediSearch query, will handle in post-processing\n } else if (typeof value === \"string\") {\n // Escape both key and value to prevent RediSearch query injection\n const escapedKey = escapeRediSearchTagValue(key);\n const escapedValue = escapeRediSearchTagValue(value);\n queryParts.push(`(@${escapedKey}:{${escapedValue}})`);\n } else if (typeof value === \"number\") {\n // Escape key to prevent injection; numbers don't need value escaping\n const escapedKey = escapeRediSearchTagValue(key);\n queryParts.push(`(@${escapedKey}:[${value} ${value}])`);\n }\n }\n }\n\n if (queryParts.length === 0) {\n queryParts.push(\"*\");\n }\n\n const query = queryParts.join(\" \");\n const limit = options?.limit ?? 10;\n\n try {\n const results = await this.client.ft.search(\"checkpoints\", query, {\n LIMIT: { from: 0, size: limit * 2 }, // Get more since we'll deduplicate\n SORTBY: { BY: \"checkpoint_ts\", DIRECTION: \"DESC\" },\n });\n\n // In shallow mode, deduplicate by thread_id\n const seenThreads = new Set<string>();\n let yieldCount = 0;\n\n for (const doc of results.documents) {\n if (yieldCount >= limit) break;\n\n const jsonDoc = doc.value;\n const threadKey = `${jsonDoc.thread_id}:${jsonDoc.checkpoint_ns}`;\n\n // Skip if we've already seen this thread\n if (seenThreads.has(threadKey)) {\n continue;\n }\n seenThreads.add(threadKey);\n\n // Check null filters manually if needed\n if (options?.filter) {\n if (\n !this.checkMetadataFilterMatch(jsonDoc.metadata, options.filter)\n ) {\n continue;\n }\n }\n\n // Channel values are inline in shallow mode\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n yield await this.createCheckpointTuple(jsonDoc, checkpoint);\n yieldCount++;\n }\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n // Index doesn't exist yet, fall back to scanning all shallow checkpoints\n const pattern = `checkpoint:*:*:shallow`;\n const keys = await this.client.keys(pattern);\n\n if (keys.length === 0) {\n return;\n }\n\n // Sort keys to have consistent ordering\n keys.sort().reverse();\n\n // Get unique threads\n const seenThreads = new Set<string>();\n let yieldCount = 0;\n const limit = options?.limit ?? 10;\n\n for (const key of keys) {\n if (yieldCount >= limit) break;\n\n const jsonDoc = await this.client.json.get(key);\n if (!jsonDoc) continue;\n\n const threadKey = `${jsonDoc.thread_id}:${jsonDoc.checkpoint_ns}`;\n\n // Skip if we've already seen this thread\n if (seenThreads.has(threadKey)) {\n continue;\n }\n seenThreads.add(threadKey);\n\n // Check filter if provided\n if (options?.filter) {\n if (\n !this.checkMetadataFilterMatch(jsonDoc.metadata, options.filter)\n ) {\n continue;\n }\n }\n\n // Channel values are inline in shallow mode\n const checkpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.checkpoint)\n );\n\n yield await this.createCheckpointTuple(jsonDoc, checkpoint);\n yieldCount++;\n }\n return;\n }\n throw error;\n }\n }\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void> {\n await this.ensureIndexes();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = config.configurable?.checkpoint_id;\n\n if (!threadId || !checkpointId) {\n throw new Error(\"thread_id and checkpoint_id are required\");\n }\n\n // In shallow mode, we overwrite all writes for the task\n // First, clean up old writes for this task\n const writePattern = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:*`;\n const oldWriteKeys = await this.client.keys(writePattern);\n if (oldWriteKeys.length > 0) {\n await this.client.del(oldWriteKeys);\n }\n\n // Store new writes\n const writeKeys: string[] = [];\n for (let idx = 0; idx < writes.length; idx++) {\n const [channel, value] = writes[idx];\n const writeKey = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:${idx}`;\n writeKeys.push(writeKey);\n\n const writeDoc = {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n task_id: taskId,\n idx: idx,\n channel: channel,\n type: typeof value === \"object\" ? \"json\" : \"string\",\n value: value,\n };\n\n await this.client.json.set(writeKey, \"$\", writeDoc);\n }\n\n // Register write keys in sorted set for efficient retrieval\n if (writeKeys.length > 0) {\n const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;\n\n // Clear existing entries for this task and add new ones\n const zaddArgs: Record<string, number> = {};\n writeKeys.forEach((key, idx) => {\n zaddArgs[key] = idx;\n });\n await this.client.zAdd(\n zsetKey,\n Object.entries(zaddArgs).map(([key, score]) => ({ score, value: key }))\n );\n\n // Apply TTL to write keys and zset if configured\n if (this.ttlConfig?.defaultTTL) {\n await this.applyTTL(...writeKeys, zsetKey);\n }\n }\n\n // Update checkpoint to indicate it has writes\n const checkpointKey = `checkpoint:${threadId}:${checkpointNs}:shallow`;\n const checkpointExists = await this.client.exists(checkpointKey);\n if (checkpointExists) {\n const currentDoc = await this.client.json.get(checkpointKey);\n if (currentDoc) {\n currentDoc.has_writes = \"true\";\n await this.client.json.set(checkpointKey, \"$\", currentDoc);\n }\n }\n }\n\n async deleteThread(threadId: string): Promise<void> {\n // Delete shallow checkpoints\n const checkpointPattern = `checkpoint:${threadId}:*:shallow`;\n const checkpointKeys = await this.client.keys(checkpointPattern);\n\n if (checkpointKeys.length > 0) {\n await this.client.del(checkpointKeys);\n }\n\n // Delete writes\n const writesPattern = `checkpoint_write:${threadId}:*`;\n const writesKeys = await this.client.keys(writesPattern);\n\n if (writesKeys.length > 0) {\n await this.client.del(writesKeys);\n }\n\n // Delete write registries\n const zsetPattern = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:*`;\n const zsetKeys = await this.client.keys(zsetPattern);\n\n if (zsetKeys.length > 0) {\n await this.client.del(zsetKeys);\n }\n }\n\n async end(): Promise<void> {\n await this.client.quit();\n }\n\n // Helper method to add searchable metadata fields\n private addSearchableMetadataFields(\n jsonDoc: any,\n metadata?: CheckpointMetadata\n ): void {\n if (!metadata) return;\n\n // Add common searchable fields at top level\n if (\"source\" in metadata) {\n jsonDoc.source = metadata.source;\n }\n if (\"step\" in metadata) {\n jsonDoc.step = metadata.step;\n }\n if (\"writes\" in metadata) {\n // Writes field needs to be JSON stringified for TAG search\n jsonDoc.writes =\n typeof metadata.writes === \"object\"\n ? JSON.stringify(metadata.writes)\n : metadata.writes;\n }\n if (\"score\" in metadata) {\n jsonDoc.score = metadata.score;\n }\n }\n\n // Helper method to create checkpoint tuple from json document\n private async createCheckpointTuple(\n jsonDoc: any,\n checkpoint: Checkpoint,\n pendingWrites?: Array<[string, string, any]>\n ): Promise<CheckpointTuple> {\n // Deserialize metadata using serde\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(jsonDoc.metadata)\n )) as CheckpointMetadata;\n\n return {\n config: {\n configurable: {\n thread_id: jsonDoc.thread_id,\n checkpoint_ns: jsonDoc.checkpoint_ns,\n checkpoint_id: jsonDoc.checkpoint_id,\n },\n },\n checkpoint,\n metadata,\n parentConfig: jsonDoc.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: jsonDoc.thread_id,\n checkpoint_ns: jsonDoc.checkpoint_ns,\n checkpoint_id: jsonDoc.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n\n // Helper method to apply TTL to keys\n private async applyTTL(...keys: string[]): Promise<void> {\n if (!this.ttlConfig?.defaultTTL) return;\n\n const ttlSeconds = Math.floor(this.ttlConfig.defaultTTL * 60);\n const results = await Promise.allSettled(\n keys.map((key) => this.client.expire(key, ttlSeconds))\n );\n\n // Log any failures but don't throw - TTL is best effort\n for (let i = 0; i < results.length; i++) {\n if (results[i].status === \"rejected\") {\n console.warn(\n `Failed to set TTL for key ${keys[i]}:`,\n (results[i] as PromiseRejectedResult).reason\n );\n }\n }\n }\n\n // Helper method to load pending writes\n private async loadPendingWrites(\n threadId: string,\n checkpointNs: string,\n checkpointId: string\n ): Promise<Array<[string, string, any]> | undefined> {\n const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;\n const writeKeys = await this.client.zRange(zsetKey, 0, -1);\n\n if (writeKeys.length === 0) {\n return undefined;\n }\n\n const pendingWrites: Array<[string, string, any]> = [];\n for (const writeKey of writeKeys) {\n const writeDoc = await this.client.json.get(writeKey);\n if (writeDoc) {\n // Deserialize write value using serde to restore LangChain objects\n const deserializedValue = await this.serde.loadsTyped(\n \"json\",\n JSON.stringify(writeDoc.value)\n );\n pendingWrites.push([\n writeDoc.task_id,\n writeDoc.channel,\n deserializedValue,\n ]);\n }\n }\n\n return pendingWrites;\n }\n\n // Helper method to check metadata filter matches\n private checkMetadataFilterMatch(\n metadata: any,\n filter: CheckpointMetadata\n ): boolean {\n for (const [key, value] of Object.entries(filter)) {\n const metadataValue = metadata?.[key];\n if (value === null) {\n if (!(key in (metadata || {})) || metadataValue !== null) {\n return false;\n }\n } else if (typeof value === \"object\" && !Array.isArray(value)) {\n // Deep comparison for objects with deterministic key ordering\n if (typeof metadataValue !== \"object\" || metadataValue === null) {\n return false;\n }\n if (\n deterministicStringify(value) !==\n deterministicStringify(metadataValue)\n ) {\n return false;\n }\n } else if (metadataValue !== value) {\n return false;\n }\n }\n return true;\n }\n\n private async cleanupOldCheckpoint(\n threadId: string,\n checkpointNs: string,\n oldCheckpointId: string\n ): Promise<void> {\n // Clean up old writes\n const writePattern = `checkpoint_write:${threadId}:${checkpointNs}:${oldCheckpointId}:*`;\n const oldWriteKeys = await this.client.keys(writePattern);\n if (oldWriteKeys.length > 0) {\n await this.client.del(oldWriteKeys);\n }\n\n // Clean up write registry\n const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${oldCheckpointId}`;\n await this.client.del(zsetKey);\n\n // Note: We don't clean up blob keys in shallow mode since we store inline\n // But for completeness, clean up any legacy blob keys if they exist\n const blobPattern = `checkpoint_blob:${threadId}:${checkpointNs}:${oldCheckpointId}:*`;\n const oldBlobKeys = await this.client.keys(blobPattern);\n if (oldBlobKeys.length > 0) {\n await this.client.del(oldBlobKeys);\n }\n }\n\n private sanitizeMetadata(metadata: CheckpointMetadata): CheckpointMetadata {\n if (!metadata) return {} as CheckpointMetadata;\n\n const sanitized: any = {};\n for (const [key, value] of Object.entries(metadata)) {\n // Remove null characters from keys and string values\n // eslint-disable-next-line no-control-regex\n const sanitizedKey = key.replace(/\\x00/g, \"\");\n sanitized[sanitizedKey] =\n // eslint-disable-next-line no-control-regex\n typeof value === \"string\" ? value.replace(/\\x00/g, \"\") : value;\n }\n return sanitized as CheckpointMetadata;\n }\n\n private async ensureIndexes(): Promise<void> {\n for (const schema of SCHEMAS) {\n try {\n // Try to create the index\n await this.client.ft.create(schema.index, schema.schema, {\n ON: \"JSON\",\n PREFIX: schema.prefix,\n });\n } catch (error: any) {\n // Ignore if index already exists\n if (!error.message?.includes(\"Index already exists\")) {\n console.error(\n `Failed to create index ${schema.index}:`,\n error.message\n );\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.cts","names":["createClient","createCluster","RedisClientConnection","ReturnType","RedisClusterConnection","RedisConnection","GetOperation","ListNamespacesOperation","Operation","PutOperation","SearchOperation","isPutOperation","isGetOperation","isSearchOperation","isListNamespacesOperation","FilterOperators","FilterValue","Filter","Record","FilterBuilder","Item","Date","SearchItem","IndexConfig","TTLConfig","StoreConfig","RedisStore","Promise","Array"],"sources":["../src/store.d.ts"],"sourcesContent":["import { createClient, createCluster } from \"redis\";\n/** A conventional Redis connection. */\nexport type RedisClientConnection = ReturnType<typeof createClient>;\n/** A clustered Redis connection. */\nexport type RedisClusterConnection = ReturnType<typeof createCluster>;\n/** A Redis connection, clustered or conventional. */\nexport type RedisConnection = RedisClientConnection | RedisClusterConnection;\nimport { type GetOperation, type ListNamespacesOperation, type Operation, type PutOperation, type SearchOperation } from \"@langchain/langgraph-checkpoint\";\nexport declare function isPutOperation(op: Operation): op is PutOperation;\nexport declare function isGetOperation(op: Operation): op is GetOperation;\nexport declare function isSearchOperation(op: Operation): op is SearchOperation;\nexport declare function isListNamespacesOperation(op: Operation): op is ListNamespacesOperation;\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}\nexport type FilterValue = any | FilterOperators;\nexport type Filter = Record<string, FilterValue>;\n/**\n * Internal class for evaluating filters against documents.\n * Supports MongoDB-style query operators.\n */\ndeclare class 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 /**\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(filter: Filter, prefix?: string): {\n query: string;\n useClientFilter: boolean;\n };\n private static matchesFieldFilter;\n private static matchesOperators;\n private static matchesOperator;\n private static isEqual;\n private static getNestedValue;\n}\nexport interface Item {\n value: any;\n key: string;\n namespace: string[];\n created_at: Date;\n updated_at: Date;\n}\nexport interface SearchItem extends Item {\n score?: number;\n}\nexport interface IndexConfig {\n dims: number;\n embed?: any;\n distanceType?: \"cosine\" | \"l2\" | \"ip\";\n fields?: string[];\n vectorStorageType?: string;\n similarityThreshold?: number;\n}\nexport interface TTLConfig {\n defaultTTL?: number;\n refreshOnRead?: boolean;\n}\nexport interface StoreConfig {\n index?: IndexConfig;\n ttl?: TTLConfig;\n}\nexport declare class RedisStore {\n private readonly client;\n private readonly indexConfig?;\n private readonly ttlConfig?;\n private readonly embeddings?;\n constructor(client: RedisConnection, config?: StoreConfig);\n static fromConnString(connString: string, config?: StoreConfig): Promise<RedisStore>;\n static fromCluster(rootNodes: Array<{\n url: string;\n }>, config?: StoreConfig): Promise<RedisStore>;\n setup(): Promise<void>;\n get(namespace: string[], key: string, options?: {\n refreshTTL?: boolean;\n }): Promise<Item | null>;\n put(namespace: string[], key: string, value: any, options?: {\n ttl?: number;\n index?: boolean | string[];\n }): Promise<void>;\n delete(namespace: string[], key: string): Promise<void>;\n search(namespacePrefix: string[], options?: {\n filter?: Filter;\n query?: string;\n limit?: number;\n offset?: number;\n refreshTTL?: boolean;\n similarityThreshold?: number;\n }): Promise<SearchItem[]>;\n listNamespaces(options?: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n }): Promise<string[][]>;\n batch(ops: Operation[]): Promise<any[]>;\n close(): Promise<void>;\n /**\n * Get statistics about the store.\n * Returns document counts and other metrics.\n */\n getStatistics(): Promise<{\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n }>;\n private validateNamespace;\n private refreshItemTTL;\n private escapeTagValue;\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;\n}\nexport { FilterBuilder };\n//# sourceMappingURL=store.d.ts.map"],"mappings":"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"}
1
+ {"version":3,"file":"store.d.cts","names":[],"sources":["../src/store.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","names":["createClient","createCluster","RedisClientConnection","ReturnType","RedisClusterConnection","RedisConnection","GetOperation","ListNamespacesOperation","Operation","PutOperation","SearchOperation","isPutOperation","isGetOperation","isSearchOperation","isListNamespacesOperation","FilterOperators","FilterValue","Filter","Record","FilterBuilder","Item","Date","SearchItem","IndexConfig","TTLConfig","StoreConfig","RedisStore","Promise","Array"],"sources":["../src/store.d.ts"],"sourcesContent":["import { createClient, createCluster } from \"redis\";\n/** A conventional Redis connection. */\nexport type RedisClientConnection = ReturnType<typeof createClient>;\n/** A clustered Redis connection. */\nexport type RedisClusterConnection = ReturnType<typeof createCluster>;\n/** A Redis connection, clustered or conventional. */\nexport type RedisConnection = RedisClientConnection | RedisClusterConnection;\nimport { type GetOperation, type ListNamespacesOperation, type Operation, type PutOperation, type SearchOperation } from \"@langchain/langgraph-checkpoint\";\nexport declare function isPutOperation(op: Operation): op is PutOperation;\nexport declare function isGetOperation(op: Operation): op is GetOperation;\nexport declare function isSearchOperation(op: Operation): op is SearchOperation;\nexport declare function isListNamespacesOperation(op: Operation): op is ListNamespacesOperation;\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}\nexport type FilterValue = any | FilterOperators;\nexport type Filter = Record<string, FilterValue>;\n/**\n * Internal class for evaluating filters against documents.\n * Supports MongoDB-style query operators.\n */\ndeclare class 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 /**\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(filter: Filter, prefix?: string): {\n query: string;\n useClientFilter: boolean;\n };\n private static matchesFieldFilter;\n private static matchesOperators;\n private static matchesOperator;\n private static isEqual;\n private static getNestedValue;\n}\nexport interface Item {\n value: any;\n key: string;\n namespace: string[];\n created_at: Date;\n updated_at: Date;\n}\nexport interface SearchItem extends Item {\n score?: number;\n}\nexport interface IndexConfig {\n dims: number;\n embed?: any;\n distanceType?: \"cosine\" | \"l2\" | \"ip\";\n fields?: string[];\n vectorStorageType?: string;\n similarityThreshold?: number;\n}\nexport interface TTLConfig {\n defaultTTL?: number;\n refreshOnRead?: boolean;\n}\nexport interface StoreConfig {\n index?: IndexConfig;\n ttl?: TTLConfig;\n}\nexport declare class RedisStore {\n private readonly client;\n private readonly indexConfig?;\n private readonly ttlConfig?;\n private readonly embeddings?;\n constructor(client: RedisConnection, config?: StoreConfig);\n static fromConnString(connString: string, config?: StoreConfig): Promise<RedisStore>;\n static fromCluster(rootNodes: Array<{\n url: string;\n }>, config?: StoreConfig): Promise<RedisStore>;\n setup(): Promise<void>;\n get(namespace: string[], key: string, options?: {\n refreshTTL?: boolean;\n }): Promise<Item | null>;\n put(namespace: string[], key: string, value: any, options?: {\n ttl?: number;\n index?: boolean | string[];\n }): Promise<void>;\n delete(namespace: string[], key: string): Promise<void>;\n search(namespacePrefix: string[], options?: {\n filter?: Filter;\n query?: string;\n limit?: number;\n offset?: number;\n refreshTTL?: boolean;\n similarityThreshold?: number;\n }): Promise<SearchItem[]>;\n listNamespaces(options?: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n }): Promise<string[][]>;\n batch(ops: Operation[]): Promise<any[]>;\n close(): Promise<void>;\n /**\n * Get statistics about the store.\n * Returns document counts and other metrics.\n */\n getStatistics(): Promise<{\n totalDocuments: number;\n namespaceCount: number;\n vectorDocuments?: number;\n indexInfo?: Record<string, any>;\n }>;\n private validateNamespace;\n private refreshItemTTL;\n private escapeTagValue;\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;\n}\nexport { FilterBuilder };\n//# sourceMappingURL=store.d.ts.map"],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","names":[],"sources":["../src/store.ts"],"mappings":"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"}
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.3",
4
4
  "description": "Redis checkpoint and store implementation for LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -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",