@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.
- package/dist/constants.cjs +7 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.cjs +7 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/shallow.cjs +8 -5
- package/dist/shallow.cjs.map +1 -1
- package/dist/shallow.d.cts.map +1 -1
- package/dist/shallow.d.ts.map +1 -1
- package/dist/shallow.js +8 -5
- package/dist/shallow.js.map +1 -1
- package/dist/store.d.cts.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.cjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const WRITE_KEYS_ZSET_PREFIX = \"write_keys_zset\";\n"],"mappings":";;AAAA,MAAa,yBAAyB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const WRITE_KEYS_ZSET_PREFIX = \"write_keys_zset\";\n"],"mappings":";AAAA,MAAa,yBAAyB"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_utils = require('./utils.cjs');
|
|
3
|
+
const require_constants = require('./constants.cjs');
|
|
3
4
|
let _langchain_langgraph_checkpoint = require("@langchain/langgraph-checkpoint");
|
|
4
5
|
let redis = require("redis");
|
|
5
6
|
|
|
@@ -171,6 +172,8 @@ var RedisSaver = class RedisSaver extends _langchain_langgraph_checkpoint.BaseCh
|
|
|
171
172
|
for (const channel of Object.keys(newVersions)) if (channel in storedCheckpoint.channel_values) filteredChannelValues[channel] = storedCheckpoint.channel_values[channel];
|
|
172
173
|
storedCheckpoint.channel_values = filteredChannelValues;
|
|
173
174
|
}
|
|
175
|
+
const zsetKey = `${require_constants.WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;
|
|
176
|
+
const writesExist = await this.client.exists(zsetKey);
|
|
174
177
|
const jsonDoc = {
|
|
175
178
|
thread_id: threadId,
|
|
176
179
|
checkpoint_ns: checkpointNs === "" ? "__empty__" : checkpointNs,
|
|
@@ -179,7 +182,7 @@ var RedisSaver = class RedisSaver extends _langchain_langgraph_checkpoint.BaseCh
|
|
|
179
182
|
checkpoint: storedCheckpoint,
|
|
180
183
|
metadata,
|
|
181
184
|
checkpoint_ts: Date.now(),
|
|
182
|
-
has_writes: "false"
|
|
185
|
+
has_writes: writesExist ? "true" : "false"
|
|
183
186
|
};
|
|
184
187
|
this.addSearchableMetadataFields(jsonDoc, metadata);
|
|
185
188
|
await this.client.json.set(key, "$", jsonDoc);
|
|
@@ -196,14 +199,14 @@ var RedisSaver = class RedisSaver extends _langchain_langgraph_checkpoint.BaseCh
|
|
|
196
199
|
const hasNullFilter = Object.values(options.filter).some((v) => v === null);
|
|
197
200
|
const queryParts = [];
|
|
198
201
|
if (config?.configurable?.thread_id) {
|
|
199
|
-
const threadId = config.configurable.thread_id
|
|
202
|
+
const threadId = require_utils.escapeRediSearchTagValue(config.configurable.thread_id);
|
|
200
203
|
queryParts.push(`(@thread_id:{${threadId}})`);
|
|
201
204
|
}
|
|
202
205
|
if (config?.configurable?.checkpoint_ns !== void 0) {
|
|
203
206
|
const checkpointNs = config.configurable.checkpoint_ns;
|
|
204
207
|
if (checkpointNs === "") queryParts.push(`(@checkpoint_ns:{__empty__})`);
|
|
205
208
|
else {
|
|
206
|
-
const escapedNs =
|
|
209
|
+
const escapedNs = require_utils.escapeRediSearchTagValue(checkpointNs);
|
|
207
210
|
queryParts.push(`(@checkpoint_ns:{${escapedNs}})`);
|
|
208
211
|
}
|
|
209
212
|
}
|
|
@@ -384,7 +387,7 @@ var RedisSaver = class RedisSaver extends _langchain_langgraph_checkpoint.BaseCh
|
|
|
384
387
|
await this.client.json.set(writeKey, "$", writeDoc);
|
|
385
388
|
}
|
|
386
389
|
if (writeKeys.length > 0) {
|
|
387
|
-
const zsetKey =
|
|
390
|
+
const zsetKey = `${require_constants.WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;
|
|
388
391
|
const zaddArgs = {};
|
|
389
392
|
writeKeys.forEach((key, idx) => {
|
|
390
393
|
zaddArgs[key] = baseTimestamp + idx;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["BaseCheckpointSaver","escapeRediSearchTagValue","TASKS"],"sources":["../src/index.ts"],"sourcesContent":["import {\n BaseCheckpointSaver,\n ChannelVersions,\n Checkpoint,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n PendingWrite,\n uuid6,\n TASKS,\n maxChannelVersion,\n copyCheckpoint,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { createClient, createCluster } from \"redis\";\nimport { escapeRediSearchTagValue } from \"./utils.js\";\n\n// Type for Redis client - supports both standalone and cluster\nexport type RedisClientType =\n | ReturnType<typeof createClient>\n | ReturnType<typeof createCluster>;\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_blobs\",\n prefix: \"checkpoint_blob:\",\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 \"$.channel\": { type: \"TAG\", AS: \"channel\" },\n \"$.version\": { type: \"TAG\", AS: \"version\" },\n \"$.type\": { type: \"TAG\", AS: \"type\" },\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\nexport interface TTLConfig {\n defaultTTL?: number; // TTL in minutes\n refreshOnRead?: boolean; // Whether to refresh TTL when reading\n}\n\ninterface CheckpointDocument {\n thread_id: string;\n checkpoint_ns: string;\n checkpoint_id: string;\n parent_checkpoint_id: string | null;\n checkpoint: Checkpoint & {\n channel_values?: Record<string, any>;\n channel_blobs?: Record<string, { __blob__: boolean; key: string }>;\n };\n metadata: CheckpointMetadata;\n checkpoint_ts: number;\n has_writes: string;\n source?: string;\n step?: number;\n [key: string]: any; // Allow additional fields for metadata\n}\n\nexport class RedisSaver extends BaseCheckpointSaver {\n private client: RedisClientType;\n private ttlConfig?: TTLConfig;\n\n constructor(client: RedisClientType, 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<RedisSaver> {\n const client = createClient({ url });\n await client.connect();\n const saver = new RedisSaver(client, ttlConfig);\n await saver.ensureIndexes();\n return saver;\n }\n\n static async fromCluster(\n rootNodes: Array<{ url: string }>,\n ttlConfig?: TTLConfig\n ): Promise<RedisSaver> {\n const client = createCluster({ rootNodes });\n await client.connect();\n const saver = new RedisSaver(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 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 let key: string;\n let jsonDoc: CheckpointDocument | null;\n\n if (checkpointId) {\n // Get specific checkpoint\n key = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n jsonDoc = (await this.client.json.get(key)) as CheckpointDocument | null;\n } else {\n // Get latest checkpoint - need to search\n const pattern = `checkpoint:${threadId}:${checkpointNs}:*`;\n // Use keys for simplicity - scan would be better for large datasets\n const keys = await (this.client as any).keys(pattern);\n\n if (keys.length === 0) {\n return undefined;\n }\n\n // Sort by key to get latest\n keys.sort();\n key = keys[keys.length - 1];\n jsonDoc = (await this.client.json.get(key)) as CheckpointDocument | null;\n }\n\n if (!jsonDoc) {\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 // Load checkpoint with pending writes\n const { checkpoint, pendingWrites } = await this.loadCheckpointWithWrites(\n jsonDoc\n );\n\n return await this.createCheckpointTuple(jsonDoc, checkpoint, pendingWrites);\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 const key = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n\n // Copy checkpoint and filter channel_values to only include changed channels\n const storedCheckpoint = copyCheckpoint(checkpoint);\n\n // If newVersions is provided and has keys, only store those channels that changed\n // If newVersions is empty {}, store no channel values\n // If newVersions is not provided (undefined), keep all channel_values as-is\n if (storedCheckpoint.channel_values && newVersions !== undefined) {\n if (Object.keys(newVersions).length === 0) {\n // Empty newVersions means no channels changed - store empty channel_values\n storedCheckpoint.channel_values = {};\n } else {\n // Only store the channels that are in newVersions\n const filteredChannelValues: Record<string, any> = {};\n for (const channel of Object.keys(newVersions)) {\n if (channel in storedCheckpoint.channel_values) {\n filteredChannelValues[channel] =\n storedCheckpoint.channel_values[channel];\n }\n }\n storedCheckpoint.channel_values = filteredChannelValues;\n }\n }\n // If newVersions is undefined, keep all channel_values as-is (for backward compatibility)\n\n // Structure matching Python implementation\n const jsonDoc: CheckpointDocument = {\n thread_id: threadId,\n // Store empty namespace as \"__empty__\" for RediSearch compatibility\n checkpoint_ns: checkpointNs === \"\" ? \"__empty__\" : checkpointNs,\n checkpoint_id: checkpointId,\n parent_checkpoint_id: parentCheckpointId || null,\n checkpoint: storedCheckpoint,\n metadata: 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 as any);\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 *list(\n config: RunnableConfig | null,\n options?: CheckpointListOptions & { filter?: CheckpointMetadata }\n ): AsyncGenerator<CheckpointTuple> {\n await this.ensureIndexes();\n\n // If filter is provided (even if empty), use search functionality\n if (options?.filter !== undefined) {\n // Check if we have null values in the filter which RediSearch can't handle\n const hasNullFilter = Object.values(options.filter).some(\n (v) => v === null\n );\n\n // Build search query\n const queryParts: string[] = [];\n\n // Add thread_id constraint if provided\n if (config?.configurable?.thread_id) {\n const threadId = config.configurable.thread_id.replace(\n /[-.@]/g,\n \"\\\\$&\"\n );\n queryParts.push(`(@thread_id:{${threadId}})`);\n }\n\n // Add checkpoint_ns constraint if provided\n if (config?.configurable?.checkpoint_ns !== undefined) {\n const checkpointNs = config.configurable.checkpoint_ns;\n if (checkpointNs === \"\") {\n // Empty string needs special handling in RediSearch\n // We'll store it as \"__empty__\" in the index\n queryParts.push(`(@checkpoint_ns:{__empty__})`);\n } else {\n const escapedNs = checkpointNs.replace(/[-.@]/g, \"\\\\$&\");\n queryParts.push(`(@checkpoint_ns:{${escapedNs}})`);\n }\n }\n\n // Skip metadata filters in search query when 'before' parameter is used\n // We'll apply them after the before filtering to get correct results\n if (!options?.before && options?.filter) {\n // Add metadata filters (but skip null values)\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 } else if (\n typeof value === \"object\" &&\n Object.keys(value).length === 0\n ) {\n // Skip empty objects\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 // Fetch more results than the limit to handle post-filtering for 'before'\n // When no thread_id is specified but 'before' is used, we need to fetch all results\n const fetchLimit =\n options?.before && !config?.configurable?.thread_id\n ? 1000 // Fetch many results for global search with 'before' filtering\n : options?.before\n ? limit * 10\n : limit;\n\n const results = await this.client.ft.search(\"checkpoints\", query, {\n LIMIT: { from: 0, size: fetchLimit },\n SORTBY: { BY: \"checkpoint_ts\", DIRECTION: \"DESC\" },\n });\n\n let documents = results.documents;\n\n let yieldedCount = 0;\n\n for (const doc of documents) {\n if (yieldedCount >= limit) break;\n\n // Handle 'before' parameter - filter based on checkpoint_id comparison\n // UUID6 IDs are time-sortable, so string comparison works for ordering\n if (options?.before?.configurable?.checkpoint_id) {\n const currentCheckpointId = doc.value.checkpoint_id as string;\n const beforeCheckpointId =\n options.before.configurable.checkpoint_id;\n\n // Skip checkpoints that are not before the specified checkpoint\n if (currentCheckpointId >= beforeCheckpointId) {\n continue;\n }\n }\n\n const jsonDoc = doc.value;\n\n // Apply metadata filters manually (either for null filters or when before parameter was used)\n let matches = true;\n if ((hasNullFilter || options?.before) && options?.filter) {\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n if (filterValue === null) {\n // Check if the field exists and is null in metadata\n // This should only match explicit null, not missing fields\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (filterValue !== undefined) {\n // Check other metadata values\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n // For objects, do deep equality check with deterministic key ordering\n if (typeof filterValue === \"object\" && filterValue !== null) {\n if (\n deterministicStringify(metadataValue) !==\n deterministicStringify(filterValue)\n ) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n }\n if (!matches) continue;\n }\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n yieldedCount++;\n }\n\n // Search succeeded, return without falling through\n return;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n // Index doesn't exist yet, fall through to regular listing\n } else {\n throw error;\n }\n }\n\n // If search failed due to missing index, fall through to regular listing\n if (config?.configurable?.thread_id) {\n // Fall back to regular listing with manual filtering when thread_id is specified\n const threadId = config.configurable.thread_id;\n const checkpointNs = config.configurable.checkpoint_ns ?? \"\";\n const pattern = `checkpoint:${threadId}:${checkpointNs}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const keys = await (this.client as any).keys(pattern);\n\n keys.sort().reverse();\n\n let filteredKeys = keys;\n\n // Handle 'before' parameter\n if (options?.before?.configurable?.checkpoint_id) {\n const beforeThreadId =\n options.before.configurable.thread_id || threadId;\n const beforeCheckpointNs =\n options.before.configurable.checkpoint_ns ?? checkpointNs;\n const beforeKey = `checkpoint:${beforeThreadId}:${beforeCheckpointNs}:${options.before.configurable.checkpoint_id}`;\n\n const beforeIndex = keys.indexOf(beforeKey);\n if (beforeIndex > 0) {\n // Return all items that come after the found index (i.e., before in time)\n filteredKeys = keys.slice(beforeIndex + 1);\n } else if (beforeIndex === 0) {\n // Nothing before the first item (most recent)\n filteredKeys = [];\n }\n // If not found, return all\n }\n\n const limit = options?.limit ?? 10;\n const limitedKeys = filteredKeys.slice(0, limit);\n\n for (const key of limitedKeys) {\n const jsonDoc = (await this.client.json.get(\n key\n )) as CheckpointDocument | null;\n if (jsonDoc) {\n // Check if metadata matches filter\n let matches = true;\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (filterValue === null) {\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n\n if (!matches) continue;\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n }\n }\n } else {\n // Fall back to global search when thread_id is undefined\n // This is needed for validation tests that search globally with 'before' parameter\n const globalPattern =\n config?.configurable?.checkpoint_ns !== undefined\n ? `checkpoint:*:${\n config.configurable.checkpoint_ns === \"\"\n ? \"__empty__\"\n : config.configurable.checkpoint_ns\n }:*`\n : \"checkpoint:*\";\n\n const allKeys = await (this.client as any).keys(globalPattern);\n const allDocuments: { key: string; doc: CheckpointDocument }[] = [];\n\n // Load all matching documents\n for (const key of allKeys) {\n const jsonDoc = (await this.client.json.get(\n key\n )) as CheckpointDocument | null;\n if (jsonDoc) {\n allDocuments.push({ key, doc: jsonDoc });\n }\n }\n\n // Sort by timestamp (descending) to match the search behavior\n allDocuments.sort((a, b) => b.doc.checkpoint_ts - a.doc.checkpoint_ts);\n\n let yieldedCount = 0;\n const limit = options?.limit ?? 10;\n\n for (const { doc: jsonDoc } of allDocuments) {\n if (yieldedCount >= limit) break;\n\n // Handle 'before' parameter - filter based on checkpoint_id comparison\n if (options?.before?.configurable?.checkpoint_id) {\n const currentCheckpointId = jsonDoc.checkpoint_id;\n const beforeCheckpointId =\n options.before.configurable.checkpoint_id;\n\n // Skip checkpoints that are not before the specified checkpoint\n if (currentCheckpointId >= beforeCheckpointId) {\n continue;\n }\n }\n\n // Apply metadata filters manually\n let matches = true;\n if (options?.filter) {\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n if (filterValue === null) {\n // Check if the field exists and is null in metadata\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (filterValue !== undefined) {\n // Check other metadata values\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n // For objects, do deep equality check with deterministic key ordering\n if (typeof filterValue === \"object\" && filterValue !== null) {\n if (\n deterministicStringify(metadataValue) !==\n deterministicStringify(filterValue)\n ) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n }\n if (!matches) continue;\n }\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n yieldedCount++;\n }\n }\n\n return;\n }\n\n // Regular listing without filter - use search with empty filter instead\n // This ensures consistent behavior between filter={} and filter=undefined\n const searchOptions: CheckpointListOptions & {\n filter?: CheckpointMetadata;\n } = {\n ...options,\n filter: {} as CheckpointMetadata,\n };\n\n // Delegate to the search path\n yield* this.list(config, searchOptions);\n return;\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 // Collect write keys for sorted set tracking\n const writeKeys: string[] = [];\n\n // Use high-resolution timestamp to ensure unique ordering across putWrites calls\n const baseTimestamp = performance.now() * 1000; // Microsecond precision\n\n // Store each write as a separate indexed JSON document\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 timestamp: baseTimestamp,\n global_idx: baseTimestamp + idx, // Add microseconds for sub-millisecond ordering\n };\n\n await this.client.json.set(writeKey, \"$\", writeDoc as any);\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 // Use timestamp + idx for scoring to maintain correct order\n const zaddArgs: Record<string, number> = {};\n writeKeys.forEach((key, idx) => {\n zaddArgs[key] = baseTimestamp + 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 // Apply TTL to write keys and zset\n await this.applyTTL(...writeKeys, zsetKey);\n }\n }\n\n // Update checkpoint to indicate it has writes\n const checkpointKey = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n const checkpointExists = await this.client.exists(checkpointKey);\n if (checkpointExists) {\n // Get the current document and update it\n const currentDoc = (await this.client.json.get(\n checkpointKey\n )) as CheckpointDocument | null;\n if (currentDoc) {\n currentDoc.has_writes = \"true\";\n await this.client.json.set(checkpointKey, \"$\", currentDoc as any);\n }\n }\n }\n\n async deleteThread(threadId: string): Promise<void> {\n // Delete checkpoints\n const checkpointPattern = `checkpoint:${threadId}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const checkpointKeys = await (this.client as any).keys(checkpointPattern);\n\n if (checkpointKeys.length > 0) {\n await this.client.del(checkpointKeys);\n }\n\n // Delete writes\n const writesPattern = `writes:${threadId}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const writesKeys = await (this.client as any).keys(writesPattern);\n\n if (writesKeys.length > 0) {\n await this.client.del(writesKeys);\n }\n }\n\n async end(): Promise<void> {\n await this.client.quit();\n }\n\n // Helper method to load channel blobs (simplified - no blob support for now)\n // private async loadChannelBlobs(\n // checkpoint: Checkpoint & { channel_blobs?: any }\n // ): Promise<Checkpoint> {\n // // Since we're not using blobs anymore, just return the checkpoint as-is\n // return checkpoint;\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 // Search for all write documents for this checkpoint\n const pattern = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:*`;\n const writeKeys = await (this.client as any).keys(pattern);\n\n if (writeKeys.length === 0) {\n return undefined;\n }\n\n const writeDocuments: any[] = [];\n for (const writeKey of writeKeys) {\n const writeDoc = (await this.client.json.get(writeKey)) as any;\n if (writeDoc) {\n writeDocuments.push(writeDoc);\n }\n }\n\n // Sort by global_idx (which represents insertion order across all putWrites calls)\n // This matches how SQLite would naturally order by insertion time + idx\n writeDocuments.sort((a, b) => (a.global_idx || 0) - (b.global_idx || 0));\n\n const pendingWrites: Array<[string, string, any]> = [];\n for (const writeDoc of writeDocuments) {\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 return pendingWrites;\n }\n\n // Helper method to load checkpoint with pending writes\n private async loadCheckpointWithWrites(jsonDoc: any): Promise<{\n checkpoint: Checkpoint;\n pendingWrites?: Array<[string, string, any]>;\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 // Migrate pending sends ONLY for OLD checkpoint versions (v < 4) with parents\n // Modern checkpoints (v >= 4) should NEVER have pending sends migrated\n if (checkpoint.v < 4 && jsonDoc.parent_checkpoint_id != null) {\n // Convert back from \"__empty__\" to empty string for migration\n const actualNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n await this.migratePendingSends(\n checkpoint,\n jsonDoc.thread_id,\n actualNs,\n jsonDoc.parent_checkpoint_id\n );\n }\n\n // Load this checkpoint's own pending writes (but don't migrate them)\n let pendingWrites: Array<[string, string, any]> | undefined;\n if (jsonDoc.has_writes === \"true\") {\n // Convert back from \"__empty__\" to empty string for key lookup\n const actualNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n pendingWrites = await this.loadPendingWrites(\n jsonDoc.thread_id,\n actualNs,\n jsonDoc.checkpoint_id\n );\n }\n\n return { checkpoint, pendingWrites };\n }\n\n // Migrate pending sends from parent checkpoint (matches SQLite implementation)\n private async migratePendingSends(\n checkpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ): Promise<void> {\n // Load pending writes from parent checkpoint that have TASKS channel\n const parentWrites = await this.loadPendingWrites(\n threadId,\n checkpointNs,\n parentCheckpointId\n );\n\n if (!parentWrites || parentWrites.length === 0) {\n return;\n }\n\n // Filter for TASKS channel writes only\n const taskWrites = parentWrites.filter(([, channel]) => channel === TASKS);\n\n if (taskWrites.length === 0) {\n return;\n }\n\n // Collect all task values in order\n const allTasks: any[] = [];\n for (const [, , value] of taskWrites) {\n allTasks.push(value);\n }\n\n // Add pending sends to checkpoint\n checkpoint.channel_values ??= {};\n checkpoint.channel_values[TASKS] = allTasks;\n\n // Add to versions (matches SQLite logic)\n checkpoint.channel_versions[TASKS] =\n Object.keys(checkpoint.channel_versions).length > 0\n ? maxChannelVersion(...Object.values(checkpoint.channel_versions))\n : 1;\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 // Convert back from \"__empty__\" to empty string\n const checkpointNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n\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: checkpointNs,\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: checkpointNs,\n checkpoint_id: jsonDoc.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\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 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 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 as any, {\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\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["BaseCheckpointSaver","WRITE_KEYS_ZSET_PREFIX","escapeRediSearchTagValue","TASKS"],"sources":["../src/index.ts"],"sourcesContent":["import {\n BaseCheckpointSaver,\n ChannelVersions,\n Checkpoint,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n PendingWrite,\n uuid6,\n TASKS,\n maxChannelVersion,\n copyCheckpoint,\n} from \"@langchain/langgraph-checkpoint\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { createClient, createCluster } from \"redis\";\nimport { escapeRediSearchTagValue } from \"./utils.js\";\nimport { WRITE_KEYS_ZSET_PREFIX } from \"./constants.js\";\n\n// Type for Redis client - supports both standalone and cluster\nexport type RedisClientType =\n | ReturnType<typeof createClient>\n | ReturnType<typeof createCluster>;\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_blobs\",\n prefix: \"checkpoint_blob:\",\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 \"$.channel\": { type: \"TAG\", AS: \"channel\" },\n \"$.version\": { type: \"TAG\", AS: \"version\" },\n \"$.type\": { type: \"TAG\", AS: \"type\" },\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\nexport interface TTLConfig {\n defaultTTL?: number; // TTL in minutes\n refreshOnRead?: boolean; // Whether to refresh TTL when reading\n}\n\ninterface CheckpointDocument {\n thread_id: string;\n checkpoint_ns: string;\n checkpoint_id: string;\n parent_checkpoint_id: string | null;\n checkpoint: Checkpoint & {\n channel_values?: Record<string, any>;\n channel_blobs?: Record<string, { __blob__: boolean; key: string }>;\n };\n metadata: CheckpointMetadata;\n checkpoint_ts: number;\n has_writes: string;\n source?: string;\n step?: number;\n [key: string]: any; // Allow additional fields for metadata\n}\n\nexport class RedisSaver extends BaseCheckpointSaver {\n private client: RedisClientType;\n private ttlConfig?: TTLConfig;\n\n constructor(client: RedisClientType, 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<RedisSaver> {\n const client = createClient({ url });\n await client.connect();\n const saver = new RedisSaver(client, ttlConfig);\n await saver.ensureIndexes();\n return saver;\n }\n\n static async fromCluster(\n rootNodes: Array<{ url: string }>,\n ttlConfig?: TTLConfig\n ): Promise<RedisSaver> {\n const client = createCluster({ rootNodes });\n await client.connect();\n const saver = new RedisSaver(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 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 let key: string;\n let jsonDoc: CheckpointDocument | null;\n\n if (checkpointId) {\n // Get specific checkpoint\n key = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n jsonDoc = (await this.client.json.get(key)) as CheckpointDocument | null;\n } else {\n // Get latest checkpoint - need to search\n const pattern = `checkpoint:${threadId}:${checkpointNs}:*`;\n // Use keys for simplicity - scan would be better for large datasets\n const keys = await (this.client as any).keys(pattern);\n\n if (keys.length === 0) {\n return undefined;\n }\n\n // Sort by key to get latest\n keys.sort();\n key = keys[keys.length - 1];\n jsonDoc = (await this.client.json.get(key)) as CheckpointDocument | null;\n }\n\n if (!jsonDoc) {\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 // Load checkpoint with pending writes\n const { checkpoint, pendingWrites } = await this.loadCheckpointWithWrites(\n jsonDoc\n );\n\n return await this.createCheckpointTuple(jsonDoc, checkpoint, pendingWrites);\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 const key = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n\n // Copy checkpoint and filter channel_values to only include changed channels\n const storedCheckpoint = copyCheckpoint(checkpoint);\n\n // If newVersions is provided and has keys, only store those channels that changed\n // If newVersions is empty {}, store no channel values\n // If newVersions is not provided (undefined), keep all channel_values as-is\n if (storedCheckpoint.channel_values && newVersions !== undefined) {\n if (Object.keys(newVersions).length === 0) {\n // Empty newVersions means no channels changed - store empty channel_values\n storedCheckpoint.channel_values = {};\n } else {\n // Only store the channels that are in newVersions\n const filteredChannelValues: Record<string, any> = {};\n for (const channel of Object.keys(newVersions)) {\n if (channel in storedCheckpoint.channel_values) {\n filteredChannelValues[channel] =\n storedCheckpoint.channel_values[channel];\n }\n }\n storedCheckpoint.channel_values = filteredChannelValues;\n }\n }\n // If newVersions is undefined, keep all channel_values as-is (for backward compatibility)\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: CheckpointDocument = {\n thread_id: threadId,\n // Store empty namespace as \"__empty__\" for RediSearch compatibility\n checkpoint_ns: checkpointNs === \"\" ? \"__empty__\" : checkpointNs,\n checkpoint_id: checkpointId,\n parent_checkpoint_id: parentCheckpointId || null,\n checkpoint: storedCheckpoint,\n metadata: 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 as any);\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 *list(\n config: RunnableConfig | null,\n options?: CheckpointListOptions & { filter?: CheckpointMetadata }\n ): AsyncGenerator<CheckpointTuple> {\n await this.ensureIndexes();\n\n // If filter is provided (even if empty), use search functionality\n if (options?.filter !== undefined) {\n // Check if we have null values in the filter which RediSearch can't handle\n const hasNullFilter = Object.values(options.filter).some(\n (v) => v === null\n );\n\n // Build search query\n const queryParts: string[] = [];\n\n // Add thread_id constraint if provided\n if (config?.configurable?.thread_id) {\n const threadId = escapeRediSearchTagValue(\n config.configurable.thread_id\n );\n queryParts.push(`(@thread_id:{${threadId}})`);\n }\n\n // Add checkpoint_ns constraint if provided\n if (config?.configurable?.checkpoint_ns !== undefined) {\n const checkpointNs = config.configurable.checkpoint_ns;\n if (checkpointNs === \"\") {\n // Empty string needs special handling in RediSearch\n // We'll store it as \"__empty__\" in the index\n queryParts.push(`(@checkpoint_ns:{__empty__})`);\n } else {\n const escapedNs = escapeRediSearchTagValue(checkpointNs);\n queryParts.push(`(@checkpoint_ns:{${escapedNs}})`);\n }\n }\n\n // Skip metadata filters in search query when 'before' parameter is used\n // We'll apply them after the before filtering to get correct results\n if (!options?.before && options?.filter) {\n // Add metadata filters (but skip null values)\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 } else if (\n typeof value === \"object\" &&\n Object.keys(value).length === 0\n ) {\n // Skip empty objects\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 // Fetch more results than the limit to handle post-filtering for 'before'\n // When no thread_id is specified but 'before' is used, we need to fetch all results\n const fetchLimit =\n options?.before && !config?.configurable?.thread_id\n ? 1000 // Fetch many results for global search with 'before' filtering\n : options?.before\n ? limit * 10\n : limit;\n\n const results = await this.client.ft.search(\"checkpoints\", query, {\n LIMIT: { from: 0, size: fetchLimit },\n SORTBY: { BY: \"checkpoint_ts\", DIRECTION: \"DESC\" },\n });\n\n let documents = results.documents;\n\n let yieldedCount = 0;\n\n for (const doc of documents) {\n if (yieldedCount >= limit) break;\n\n // Handle 'before' parameter - filter based on checkpoint_id comparison\n // UUID6 IDs are time-sortable, so string comparison works for ordering\n if (options?.before?.configurable?.checkpoint_id) {\n const currentCheckpointId = doc.value.checkpoint_id as string;\n const beforeCheckpointId =\n options.before.configurable.checkpoint_id;\n\n // Skip checkpoints that are not before the specified checkpoint\n if (currentCheckpointId >= beforeCheckpointId) {\n continue;\n }\n }\n\n const jsonDoc = doc.value;\n\n // Apply metadata filters manually (either for null filters or when before parameter was used)\n let matches = true;\n if ((hasNullFilter || options?.before) && options?.filter) {\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n if (filterValue === null) {\n // Check if the field exists and is null in metadata\n // This should only match explicit null, not missing fields\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (filterValue !== undefined) {\n // Check other metadata values\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n // For objects, do deep equality check with deterministic key ordering\n if (typeof filterValue === \"object\" && filterValue !== null) {\n if (\n deterministicStringify(metadataValue) !==\n deterministicStringify(filterValue)\n ) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n }\n if (!matches) continue;\n }\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n yieldedCount++;\n }\n\n // Search succeeded, return without falling through\n return;\n } catch (error: any) {\n if (error.message?.includes(\"no such index\")) {\n // Index doesn't exist yet, fall through to regular listing\n } else {\n throw error;\n }\n }\n\n // If search failed due to missing index, fall through to regular listing\n if (config?.configurable?.thread_id) {\n // Fall back to regular listing with manual filtering when thread_id is specified\n const threadId = config.configurable.thread_id;\n const checkpointNs = config.configurable.checkpoint_ns ?? \"\";\n const pattern = `checkpoint:${threadId}:${checkpointNs}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const keys = await (this.client as any).keys(pattern);\n\n keys.sort().reverse();\n\n let filteredKeys = keys;\n\n // Handle 'before' parameter\n if (options?.before?.configurable?.checkpoint_id) {\n const beforeThreadId =\n options.before.configurable.thread_id || threadId;\n const beforeCheckpointNs =\n options.before.configurable.checkpoint_ns ?? checkpointNs;\n const beforeKey = `checkpoint:${beforeThreadId}:${beforeCheckpointNs}:${options.before.configurable.checkpoint_id}`;\n\n const beforeIndex = keys.indexOf(beforeKey);\n if (beforeIndex > 0) {\n // Return all items that come after the found index (i.e., before in time)\n filteredKeys = keys.slice(beforeIndex + 1);\n } else if (beforeIndex === 0) {\n // Nothing before the first item (most recent)\n filteredKeys = [];\n }\n // If not found, return all\n }\n\n const limit = options?.limit ?? 10;\n const limitedKeys = filteredKeys.slice(0, limit);\n\n for (const key of limitedKeys) {\n const jsonDoc = (await this.client.json.get(\n key\n )) as CheckpointDocument | null;\n if (jsonDoc) {\n // Check if metadata matches filter\n let matches = true;\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (filterValue === null) {\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n\n if (!matches) continue;\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n }\n }\n } else {\n // Fall back to global search when thread_id is undefined\n // This is needed for validation tests that search globally with 'before' parameter\n const globalPattern =\n config?.configurable?.checkpoint_ns !== undefined\n ? `checkpoint:*:${\n config.configurable.checkpoint_ns === \"\"\n ? \"__empty__\"\n : config.configurable.checkpoint_ns\n }:*`\n : \"checkpoint:*\";\n\n const allKeys = await (this.client as any).keys(globalPattern);\n const allDocuments: { key: string; doc: CheckpointDocument }[] = [];\n\n // Load all matching documents\n for (const key of allKeys) {\n const jsonDoc = (await this.client.json.get(\n key\n )) as CheckpointDocument | null;\n if (jsonDoc) {\n allDocuments.push({ key, doc: jsonDoc });\n }\n }\n\n // Sort by timestamp (descending) to match the search behavior\n allDocuments.sort((a, b) => b.doc.checkpoint_ts - a.doc.checkpoint_ts);\n\n let yieldedCount = 0;\n const limit = options?.limit ?? 10;\n\n for (const { doc: jsonDoc } of allDocuments) {\n if (yieldedCount >= limit) break;\n\n // Handle 'before' parameter - filter based on checkpoint_id comparison\n if (options?.before?.configurable?.checkpoint_id) {\n const currentCheckpointId = jsonDoc.checkpoint_id;\n const beforeCheckpointId =\n options.before.configurable.checkpoint_id;\n\n // Skip checkpoints that are not before the specified checkpoint\n if (currentCheckpointId >= beforeCheckpointId) {\n continue;\n }\n }\n\n // Apply metadata filters manually\n let matches = true;\n if (options?.filter) {\n for (const [filterKey, filterValue] of Object.entries(\n options.filter\n )) {\n if (filterValue === null) {\n // Check if the field exists and is null in metadata\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n if (metadataValue !== null) {\n matches = false;\n break;\n }\n } else if (filterValue !== undefined) {\n // Check other metadata values\n const metadataValue = (jsonDoc.metadata as any)?.[filterKey];\n // For objects, do deep equality check with deterministic key ordering\n if (typeof filterValue === \"object\" && filterValue !== null) {\n if (\n deterministicStringify(metadataValue) !==\n deterministicStringify(filterValue)\n ) {\n matches = false;\n break;\n }\n } else if (metadataValue !== filterValue) {\n matches = false;\n break;\n }\n }\n }\n if (!matches) continue;\n }\n\n // Load checkpoint with pending writes and migrate sends\n const { checkpoint, pendingWrites } =\n await this.loadCheckpointWithWrites(jsonDoc);\n yield await this.createCheckpointTuple(\n jsonDoc,\n checkpoint,\n pendingWrites\n );\n yieldedCount++;\n }\n }\n\n return;\n }\n\n // Regular listing without filter - use search with empty filter instead\n // This ensures consistent behavior between filter={} and filter=undefined\n const searchOptions: CheckpointListOptions & {\n filter?: CheckpointMetadata;\n } = {\n ...options,\n filter: {} as CheckpointMetadata,\n };\n\n // Delegate to the search path\n yield* this.list(config, searchOptions);\n return;\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 // Collect write keys for sorted set tracking\n const writeKeys: string[] = [];\n\n // Use high-resolution timestamp to ensure unique ordering across putWrites calls\n const baseTimestamp = performance.now() * 1000; // Microsecond precision\n\n // Store each write as a separate indexed JSON document\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 timestamp: baseTimestamp,\n global_idx: baseTimestamp + idx, // Add microseconds for sub-millisecond ordering\n };\n\n await this.client.json.set(writeKey, \"$\", writeDoc as any);\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 // Use timestamp + idx for scoring to maintain correct order\n const zaddArgs: Record<string, number> = {};\n writeKeys.forEach((key, idx) => {\n zaddArgs[key] = baseTimestamp + 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 // Apply TTL to write keys and zset\n await this.applyTTL(...writeKeys, zsetKey);\n }\n }\n\n // Update checkpoint to indicate it has writes\n const checkpointKey = `checkpoint:${threadId}:${checkpointNs}:${checkpointId}`;\n const checkpointExists = await this.client.exists(checkpointKey);\n if (checkpointExists) {\n // Get the current document and update it\n const currentDoc = (await this.client.json.get(\n checkpointKey\n )) as CheckpointDocument | null;\n if (currentDoc) {\n currentDoc.has_writes = \"true\";\n await this.client.json.set(checkpointKey, \"$\", currentDoc as any);\n }\n }\n }\n\n async deleteThread(threadId: string): Promise<void> {\n // Delete checkpoints\n const checkpointPattern = `checkpoint:${threadId}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const checkpointKeys = await (this.client as any).keys(checkpointPattern);\n\n if (checkpointKeys.length > 0) {\n await this.client.del(checkpointKeys);\n }\n\n // Delete writes\n const writesPattern = `writes:${threadId}:*`;\n // Use scan for better performance and cluster compatibility\n // Use keys for simplicity - scan would be better for large datasets\n const writesKeys = await (this.client as any).keys(writesPattern);\n\n if (writesKeys.length > 0) {\n await this.client.del(writesKeys);\n }\n }\n\n async end(): Promise<void> {\n await this.client.quit();\n }\n\n // Helper method to load channel blobs (simplified - no blob support for now)\n // private async loadChannelBlobs(\n // checkpoint: Checkpoint & { channel_blobs?: any }\n // ): Promise<Checkpoint> {\n // // Since we're not using blobs anymore, just return the checkpoint as-is\n // return checkpoint;\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 // Search for all write documents for this checkpoint\n const pattern = `checkpoint_write:${threadId}:${checkpointNs}:${checkpointId}:*`;\n const writeKeys = await (this.client as any).keys(pattern);\n\n if (writeKeys.length === 0) {\n return undefined;\n }\n\n const writeDocuments: any[] = [];\n for (const writeKey of writeKeys) {\n const writeDoc = (await this.client.json.get(writeKey)) as any;\n if (writeDoc) {\n writeDocuments.push(writeDoc);\n }\n }\n\n // Sort by global_idx (which represents insertion order across all putWrites calls)\n // This matches how SQLite would naturally order by insertion time + idx\n writeDocuments.sort((a, b) => (a.global_idx || 0) - (b.global_idx || 0));\n\n const pendingWrites: Array<[string, string, any]> = [];\n for (const writeDoc of writeDocuments) {\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 return pendingWrites;\n }\n\n // Helper method to load checkpoint with pending writes\n private async loadCheckpointWithWrites(jsonDoc: any): Promise<{\n checkpoint: Checkpoint;\n pendingWrites?: Array<[string, string, any]>;\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 // Migrate pending sends ONLY for OLD checkpoint versions (v < 4) with parents\n // Modern checkpoints (v >= 4) should NEVER have pending sends migrated\n if (checkpoint.v < 4 && jsonDoc.parent_checkpoint_id != null) {\n // Convert back from \"__empty__\" to empty string for migration\n const actualNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n await this.migratePendingSends(\n checkpoint,\n jsonDoc.thread_id,\n actualNs,\n jsonDoc.parent_checkpoint_id\n );\n }\n\n // Load this checkpoint's own pending writes (but don't migrate them)\n let pendingWrites: Array<[string, string, any]> | undefined;\n if (jsonDoc.has_writes === \"true\") {\n // Convert back from \"__empty__\" to empty string for key lookup\n const actualNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n pendingWrites = await this.loadPendingWrites(\n jsonDoc.thread_id,\n actualNs,\n jsonDoc.checkpoint_id\n );\n }\n\n return { checkpoint, pendingWrites };\n }\n\n // Migrate pending sends from parent checkpoint (matches SQLite implementation)\n private async migratePendingSends(\n checkpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ): Promise<void> {\n // Load pending writes from parent checkpoint that have TASKS channel\n const parentWrites = await this.loadPendingWrites(\n threadId,\n checkpointNs,\n parentCheckpointId\n );\n\n if (!parentWrites || parentWrites.length === 0) {\n return;\n }\n\n // Filter for TASKS channel writes only\n const taskWrites = parentWrites.filter(([, channel]) => channel === TASKS);\n\n if (taskWrites.length === 0) {\n return;\n }\n\n // Collect all task values in order\n const allTasks: any[] = [];\n for (const [, , value] of taskWrites) {\n allTasks.push(value);\n }\n\n // Add pending sends to checkpoint\n checkpoint.channel_values ??= {};\n checkpoint.channel_values[TASKS] = allTasks;\n\n // Add to versions (matches SQLite logic)\n checkpoint.channel_versions[TASKS] =\n Object.keys(checkpoint.channel_versions).length > 0\n ? maxChannelVersion(...Object.values(checkpoint.channel_versions))\n : 1;\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 // Convert back from \"__empty__\" to empty string\n const checkpointNs =\n jsonDoc.checkpoint_ns === \"__empty__\" ? \"\" : jsonDoc.checkpoint_ns;\n\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: checkpointNs,\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: checkpointNs,\n checkpoint_id: jsonDoc.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\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 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 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 as any, {\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\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"],"mappings":"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"}
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;KAmBY,eAAA,GACR,UAAA,QAAkB,YAAA,IAClB,UAAA,QAAkB,aAAA;AAAA,UA4CL,SAAA;EACf,UAAA;EACA,aAAA;AAAA;AAAA,cAoBW,UAAA,SAAmB,mBAAA;EAAA,QACtB,MAAA;EAAA,QACA,SAAA;EAER,WAAA,CAAY,MAAA,EAAQ,eAAA,EAAiB,SAAA,GAAY,SAAA;EAAA,OAMpC,OAAA,CACX,GAAA,UACA,SAAA,GAAY,SAAA,GACX,OAAA,CAAQ,UAAA;EAAA,OAQE,WAAA,CACX,SAAA,EAAW,KAAA;IAAQ,GAAA;EAAA,IACnB,SAAA,GAAY,SAAA,GACX,OAAA,CAAQ,UAAA;EAQL,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,UAAA;EAKrC,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAiD1C,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,EACV,WAAA,EAAa,eAAA,GACZ,OAAA,CAAQ,cAAA;EA2EJ,IAAA,CACL,MAAA,EAAQ,cAAA,SACR,OAAA,GAAU,qBAAA;IAA0B,MAAA,GAAS,kBAAA;EAAA,IAC5C,cAAA,CAAe,eAAA;EAoVZ,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA2EG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAsBhC,GAAA,CAAA,GAAO,OAAA;EAAA,QAaC,iBAAA;EAAA,QA2CA,wBAAA;EAAA,QAyCA,mBAAA;EAAA,QA0CA,qBAAA;EAAA,QAuCN,2BAAA;EAAA,QA0BM,QAAA;EAAA,QAmBA,aAAA;AAAA"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;KAmBY,eAAA,GACR,UAAA,QAAkB,YAAA,IAClB,UAAA,QAAkB,aAAA;AAAA,UA4CL,SAAA;EACf,UAAA;EACA,aAAA;AAAA;AAAA,cAoBW,UAAA,SAAmB,mBAAA;EAAA,QACtB,MAAA;EAAA,QACA,SAAA;EAER,WAAA,CAAY,MAAA,EAAQ,eAAA,EAAiB,SAAA,GAAY,SAAA;EAAA,OAMpC,OAAA,CACX,GAAA,UACA,SAAA,GAAY,SAAA,GACX,OAAA,CAAQ,UAAA;EAAA,OAQE,WAAA,CACX,SAAA,EAAW,KAAA;IAAQ,GAAA;EAAA,IACnB,SAAA,GAAY,SAAA,GACX,OAAA,CAAQ,UAAA;EAQL,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,UAAA;EAKrC,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAiD1C,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,EACV,WAAA,EAAa,eAAA,GACZ,OAAA,CAAQ,cAAA;EA2EJ,IAAA,CACL,MAAA,EAAQ,cAAA,SACR,OAAA,GAAU,qBAAA;IAA0B,MAAA,GAAS,kBAAA;EAAA,IAC5C,cAAA,CAAe,eAAA;EAoVZ,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA2EG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAsBhC,GAAA,CAAA,GAAO,OAAA;EAAA,QAaC,iBAAA;EAAA,QA2CA,wBAAA;EAAA,QAyCA,mBAAA;EAAA,QA0CA,qBAAA;EAAA,QAuCN,2BAAA;EAAA,QA0BM,QAAA;EAAA,QAmBA,aAAA;AAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeRediSearchTagValue } from "./utils.js";
|
|
2
|
+
import { WRITE_KEYS_ZSET_PREFIX } from "./constants.js";
|
|
2
3
|
import { BaseCheckpointSaver, TASKS, copyCheckpoint, maxChannelVersion, uuid6 } from "@langchain/langgraph-checkpoint";
|
|
3
4
|
import { createClient, createCluster } from "redis";
|
|
4
5
|
|
|
@@ -170,6 +171,8 @@ var RedisSaver = class RedisSaver extends BaseCheckpointSaver {
|
|
|
170
171
|
for (const channel of Object.keys(newVersions)) if (channel in storedCheckpoint.channel_values) filteredChannelValues[channel] = storedCheckpoint.channel_values[channel];
|
|
171
172
|
storedCheckpoint.channel_values = filteredChannelValues;
|
|
172
173
|
}
|
|
174
|
+
const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;
|
|
175
|
+
const writesExist = await this.client.exists(zsetKey);
|
|
173
176
|
const jsonDoc = {
|
|
174
177
|
thread_id: threadId,
|
|
175
178
|
checkpoint_ns: checkpointNs === "" ? "__empty__" : checkpointNs,
|
|
@@ -178,7 +181,7 @@ var RedisSaver = class RedisSaver extends BaseCheckpointSaver {
|
|
|
178
181
|
checkpoint: storedCheckpoint,
|
|
179
182
|
metadata,
|
|
180
183
|
checkpoint_ts: Date.now(),
|
|
181
|
-
has_writes: "false"
|
|
184
|
+
has_writes: writesExist ? "true" : "false"
|
|
182
185
|
};
|
|
183
186
|
this.addSearchableMetadataFields(jsonDoc, metadata);
|
|
184
187
|
await this.client.json.set(key, "$", jsonDoc);
|
|
@@ -195,14 +198,14 @@ var RedisSaver = class RedisSaver extends BaseCheckpointSaver {
|
|
|
195
198
|
const hasNullFilter = Object.values(options.filter).some((v) => v === null);
|
|
196
199
|
const queryParts = [];
|
|
197
200
|
if (config?.configurable?.thread_id) {
|
|
198
|
-
const threadId = config.configurable.thread_id
|
|
201
|
+
const threadId = escapeRediSearchTagValue(config.configurable.thread_id);
|
|
199
202
|
queryParts.push(`(@thread_id:{${threadId}})`);
|
|
200
203
|
}
|
|
201
204
|
if (config?.configurable?.checkpoint_ns !== void 0) {
|
|
202
205
|
const checkpointNs = config.configurable.checkpoint_ns;
|
|
203
206
|
if (checkpointNs === "") queryParts.push(`(@checkpoint_ns:{__empty__})`);
|
|
204
207
|
else {
|
|
205
|
-
const escapedNs = checkpointNs
|
|
208
|
+
const escapedNs = escapeRediSearchTagValue(checkpointNs);
|
|
206
209
|
queryParts.push(`(@checkpoint_ns:{${escapedNs}})`);
|
|
207
210
|
}
|
|
208
211
|
}
|
|
@@ -383,7 +386,7 @@ var RedisSaver = class RedisSaver extends BaseCheckpointSaver {
|
|
|
383
386
|
await this.client.json.set(writeKey, "$", writeDoc);
|
|
384
387
|
}
|
|
385
388
|
if (writeKeys.length > 0) {
|
|
386
|
-
const zsetKey =
|
|
389
|
+
const zsetKey = `${WRITE_KEYS_ZSET_PREFIX}:${threadId}:${checkpointNs}:${checkpointId}`;
|
|
387
390
|
const zaddArgs = {};
|
|
388
391
|
writeKeys.forEach((key, idx) => {
|
|
389
392
|
zaddArgs[key] = baseTimestamp + idx;
|