@langchain/langgraph-checkpoint
Version:
Library with base interfaces for LangGraph checkpoint savers.
221 lines • 7.08 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseStore = exports.InvalidNamespaceError = void 0;
exports.getTextAtPath = getTextAtPath;
exports.tokenizePath = tokenizePath;
/**
* Error thrown when an invalid namespace is provided.
*/
class InvalidNamespaceError extends Error {
constructor(message) {
super(message);
this.name = "InvalidNamespaceError";
}
}
exports.InvalidNamespaceError = InvalidNamespaceError;
/**
* Validates the provided namespace.
* @param namespace The namespace to validate.
* @throws {InvalidNamespaceError} If the namespace is invalid.
*/
function validateNamespace(namespace) {
if (namespace.length === 0) {
throw new InvalidNamespaceError("Namespace cannot be empty.");
}
for (const label of namespace) {
if (typeof label !== "string") {
throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels ` +
`must be strings, but got ${typeof label}.`);
}
if (label.includes(".")) {
throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`);
}
if (label === "") {
throw new InvalidNamespaceError(`Namespace labels cannot be empty strings. Got ${label} in ${namespace}`);
}
}
if (namespace[0] === "langgraph") {
throw new InvalidNamespaceError(`Root label for namespace cannot be "langgraph". Got: ${namespace}`);
}
}
/**
* Utility function to get text at a specific JSON path
*/
function getTextAtPath(obj, path) {
const parts = path.split(".");
let current = obj;
for (const part of parts) {
if (part.includes("[")) {
const [arrayName, indexStr] = part.split("[");
const index = indexStr.replace("]", "");
if (!current[arrayName])
return [];
if (index === "*") {
const results = [];
for (const item of current[arrayName]) {
if (typeof item === "string")
results.push(item);
}
return results;
}
const idx = parseInt(index, 10);
if (Number.isNaN(idx))
return [];
current = current[arrayName][idx];
}
else {
current = current[part];
}
if (current === undefined)
return [];
}
return typeof current === "string" ? [current] : [];
}
/**
* Tokenizes a JSON path into parts
*/
function tokenizePath(path) {
return path.split(".");
}
/**
* Abstract base class for persistent key-value stores.
*
* Stores enable persistence and memory that can be shared across threads,
* scoped to user IDs, assistant IDs, or other arbitrary namespaces.
*
* Features:
* - Hierarchical namespaces for organization
* - Key-value storage with metadata
* - Vector similarity search (if configured)
* - Filtering and pagination
*/
class BaseStore {
/**
* Retrieve a single item by its namespace and key.
*
* @param namespace Hierarchical path for the item
* @param key Unique identifier within the namespace
* @returns Promise resolving to the item or null if not found
*/
async get(namespace, key) {
return (await this.batch([{ namespace, key }]))[0];
}
/**
* Search for items within a namespace prefix.
* Supports both metadata filtering and vector similarity search.
*
* @param namespacePrefix Hierarchical path prefix to search within
* @param options Search options for filtering and pagination
* @returns Promise resolving to list of matching items with relevance scores
*
* @example
* // Search with filters
* await store.search(["documents"], {
* filter: { type: "report", status: "active" },
* limit: 5,
* offset: 10
* });
*
* // Vector similarity search
* await store.search(["users", "content"], {
* query: "technical documentation about APIs",
* limit: 20
* });
*/
async search(namespacePrefix, options = {}) {
const { filter, limit = 10, offset = 0, query } = options;
return (await this.batch([
{
namespacePrefix,
filter,
limit,
offset,
query,
},
]))[0];
}
/**
* Store or update an item.
*
* @param namespace Hierarchical path for the item
* @param key Unique identifier within the namespace
* @param value Object containing the item's data
* @param index Optional indexing configuration
*
* @example
* // Simple storage
* await store.put(["docs"], "report", { title: "Annual Report" });
*
* // With specific field indexing
* await store.put(
* ["docs"],
* "report",
* {
* title: "Q4 Report",
* chapters: [{ content: "..." }, { content: "..." }]
* },
* ["title", "chapters[*].content"]
* );
*/
async put(namespace, key, value, index) {
validateNamespace(namespace);
await this.batch([{ namespace, key, value, index }]);
}
/**
* Delete an item from the store.
*
* @param namespace Hierarchical path for the item
* @param key Unique identifier within the namespace
*/
async delete(namespace, key) {
await this.batch([{ namespace, key, value: null }]);
}
/**
* List and filter namespaces in the store.
* Used to explore data organization and navigate the namespace hierarchy.
*
* @param options Options for listing namespaces
* @returns Promise resolving to list of namespace paths
*
* @example
* // List all namespaces under "documents"
* await store.listNamespaces({
* prefix: ["documents"],
* maxDepth: 2
* });
*
* // List namespaces ending with "v1"
* await store.listNamespaces({
* suffix: ["v1"],
* limit: 50
* });
*/
async listNamespaces(options = {}) {
const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;
const matchConditions = [];
if (prefix) {
matchConditions.push({ matchType: "prefix", path: prefix });
}
if (suffix) {
matchConditions.push({ matchType: "suffix", path: suffix });
}
return (await this.batch([
{
matchConditions: matchConditions.length ? matchConditions : undefined,
maxDepth,
limit,
offset,
},
]))[0];
}
/**
* Start the store. Override if initialization is needed.
*/
start() { }
/**
* Stop the store. Override if cleanup is needed.
*/
stop() { }
}
exports.BaseStore = BaseStore;
//# sourceMappingURL=base.js.map
;