@langchain/langgraph-checkpoint 1.1.5 → 1.2.0
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/base.cjs +67 -1
- package/dist/base.cjs.map +1 -1
- package/dist/base.d.cts +47 -2
- package/dist/base.d.cts.map +1 -1
- package/dist/base.d.ts +47 -2
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +66 -2
- package/dist/base.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/memory.cjs +47 -39
- package/dist/memory.cjs.map +1 -1
- package/dist/memory.d.cts +12 -2
- package/dist/memory.d.cts.map +1 -1
- package/dist/memory.d.ts +12 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +48 -40
- package/dist/memory.js.map +1 -1
- package/dist/store/base.cjs +44 -11
- package/dist/store/base.cjs.map +1 -1
- package/dist/store/base.d.cts +15 -1
- package/dist/store/base.d.cts.map +1 -1
- package/dist/store/base.d.ts +15 -1
- package/dist/store/base.d.ts.map +1 -1
- package/dist/store/base.js +44 -12
- package/dist/store/base.js.map +1 -1
- package/dist/store/batch.cjs +13 -0
- package/dist/store/batch.cjs.map +1 -1
- package/dist/store/batch.d.cts.map +1 -1
- package/dist/store/batch.d.ts.map +1 -1
- package/dist/store/batch.js +14 -1
- package/dist/store/batch.js.map +1 -1
- package/dist/store/memory.cjs +8 -1
- package/dist/store/memory.cjs.map +1 -1
- package/dist/store/memory.d.cts.map +1 -1
- package/dist/store/memory.d.ts.map +1 -1
- package/dist/store/memory.js +9 -2
- package/dist/store/memory.js.map +1 -1
- package/package.json +2 -2
package/dist/memory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","names":[],"sources":["../src/memory.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n Checkpoint,\n CheckpointListOptions,\n CheckpointTuple,\n copyCheckpoint,\n getCheckpointId,\n maxChannelVersion,\n WRITES_IDX_MAP,\n} from \"./base.js\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport {\n CheckpointMetadata,\n CheckpointPendingWrite,\n DeltaChannelHistory,\n PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\n\n/**\n * Keys that, when written into a plain JavaScript object via bracket\n * notation, traverse the prototype chain and mutate `Object.prototype`\n * (or the constructor) instead of creating a new own property. Any of\n * the three reaches `Object.prototype` and pollutes every object in\n * the running process. CWE-1321 (Prototype Pollution).\n */\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Asserts that a value sourced from {@link RunnableConfig.configurable} (or\n * any other caller-influenced position) is safe to use as a property key\n * on the in-memory checkpoint store.\n *\n * `MemorySaver` keeps state in two nested plain objects (`storage` and\n * `writes`) and writes to them with bracket notation:\n *\n * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...\n *\n * Without this guard a `threadId` of `\"__proto__\"` (or `\"constructor\"`)\n * resolves through the prototype chain, and the subsequent assignment\n * mutates `Object.prototype`. From that point every plain object in the\n * process inherits the injected property: `for...in` loops over unrelated\n * objects iterate it, framework code that does `if (obj[x])` short-circuits\n * unexpectedly, and downstream serializers may emit it. In a Node.js\n * server this is a stepping stone to remote code execution.\n *\n * `MemorySaver` is the default saver used by every quickstart, every\n * tutorial, and most test fixtures, so this guard runs in the hot path\n * for the most common LangGraph configuration.\n *\n * @param field Name of the configurable field, used in the error message.\n * @param value Value to validate. Must be a non-empty string that is not\n * one of the three prototype-pollution keys.\n * @param options.allowEmpty When true the empty string is accepted, used\n * for the documented empty `checkpoint_ns`\n * default; otherwise an empty string is\n * rejected the same way as a non-string.\n */\nfunction assertSafeStorageKey(\n field: string,\n value: unknown,\n options: { allowEmpty?: boolean } = {}\n): asserts value is string {\n const { allowEmpty = false } = options;\n if (typeof value !== \"string\") {\n const observed =\n value === null\n ? \"null\"\n : value === undefined\n ? \"undefined\"\n : Array.isArray(value)\n ? \"array\"\n : typeof value;\n throw new Error(\n `Invalid configurable value for key \"${field}\": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`\n );\n }\n if (!allowEmpty && value === \"\") {\n throw new Error(\n `Invalid configurable value for key \"${field}\": empty string is not permitted as an in-memory storage key.`\n );\n }\n if (POLLUTION_KEYS.has(value)) {\n throw new Error(\n `Invalid configurable value for key \"${field}\": value \"${value}\" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`\n );\n }\n}\n\nfunction _generateKey(\n threadId: string,\n checkpointNamespace: string,\n checkpointId: string\n) {\n return JSON.stringify([threadId, checkpointNamespace, checkpointId]);\n}\n\nfunction _parseKey(key: string) {\n const [threadId, checkpointNamespace, checkpointId] = JSON.parse(key);\n return { threadId, checkpointNamespace, checkpointId };\n}\n\nexport class MemorySaver extends BaseCheckpointSaver {\n // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping\n //\n // Defense in depth against prototype pollution: the backing\n // objects (and every nested level created below) use a null prototype, so\n // even if a malicious key bypassed `assertSafeStorageKey` it could not reach\n // `Object.prototype`. The guard remains the primary control; this is the\n // structural safety net.\n storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = Object.create(null);\n\n writes: Record<string, Record<string, [string, string, Uint8Array]>> =\n Object.create(null);\n\n constructor(serde?: SerializerProtocol) {\n super(serde);\n }\n\n /** @internal */\n async _migratePendingSends(\n mutableCheckpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ) {\n const deseriablizableCheckpoint = mutableCheckpoint;\n const parentKey = _generateKey(threadId, checkpointNs, parentCheckpointId);\n\n const pendingSends = await Promise.all(\n Object.values(this.writes[parentKey] ?? {})\n .filter(([_taskId, channel]) => channel === TASKS)\n .map(\n async ([_taskId, _channel, writes]) =>\n await this.serde.loadsTyped(\"json\", writes)\n )\n );\n\n deseriablizableCheckpoint.channel_values ??= {};\n deseriablizableCheckpoint.channel_values[TASKS] = pendingSends;\n\n deseriablizableCheckpoint.channel_versions ??= {};\n deseriablizableCheckpoint.channel_versions[TASKS] =\n Object.keys(deseriablizableCheckpoint.channel_versions).length > 0\n ? maxChannelVersion(\n ...Object.values(deseriablizableCheckpoint.channel_versions)\n )\n : this.getNextVersion(undefined);\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns ?? \"\";\n let checkpoint_id = getCheckpointId(config);\n\n // Defense in depth: every public entry that mutates state already\n // validates these, but read paths must not return data sourced from\n // prototype-chain lookups when an attacker passes the magic keys.\n // `checkpoint_id` is intentionally allowed to be empty / undefined\n // here because the downstream `if (checkpoint_id)` branch treats\n // both as \"fetch the latest checkpoint\" rather than as a lookup key.\n if (thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", thread_id);\n }\n assertSafeStorageKey(\"checkpoint_ns\", checkpoint_ns, { allowEmpty: true });\n if (checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", checkpoint_id);\n }\n\n if (checkpoint_id) {\n const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];\n if (saved !== undefined) {\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n Object.values(this.writes[key] || {}).map(\n async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n }\n )\n );\n const checkpointTuple: CheckpointTuple = {\n config,\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n } else {\n const checkpoints = this.storage[thread_id]?.[checkpoint_ns];\n if (checkpoints !== undefined) {\n // eslint-disable-next-line prefer-destructuring\n checkpoint_id = Object.keys(checkpoints).sort((a, b) =>\n b.localeCompare(a)\n )[0];\n const saved = checkpoints[checkpoint_id];\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n Object.values(this.writes[key] || {}).map(\n async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n }\n )\n );\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id,\n checkpoint_id,\n checkpoint_ns,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n }\n\n return undefined;\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple> {\n // eslint-disable-next-line prefer-const\n let { before, limit, filter } = options ?? {};\n if (config.configurable?.thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", config.configurable.thread_id);\n }\n if (config.configurable?.checkpoint_ns !== undefined) {\n assertSafeStorageKey(\"checkpoint_ns\", config.configurable.checkpoint_ns, {\n allowEmpty: true,\n });\n }\n if (config.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", config.configurable.checkpoint_id);\n }\n if (before?.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", before.configurable.checkpoint_id);\n }\n const threadIds = config.configurable?.thread_id\n ? [config.configurable?.thread_id]\n : Object.keys(this.storage);\n const configCheckpointNamespace = config.configurable?.checkpoint_ns;\n const configCheckpointId = config.configurable?.checkpoint_id;\n\n for (const threadId of threadIds) {\n for (const checkpointNamespace of Object.keys(\n this.storage[threadId] ?? {}\n )) {\n if (\n configCheckpointNamespace !== undefined &&\n checkpointNamespace !== configCheckpointNamespace\n ) {\n continue;\n }\n const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};\n const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) =>\n b[0].localeCompare(a[0])\n );\n\n for (const [\n checkpointId,\n [checkpoint, metadataStr, parentCheckpointId],\n ] of sortedCheckpoints) {\n // Filter by checkpoint ID from config\n if (configCheckpointId && checkpointId !== configCheckpointId) {\n continue;\n }\n\n // Filter by checkpoint ID from before config\n if (\n before &&\n before.configurable?.checkpoint_id &&\n checkpointId >= before.configurable.checkpoint_id\n ) {\n continue;\n }\n\n // Parse metadata\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n metadataStr\n )) as CheckpointMetadata;\n\n if (\n filter &&\n !Object.entries(filter).every(\n ([key, value]) =>\n (metadata as unknown as Record<string, unknown>)[key] === value\n )\n ) {\n continue;\n }\n\n // Limit search results\n if (limit !== undefined) {\n if (limit <= 0) break;\n limit -= 1;\n }\n\n const key = _generateKey(threadId, checkpointNamespace, checkpointId);\n const writes = Object.values(this.writes[key] || {});\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n writes.map(async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n })\n );\n\n const deserializedCheckpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (\n deserializedCheckpoint.v < 4 &&\n parentCheckpointId !== undefined\n ) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n threadId,\n checkpointNamespace,\n parentCheckpointId\n );\n }\n\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpointId,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n yield checkpointTuple;\n }\n }\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata\n ): Promise<RunnableConfig> {\n const preparedCheckpoint: Partial<Checkpoint> = copyCheckpoint(checkpoint);\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns ?? \"\";\n if (threadId === undefined) {\n throw new Error(\n `Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpoint.id);\n\n if (!this.storage[threadId]) {\n this.storage[threadId] = Object.create(null);\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = Object.create(null);\n }\n\n const [[, serializedCheckpoint], [, serializedMetadata]] =\n await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n\n this.storage[threadId][checkpointNamespace][checkpoint.id] = [\n serializedCheckpoint,\n serializedMetadata,\n config.configurable?.checkpoint_id, // parent\n ];\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void> {\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns;\n const checkpointId = config.configurable?.checkpoint_id;\n if (threadId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n if (checkpointId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"checkpoint_id\" field in its \"configurable\" property.`\n );\n }\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpointId);\n assertSafeStorageKey(\"task_id\", taskId);\n const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = Object.create(null);\n }\n\n await Promise.all(\n writes.map(async ([channel, value], idx) => {\n const [, serializedValue] = await this.serde.dumpsTyped(value);\n const innerKey: [string, number] = [\n taskId,\n WRITES_IDX_MAP[channel] || idx,\n ];\n const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;\n if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {\n return;\n }\n this.writes[outerKey][innerKeyStr] = [taskId, channel, serializedValue];\n })\n );\n }\n\n async deleteThread(threadId: string): Promise<void> {\n assertSafeStorageKey(\"thread_id\", threadId);\n delete this.storage[threadId];\n for (const key of Object.keys(this.writes)) {\n if (_parseKey(key).threadId === threadId) delete this.writes[key];\n }\n }\n\n /**\n * Override: walk the parent chain ONCE for all requested channels using\n * direct storage access.\n *\n * Each channel terminates independently at the nearest ancestor whose\n * stored `channel_values[ch]` is populated. Other channels keep walking\n * until they find their own terminator or hit the root.\n *\n * The seed value (whether a `DeltaSnapshot` or a plain pre-delta migration\n * blob) is the value AT that ancestor, prior to its own pending writes that\n * produce the child. Those on-path writes — including the ones stored on the\n * terminating ancestor — are always collected and replayed on top of the\n * seed, so a thread migrated from a pre-delta channel does not drop the\n * writes saved under the migration boundary checkpoint.\n *\n * @remarks Beta. See {@link BaseCheckpointSaver.getDeltaChannelHistory}.\n */\n async getDeltaChannelHistory(options: {\n config: RunnableConfig;\n channels: string[];\n }): Promise<Record<string, DeltaChannelHistory>> {\n const { config, channels } = options;\n if (channels.length === 0) return {};\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = getCheckpointId(config);\n\n if (threadId !== undefined) assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNs, { allowEmpty: true });\n\n const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};\n\n // Build the parent chain starting at the target's parent (the target's\n // own pending writes are for the next super-step and excluded).\n const chain: string[] = [];\n const targetEntry = checkpointId ? nsStorage[checkpointId] : undefined;\n let current: string | undefined = targetEntry?.[2];\n while (current !== undefined) {\n const entry = nsStorage[current];\n if (entry === undefined) break;\n chain.push(current);\n current = entry[2];\n }\n\n const collectedByCh: Record<string, CheckpointPendingWrite[]> = {};\n const seedByCh: Record<string, unknown> = {};\n const remaining = new Set(channels);\n for (const ch of channels) collectedByCh[ch] = [];\n\n for (const cpId of chain) {\n if (remaining.size === 0) break;\n const entry = nsStorage[cpId];\n const ckpt: Checkpoint | undefined =\n entry !== undefined\n ? await this.serde.loadsTyped(\"json\", entry[0])\n : undefined;\n\n const blobValueByCh: Record<string, unknown> = {};\n const terminatedHere = new Set<string>();\n if (ckpt !== undefined) {\n for (const ch of remaining) {\n if (\n Object.prototype.hasOwnProperty.call(ckpt.channel_values, ch) &&\n ckpt.channel_values[ch] !== undefined\n ) {\n blobValueByCh[ch] = ckpt.channel_values[ch];\n terminatedHere.add(ch);\n }\n }\n }\n\n const stepWritesKey = _generateKey(threadId, checkpointNs, cpId);\n const stepWrites = Object.entries(this.writes[stepWritesKey] ?? {});\n // Sort by [taskId, idx] descending to mirror the Python walk order;\n // the full list is reversed once at the end to get oldest→newest.\n stepWrites.sort(([a], [b]) => {\n const [aTask, aIdx] = a.split(\",\");\n const [bTask, bIdx] = b.split(\",\");\n if (aTask !== bTask) return aTask < bTask ? 1 : -1;\n return Number(bIdx) - Number(aIdx);\n });\n for (const [, [tid, ch, serialized]] of stepWrites) {\n if (!remaining.has(ch)) continue;\n // Collect on-path writes regardless of seed type. A plain (pre-delta\n // migration) blob is the settled value AT that ancestor; its own\n // pending writes produce the child and must still be replayed, just\n // like a `DeltaSnapshot` seed. Skipping them would drop post-migration\n // writes saved under the migration boundary checkpoint.\n collectedByCh[ch].push([\n tid,\n ch,\n await this.serde.loadsTyped(\"json\", serialized),\n ]);\n }\n\n for (const ch of terminatedHere) {\n seedByCh[ch] = blobValueByCh[ch];\n remaining.delete(ch);\n }\n }\n\n const result: Record<string, DeltaChannelHistory> = {};\n for (const ch of channels) {\n const entryH: DeltaChannelHistory = {\n writes: collectedByCh[ch].slice().reverse(),\n };\n if (Object.prototype.hasOwnProperty.call(seedByCh, ch)) {\n entryH.seed = seedByCh[ch];\n }\n result[ch] = entryH;\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory.js","names":[],"sources":["../src/memory.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n Checkpoint,\n CheckpointListOptions,\n CheckpointTuple,\n compareWritesSortKeys,\n copyCheckpoint,\n getCheckpointId,\n maxChannelVersion,\n writesSortKey,\n WRITES_IDX_MAP,\n} from \"./base.js\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport {\n CheckpointMetadata,\n CheckpointPendingWrite,\n DeltaChannelHistory,\n PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\n\n/**\n * Keys that, when written into a plain JavaScript object via bracket\n * notation, traverse the prototype chain and mutate `Object.prototype`\n * (or the constructor) instead of creating a new own property. Any of\n * the three reaches `Object.prototype` and pollutes every object in\n * the running process. CWE-1321 (Prototype Pollution).\n */\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Asserts that a value sourced from {@link RunnableConfig.configurable} (or\n * any other caller-influenced position) is safe to use as a property key\n * on the in-memory checkpoint store.\n *\n * `MemorySaver` keeps state in two nested plain objects (`storage` and\n * `writes`) and writes to them with bracket notation:\n *\n * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...\n *\n * Without this guard a `threadId` of `\"__proto__\"` (or `\"constructor\"`)\n * resolves through the prototype chain, and the subsequent assignment\n * mutates `Object.prototype`. From that point every plain object in the\n * process inherits the injected property: `for...in` loops over unrelated\n * objects iterate it, framework code that does `if (obj[x])` short-circuits\n * unexpectedly, and downstream serializers may emit it. In a Node.js\n * server this is a stepping stone to remote code execution.\n *\n * `MemorySaver` is the default saver used by every quickstart, every\n * tutorial, and most test fixtures, so this guard runs in the hot path\n * for the most common LangGraph configuration.\n *\n * @param field Name of the configurable field, used in the error message.\n * @param value Value to validate. Must be a non-empty string that is not\n * one of the three prototype-pollution keys.\n * @param options.allowEmpty When true the empty string is accepted, used\n * for the documented empty `checkpoint_ns`\n * default; otherwise an empty string is\n * rejected the same way as a non-string.\n */\nfunction assertSafeStorageKey(\n field: string,\n value: unknown,\n options: { allowEmpty?: boolean } = {}\n): asserts value is string {\n const { allowEmpty = false } = options;\n if (typeof value !== \"string\") {\n const observed =\n value === null\n ? \"null\"\n : value === undefined\n ? \"undefined\"\n : Array.isArray(value)\n ? \"array\"\n : typeof value;\n throw new Error(\n `Invalid configurable value for key \"${field}\": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`\n );\n }\n if (!allowEmpty && value === \"\") {\n throw new Error(\n `Invalid configurable value for key \"${field}\": empty string is not permitted as an in-memory storage key.`\n );\n }\n if (POLLUTION_KEYS.has(value)) {\n throw new Error(\n `Invalid configurable value for key \"${field}\": value \"${value}\" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`\n );\n }\n}\n\nfunction _generateKey(\n threadId: string,\n checkpointNamespace: string,\n checkpointId: string\n) {\n return JSON.stringify([threadId, checkpointNamespace, checkpointId]);\n}\n\nfunction _parseKey(key: string) {\n const [threadId, checkpointNamespace, checkpointId] = JSON.parse(key);\n return { threadId, checkpointNamespace, checkpointId };\n}\n\nexport class MemorySaver extends BaseCheckpointSaver {\n // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping\n //\n // Defense in depth against prototype pollution: the backing\n // objects (and every nested level created below) use a null prototype, so\n // even if a malicious key bypassed `assertSafeStorageKey` it could not reach\n // `Object.prototype`. The guard remains the primary control; this is the\n // structural safety net.\n storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = Object.create(null);\n\n // Each stored write is `[taskId, channel, serializedValue, taskPath?]`,\n // keyed by `${taskId},${idx}`. The optional 4th element is append-only:\n // records persisted before task paths existed (e.g. a `langgraph-api`\n // `.langgraphjs_api.checkpointer.json` file written by an older version)\n // have 3 elements and read back with path `\"\"`, which sorts first, by\n // task id.\n writes: Record<\n string,\n Record<string, [string, string, Uint8Array, string?]>\n > = Object.create(null);\n\n constructor(serde?: SerializerProtocol) {\n super(serde);\n }\n\n /** @internal */\n async _migratePendingSends(\n mutableCheckpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ) {\n const deseriablizableCheckpoint = mutableCheckpoint;\n const parentKey = _generateKey(threadId, checkpointNs, parentCheckpointId);\n\n const pendingSends = await Promise.all(\n Object.values(this.writes[parentKey] ?? {})\n .filter(([_taskId, channel]) => channel === TASKS)\n .map(\n async ([_taskId, _channel, writes]) =>\n await this.serde.loadsTyped(\"json\", writes)\n )\n );\n\n deseriablizableCheckpoint.channel_values ??= {};\n deseriablizableCheckpoint.channel_values[TASKS] = pendingSends;\n\n deseriablizableCheckpoint.channel_versions ??= {};\n deseriablizableCheckpoint.channel_versions[TASKS] =\n Object.keys(deseriablizableCheckpoint.channel_versions).length > 0\n ? maxChannelVersion(\n ...Object.values(deseriablizableCheckpoint.channel_versions)\n )\n : this.getNextVersion(undefined);\n }\n\n /**\n * Stored writes for one checkpoint, in `writesSortKey` order.\n *\n * `taskId` and `idx` come from the storage key, `taskPath` from the stored\n * record (missing on records written before paths existed).\n *\n * @internal\n */\n _orderedWrites(\n threadId: string | undefined,\n checkpointNs: string,\n checkpointId: string\n ): [string, number, [string, string, Uint8Array, string?]][] {\n const stored =\n threadId === undefined\n ? undefined\n : this.writes[_generateKey(threadId, checkpointNs, checkpointId)];\n if (stored === undefined) return [];\n // Compute each record's key once, then sort on the cached keys.\n const keyed = Object.entries(stored).map(([keyStr, record]) => {\n const comma = keyStr.lastIndexOf(\",\");\n const taskId = keyStr.slice(0, comma);\n const idx = Number(keyStr.slice(comma + 1));\n return {\n key: writesSortKey(record[3] ?? \"\", taskId, idx),\n entry: [taskId, idx, record] as [\n string,\n number,\n [string, string, Uint8Array, string?],\n ],\n };\n });\n keyed.sort((a, b) => compareWritesSortKeys(a.key, b.key));\n return keyed.map(({ entry }) => entry);\n }\n\n /** Deserialize `_orderedWrites` records into public pending writes. */\n private async _deserializeOrderedWrites(\n ordered: [string, number, [string, string, Uint8Array, string?]][]\n ): Promise<CheckpointPendingWrite[]> {\n return Promise.all(\n ordered.map(\n async ([, , [taskId, channel, value]]) =>\n [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ] as CheckpointPendingWrite\n )\n );\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns ?? \"\";\n let checkpoint_id = getCheckpointId(config);\n\n // Defense in depth: every public entry that mutates state already\n // validates these, but read paths must not return data sourced from\n // prototype-chain lookups when an attacker passes the magic keys.\n // `checkpoint_id` is intentionally allowed to be empty / undefined\n // here because the downstream `if (checkpoint_id)` branch treats\n // both as \"fetch the latest checkpoint\" rather than as a lookup key.\n if (thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", thread_id);\n }\n assertSafeStorageKey(\"checkpoint_ns\", checkpoint_ns, { allowEmpty: true });\n if (checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", checkpoint_id);\n }\n\n if (checkpoint_id) {\n const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];\n if (saved !== undefined) {\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id)\n );\n const checkpointTuple: CheckpointTuple = {\n config,\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n } else {\n const checkpoints = this.storage[thread_id]?.[checkpoint_ns];\n if (checkpoints !== undefined) {\n // eslint-disable-next-line prefer-destructuring\n checkpoint_id = Object.keys(checkpoints).sort().reverse()[0];\n const saved = checkpoints[checkpoint_id];\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id)\n );\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id,\n checkpoint_id,\n checkpoint_ns,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n }\n\n return undefined;\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple> {\n // eslint-disable-next-line prefer-const\n let { before, limit, filter } = options ?? {};\n if (config.configurable?.thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", config.configurable.thread_id);\n }\n if (config.configurable?.checkpoint_ns !== undefined) {\n assertSafeStorageKey(\"checkpoint_ns\", config.configurable.checkpoint_ns, {\n allowEmpty: true,\n });\n }\n if (config.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", config.configurable.checkpoint_id);\n }\n if (before?.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", before.configurable.checkpoint_id);\n }\n const threadIds = config.configurable?.thread_id\n ? [config.configurable?.thread_id]\n : Object.keys(this.storage);\n const configCheckpointNamespace = config.configurable?.checkpoint_ns;\n const configCheckpointId = config.configurable?.checkpoint_id;\n\n for (const threadId of threadIds) {\n for (const checkpointNamespace of Object.keys(\n this.storage[threadId] ?? {}\n )) {\n if (\n configCheckpointNamespace !== undefined &&\n checkpointNamespace !== configCheckpointNamespace\n ) {\n continue;\n }\n const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};\n const sortedCheckpoints = Object.keys(checkpoints)\n .sort()\n .reverse()\n .map((id) => [id, checkpoints[id]] as const);\n\n for (const [\n checkpointId,\n [checkpoint, metadataStr, parentCheckpointId],\n ] of sortedCheckpoints) {\n // Filter by checkpoint ID from config\n if (configCheckpointId && checkpointId !== configCheckpointId) {\n continue;\n }\n\n // Filter by checkpoint ID from before config\n if (\n before &&\n before.configurable?.checkpoint_id &&\n checkpointId >= before.configurable.checkpoint_id\n ) {\n continue;\n }\n\n // Parse metadata\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n metadataStr\n )) as CheckpointMetadata;\n\n if (\n filter &&\n !Object.entries(filter).every(\n ([key, value]) =>\n (metadata as unknown as Record<string, unknown>)[key] === value\n )\n ) {\n continue;\n }\n\n // Limit search results\n if (limit !== undefined) {\n if (limit <= 0) break;\n limit -= 1;\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(threadId, checkpointNamespace, checkpointId)\n );\n\n const deserializedCheckpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (\n deserializedCheckpoint.v < 4 &&\n parentCheckpointId !== undefined\n ) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n threadId,\n checkpointNamespace,\n parentCheckpointId\n );\n }\n\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpointId,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n yield checkpointTuple;\n }\n }\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata\n ): Promise<RunnableConfig> {\n const preparedCheckpoint: Partial<Checkpoint> = copyCheckpoint(checkpoint);\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns ?? \"\";\n if (threadId === undefined) {\n throw new Error(\n `Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpoint.id);\n\n if (!this.storage[threadId]) {\n this.storage[threadId] = Object.create(null);\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = Object.create(null);\n }\n\n const [[, serializedCheckpoint], [, serializedMetadata]] =\n await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n\n this.storage[threadId][checkpointNamespace][checkpoint.id] = [\n serializedCheckpoint,\n serializedMetadata,\n config.configurable?.checkpoint_id, // parent\n ];\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string,\n taskPath?: string\n ): Promise<void> {\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns;\n const checkpointId = config.configurable?.checkpoint_id;\n if (threadId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n if (checkpointId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"checkpoint_id\" field in its \"configurable\" property.`\n );\n }\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpointId);\n assertSafeStorageKey(\"task_id\", taskId);\n const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = Object.create(null);\n }\n\n await Promise.all(\n writes.map(async ([channel, value], idx) => {\n const [, serializedValue] = await this.serde.dumpsTyped(value);\n const innerKey: [string, number] = [\n taskId,\n WRITES_IDX_MAP[channel] || idx,\n ];\n const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;\n if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {\n return;\n }\n this.writes[outerKey][innerKeyStr] = [\n taskId,\n channel,\n serializedValue,\n taskPath ?? \"\",\n ];\n })\n );\n }\n\n async deleteThread(threadId: string): Promise<void> {\n assertSafeStorageKey(\"thread_id\", threadId);\n delete this.storage[threadId];\n for (const key of Object.keys(this.writes)) {\n if (_parseKey(key).threadId === threadId) delete this.writes[key];\n }\n }\n\n /**\n * Override: walk the parent chain ONCE for all requested channels using\n * direct storage access.\n *\n * Each channel terminates independently at the nearest ancestor whose\n * stored `channel_values[ch]` is populated. Other channels keep walking\n * until they find their own terminator or hit the root.\n *\n * The seed value (whether a `DeltaSnapshot` or a plain pre-delta migration\n * blob) is the value AT that ancestor, prior to its own pending writes that\n * produce the child. Those on-path writes — including the ones stored on the\n * terminating ancestor — are always collected and replayed on top of the\n * seed, so a thread migrated from a pre-delta channel does not drop the\n * writes saved under the migration boundary checkpoint.\n *\n * @remarks Beta. See {@link BaseCheckpointSaver.getDeltaChannelHistory}.\n */\n async getDeltaChannelHistory(options: {\n config: RunnableConfig;\n channels: string[];\n }): Promise<Record<string, DeltaChannelHistory>> {\n const { config, channels } = options;\n if (channels.length === 0) return {};\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n\n if (threadId !== undefined) assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNs, { allowEmpty: true });\n\n const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};\n const checkpointId =\n getCheckpointId(config) || Object.keys(nsStorage).sort().reverse()[0];\n\n // Build the parent chain starting at the target's parent (the target's\n // own pending writes are for the next super-step and excluded).\n const chain: string[] = [];\n const targetEntry = checkpointId ? nsStorage[checkpointId] : undefined;\n let current: string | undefined = targetEntry?.[2];\n while (current !== undefined) {\n const entry = nsStorage[current];\n if (entry === undefined) break;\n chain.push(current);\n current = entry[2];\n }\n\n const collectedByCh: Record<string, CheckpointPendingWrite[]> = {};\n const seedByCh: Record<string, unknown> = {};\n const remaining = new Set(channels);\n for (const ch of channels) collectedByCh[ch] = [];\n\n for (const cpId of chain) {\n if (remaining.size === 0) break;\n const entry = nsStorage[cpId];\n const ckpt: Checkpoint | undefined =\n entry !== undefined\n ? await this.serde.loadsTyped(\"json\", entry[0])\n : undefined;\n\n const blobValueByCh: Record<string, unknown> = {};\n const terminatedHere = new Set<string>();\n if (ckpt !== undefined) {\n for (const ch of remaining) {\n if (\n Object.prototype.hasOwnProperty.call(ckpt.channel_values, ch) &&\n ckpt.channel_values[ch] !== undefined\n ) {\n blobValueByCh[ch] = ckpt.channel_values[ch];\n terminatedHere.add(ch);\n }\n }\n }\n\n // `writesSortKey` order — the order live execution applies a\n // superstep's writes in (the same order `getTuple` returns them in).\n // Each block is pushed reversed so the final `.reverse()` below yields\n // oldest→newest checkpoints with each checkpoint's writes ascending.\n const stepWrites = this._orderedWrites(threadId, checkpointNs, cpId);\n for (let i = stepWrites.length - 1; i >= 0; i -= 1) {\n const [tid, ch, serialized] = stepWrites[i][2];\n if (!remaining.has(ch)) continue;\n // Collect on-path writes regardless of seed type. A plain (pre-delta\n // migration) blob is the settled value AT that ancestor; its own\n // pending writes produce the child and must still be replayed, just\n // like a `DeltaSnapshot` seed. Skipping them would drop post-migration\n // writes saved under the migration boundary checkpoint.\n collectedByCh[ch].push([\n tid,\n ch,\n await this.serde.loadsTyped(\"json\", serialized),\n ]);\n }\n\n for (const ch of terminatedHere) {\n seedByCh[ch] = blobValueByCh[ch];\n remaining.delete(ch);\n }\n }\n\n const result: Record<string, DeltaChannelHistory> = {};\n for (const ch of channels) {\n const entryH: DeltaChannelHistory = {\n writes: collectedByCh[ch].slice().reverse(),\n };\n if (Object.prototype.hasOwnProperty.call(seedByCh, ch)) {\n entryH.seed = seedByCh[ch];\n }\n result[ch] = entryH;\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
package/dist/store/base.cjs
CHANGED
|
@@ -11,16 +11,24 @@ var InvalidNamespaceError = class extends Error {
|
|
|
11
11
|
/**
|
|
12
12
|
* Validates the provided namespace.
|
|
13
13
|
* @param namespace The namespace to validate.
|
|
14
|
+
* @param options.allowEmpty When true, an empty namespace is permitted
|
|
15
|
+
* (`search([])` means "search everything"). Put still rejects empty.
|
|
16
|
+
* @param options.allowReservedRoot When true, the `langgraph` root label is
|
|
17
|
+
* permitted. Put still rejects it; search may read a namespace written via
|
|
18
|
+
* `batch()` which bypasses put validation.
|
|
14
19
|
* @throws {InvalidNamespaceError} If the namespace is invalid.
|
|
15
20
|
*/
|
|
16
|
-
function validateNamespace(namespace) {
|
|
17
|
-
if (namespace.length === 0)
|
|
21
|
+
function validateNamespace(namespace, options = {}) {
|
|
22
|
+
if (namespace.length === 0) {
|
|
23
|
+
if (options.allowEmpty) return;
|
|
24
|
+
throw new InvalidNamespaceError("Namespace cannot be empty.");
|
|
25
|
+
}
|
|
18
26
|
for (const label of namespace) {
|
|
19
27
|
if (typeof label !== "string") throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels must be strings, but got ${typeof label}.`);
|
|
20
28
|
if (label.includes(".")) throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`);
|
|
21
29
|
if (label === "") throw new InvalidNamespaceError(`Namespace labels cannot be empty strings. Got ${label} in ${namespace}`);
|
|
22
30
|
}
|
|
23
|
-
if (namespace[0] === "langgraph") throw new InvalidNamespaceError(`Root label for namespace cannot be "langgraph". Got: ${namespace}`);
|
|
31
|
+
if (namespace[0] === "langgraph" && !options.allowReservedRoot) throw new InvalidNamespaceError(`Root label for namespace cannot be "langgraph". Got: ${namespace}`);
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Utility function to get text at a specific JSON path
|
|
@@ -73,6 +81,10 @@ var BaseStore = class {
|
|
|
73
81
|
* @returns Promise resolving to the item or null if not found
|
|
74
82
|
*/
|
|
75
83
|
async get(namespace, key) {
|
|
84
|
+
validateNamespace(namespace, {
|
|
85
|
+
allowEmpty: true,
|
|
86
|
+
allowReservedRoot: true
|
|
87
|
+
});
|
|
76
88
|
return (await this.batch([{
|
|
77
89
|
namespace,
|
|
78
90
|
key
|
|
@@ -101,6 +113,10 @@ var BaseStore = class {
|
|
|
101
113
|
* });
|
|
102
114
|
*/
|
|
103
115
|
async search(namespacePrefix, options = {}) {
|
|
116
|
+
validateNamespace(namespacePrefix, {
|
|
117
|
+
allowEmpty: true,
|
|
118
|
+
allowReservedRoot: true
|
|
119
|
+
});
|
|
104
120
|
const { filter, limit = 10, offset = 0, query } = options;
|
|
105
121
|
return (await this.batch([{
|
|
106
122
|
namespacePrefix,
|
|
@@ -149,6 +165,10 @@ var BaseStore = class {
|
|
|
149
165
|
* @param key Unique identifier within the namespace
|
|
150
166
|
*/
|
|
151
167
|
async delete(namespace, key) {
|
|
168
|
+
validateNamespace(namespace, {
|
|
169
|
+
allowEmpty: true,
|
|
170
|
+
allowReservedRoot: true
|
|
171
|
+
});
|
|
152
172
|
await this.batch([{
|
|
153
173
|
namespace,
|
|
154
174
|
key,
|
|
@@ -178,14 +198,26 @@ var BaseStore = class {
|
|
|
178
198
|
async listNamespaces(options = {}) {
|
|
179
199
|
const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;
|
|
180
200
|
const matchConditions = [];
|
|
181
|
-
if (prefix)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
if (prefix) {
|
|
202
|
+
validateNamespace(prefix, {
|
|
203
|
+
allowEmpty: true,
|
|
204
|
+
allowReservedRoot: true
|
|
205
|
+
});
|
|
206
|
+
matchConditions.push({
|
|
207
|
+
matchType: "prefix",
|
|
208
|
+
path: prefix
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (suffix) {
|
|
212
|
+
validateNamespace(suffix, {
|
|
213
|
+
allowEmpty: true,
|
|
214
|
+
allowReservedRoot: true
|
|
215
|
+
});
|
|
216
|
+
matchConditions.push({
|
|
217
|
+
matchType: "suffix",
|
|
218
|
+
path: suffix
|
|
219
|
+
});
|
|
220
|
+
}
|
|
189
221
|
return (await this.batch([{
|
|
190
222
|
matchConditions: matchConditions.length ? matchConditions : void 0,
|
|
191
223
|
maxDepth,
|
|
@@ -207,5 +239,6 @@ exports.BaseStore = BaseStore;
|
|
|
207
239
|
exports.InvalidNamespaceError = InvalidNamespaceError;
|
|
208
240
|
exports.getTextAtPath = getTextAtPath;
|
|
209
241
|
exports.tokenizePath = tokenizePath;
|
|
242
|
+
exports.validateNamespace = validateNamespace;
|
|
210
243
|
|
|
211
244
|
//# sourceMappingURL=base.cjs.map
|
package/dist/store/base.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.cjs","names":[],"sources":["../../src/store/base.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Embeddings } from \"@langchain/core/embeddings\";\n\n/**\n * Error thrown when an invalid namespace is provided.\n */\nexport class InvalidNamespaceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidNamespaceError\";\n }\n}\n\n/**\n * Validates the provided namespace.\n * @param namespace The namespace to validate.\n * @throws {InvalidNamespaceError} If the namespace is invalid.\n */\nfunction validateNamespace(namespace: string[]): void {\n if (namespace.length === 0) {\n throw new InvalidNamespaceError(\"Namespace cannot be empty.\");\n }\n for (const label of namespace) {\n if (typeof label !== \"string\") {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels ` +\n `must be strings, but got ${typeof label}.`\n );\n }\n if (label.includes(\".\")) {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`\n );\n }\n if (label === \"\") {\n throw new InvalidNamespaceError(\n `Namespace labels cannot be empty strings. Got ${label} in ${namespace}`\n );\n }\n }\n if (namespace[0] === \"langgraph\") {\n throw new InvalidNamespaceError(\n `Root label for namespace cannot be \"langgraph\". Got: ${namespace}`\n );\n }\n}\n\n/**\n * Represents a stored item with metadata.\n */\nexport interface Item {\n /**\n * The stored data as an object. Keys are filterable.\n */\n value: Record<string, any>;\n /**\n * Unique identifier within the namespace.\n */\n key: string;\n /**\n * Hierarchical path defining the collection in which this document resides.\n * Represented as an array of strings, allowing for nested categorization.\n * For example: [\"documents\", \"user123\"]\n */\n namespace: string[];\n /**\n * Timestamp of item creation.\n */\n createdAt: Date;\n /**\n * Timestamp of last update.\n */\n updatedAt: Date;\n}\n\n/**\n * Represents a search result item with relevance score.\n * Extends the base Item interface with an optional similarity score.\n */\nexport interface SearchItem extends Item {\n /**\n * Relevance/similarity score if from a ranked operation.\n * Higher scores indicate better matches.\n *\n * This is typically a cosine similarity score between -1 and 1,\n * where 1 indicates identical vectors and -1 indicates opposite vectors.\n */\n score?: number;\n}\n\n/**\n * Operation to retrieve an item by namespace and ID.\n */\nexport interface GetOperation {\n /**\n * Hierarchical path for the item.\n *\n * @example\n * // Get a user profile\n * namespace: [\"users\", \"profiles\"]\n */\n namespace: string[];\n\n /**\n * Unique identifier within the namespace.\n * Together with namespace forms the complete path to the item.\n *\n * @example\n * key: \"user123\" // For a user profile\n * key: \"doc456\" // For a document\n */\n key: string;\n}\n\n/**\n * Operation to search for items within a namespace prefix.\n */\nexport interface SearchOperation {\n /**\n * Hierarchical path prefix to search within.\n * Only items under this prefix will be searched.\n *\n * @example\n * // Search all user documents\n * namespacePrefix: [\"users\", \"documents\"]\n *\n * // Search everything\n * namespacePrefix: []\n */\n namespacePrefix: string[];\n\n /**\n * Key-value pairs to filter results based on exact matches or comparison operators.\n *\n * Supports both exact matches and operator-based comparisons:\n * - $eq: Equal to (same as direct value comparison)\n * - $ne: Not equal to\n * - $gt: Greater than\n * - $gte: Greater than or equal to\n * - $lt: Less than\n * - $lte: Less than or equal to\n *\n * @example\n * // Exact match\n * filter: { status: \"active\" }\n *\n * // With operators\n * filter: { score: { $gt: 4.99 } }\n *\n * // Multiple conditions\n * filter: {\n * score: { $gte: 3.0 },\n * color: \"red\"\n * }\n */\n filter?: Record<string, any>;\n\n /**\n * Maximum number of items to return.\n * @default 10\n */\n limit?: number;\n\n /**\n * Number of items to skip before returning results.\n * Useful for pagination.\n * @default 0\n */\n offset?: number;\n\n /**\n * Natural language search query for semantic search.\n * When provided, results will be ranked by relevance to this query\n * using vector similarity search.\n *\n * @example\n * // Find technical documentation about APIs\n * query: \"technical documentation about REST APIs\"\n *\n * // Find recent ML papers\n * query: \"machine learning papers from 2023\"\n */\n query?: string;\n}\n\n/**\n * Operation to store, update, or delete an item.\n */\nexport interface PutOperation {\n /**\n * Hierarchical path for the item.\n * Acts as a folder-like structure to organize items.\n * Each element represents one level in the hierarchy.\n *\n * @example\n * // Root level documents\n * namespace: [\"documents\"]\n *\n * // User-specific documents\n * namespace: [\"documents\", \"user123\"]\n *\n * // Nested cache structure\n * namespace: [\"cache\", \"docs\", \"v1\"]\n */\n namespace: string[];\n\n /**\n * Unique identifier for the document within its namespace.\n * Together with namespace forms the complete path to the item.\n *\n * Example: If namespace is [\"documents\", \"user123\"] and key is \"report1\",\n * the full path would effectively be \"documents/user123/report1\"\n */\n key: string;\n\n /**\n * Data to be stored, or null to delete the item.\n * Must be a JSON-serializable object with string keys.\n * Setting to null signals that the item should be deleted.\n *\n * @example\n * {\n * field1: \"string value\",\n * field2: 123,\n * nested: { can: \"contain\", any: \"serializable data\" }\n * }\n */\n value: Record<string, any> | null;\n\n /**\n * Controls how the item's fields are indexed for search operations.\n *\n * - undefined: Uses store's default indexing configuration\n * - false: Disables indexing for this item\n * - string[]: List of field paths to index\n *\n * Path syntax supports:\n * - Nested fields: \"metadata.title\"\n * - Array access: \"chapters[*].content\" (each indexed separately)\n * - Specific indices: \"authors[0].name\"\n *\n * @example\n * // Index specific fields\n * index: [\"metadata.title\", \"chapters[*].content\"]\n *\n * // Disable indexing\n * index: false\n */\n index?: false | string[];\n}\n\n/**\n * Operation to list and filter namespaces in the store.\n */\nexport interface ListNamespacesOperation {\n matchConditions?: MatchCondition[];\n maxDepth?: number;\n limit: number;\n offset: number;\n}\n\nexport type NameSpacePath = (string | \"*\")[];\n\nexport type NamespaceMatchType = \"prefix\" | \"suffix\";\n\nexport interface MatchCondition {\n matchType: NamespaceMatchType;\n path: NameSpacePath;\n}\n\nexport type Operation =\n | GetOperation\n | SearchOperation\n | PutOperation\n | ListNamespacesOperation;\n\nexport type OperationResults<Tuple extends readonly Operation[]> = {\n [K in keyof Tuple]: Tuple[K] extends PutOperation\n ? void\n : Tuple[K] extends SearchOperation\n ? SearchItem[]\n : Tuple[K] extends GetOperation\n ? Item | null\n : Tuple[K] extends ListNamespacesOperation\n ? string[][]\n : never;\n};\n\n/**\n * Configuration for indexing documents for semantic search in the store.\n *\n * This configures how documents are embedded and indexed for vector similarity search.\n */\nexport interface IndexConfig {\n /**\n * Number of dimensions in the embedding vectors.\n *\n * Common embedding model dimensions:\n * - OpenAI text-embedding-3-large: 256, 1024, or 3072\n * - OpenAI text-embedding-3-small: 512 or 1536\n * - OpenAI text-embedding-ada-002: 1536\n * - Cohere embed-english-v3.0: 1024\n * - Cohere embed-english-light-v3.0: 384\n * - Cohere embed-multilingual-v3.0: 1024\n * - Cohere embed-multilingual-light-v3.0: 384\n */\n dims: number;\n\n /**\n * The embeddings model to use for generating vectors.\n * This should be a LangChain Embeddings implementation.\n */\n embeddings: Embeddings;\n\n /**\n * Fields to extract text from for embedding generation.\n *\n * Path syntax supports:\n * - Simple field access: \"field\"\n * - Nested fields: \"metadata.title\"\n * - Array indexing:\n * - All elements: \"chapters[*].content\"\n * - Specific index: \"authors[0].name\"\n * - Last element: \"array[-1]\"\n *\n * @default [\"$\"] Embeds the entire document as one vector\n */\n fields?: string[];\n}\n\n/**\n * Utility function to get text at a specific JSON path\n */\nexport function getTextAtPath(obj: any, path: string): string[] {\n const parts = path.split(\".\");\n let current: any = obj;\n\n for (const part of parts) {\n if (part.includes(\"[\")) {\n const [arrayName, indexStr] = part.split(\"[\");\n const index = indexStr.replace(\"]\", \"\");\n\n if (!current[arrayName]) return [];\n\n if (index === \"*\") {\n const results: string[] = [];\n for (const item of current[arrayName]) {\n if (typeof item === \"string\") results.push(item);\n }\n return results;\n }\n\n const idx = parseInt(index, 10);\n if (Number.isNaN(idx)) return [];\n current = current[arrayName][idx];\n } else {\n current = current[part];\n }\n\n if (current === undefined) return [];\n }\n\n return typeof current === \"string\" ? [current] : [];\n}\n\n/**\n * Tokenizes a JSON path into parts\n */\nexport function tokenizePath(path: string): string[] {\n return path.split(\".\");\n}\n\n/**\n * Abstract base class for persistent key-value stores.\n *\n * Stores enable persistence and memory that can be shared across threads,\n * scoped to user IDs, assistant IDs, or other arbitrary namespaces.\n *\n * Features:\n * - Hierarchical namespaces for organization\n * - Key-value storage with metadata\n * - Vector similarity search (if configured)\n * - Filtering and pagination\n */\nexport abstract class BaseStore {\n /**\n * Execute multiple operations in a single batch.\n * This is more efficient than executing operations individually.\n *\n * @param operations Array of operations to execute\n * @returns Promise resolving to results matching the operations\n */\n abstract batch<Op extends Operation[]>(\n operations: Op\n ): Promise<OperationResults<Op>>;\n\n /**\n * Retrieve a single item by its namespace and key.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n * @returns Promise resolving to the item or null if not found\n */\n async get(namespace: string[], key: string): Promise<Item | null> {\n return (await this.batch<[GetOperation]>([{ namespace, key }]))[0];\n }\n\n /**\n * Search for items within a namespace prefix.\n * Supports both metadata filtering and vector similarity search.\n *\n * @param namespacePrefix Hierarchical path prefix to search within\n * @param options Search options for filtering and pagination\n * @returns Promise resolving to list of matching items with relevance scores\n *\n * @example\n * // Search with filters\n * await store.search([\"documents\"], {\n * filter: { type: \"report\", status: \"active\" },\n * limit: 5,\n * offset: 10\n * });\n *\n * // Vector similarity search\n * await store.search([\"users\", \"content\"], {\n * query: \"technical documentation about APIs\",\n * limit: 20\n * });\n */\n async search(\n namespacePrefix: string[],\n options: {\n filter?: Record<string, any>;\n limit?: number;\n offset?: number;\n query?: string;\n } = {}\n ): Promise<SearchItem[]> {\n const { filter, limit = 10, offset = 0, query } = options;\n return (\n await this.batch<[SearchOperation]>([\n {\n namespacePrefix,\n filter,\n limit,\n offset,\n query,\n },\n ])\n )[0];\n }\n\n /**\n * Store or update an item.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n * @param value Object containing the item's data\n * @param index Optional indexing configuration\n *\n * @example\n * // Simple storage\n * await store.put([\"docs\"], \"report\", { title: \"Annual Report\" });\n *\n * // With specific field indexing\n * await store.put(\n * [\"docs\"],\n * \"report\",\n * {\n * title: \"Q4 Report\",\n * chapters: [{ content: \"...\" }, { content: \"...\" }]\n * },\n * [\"title\", \"chapters[*].content\"]\n * );\n */\n async put(\n namespace: string[],\n key: string,\n value: Record<string, any>,\n index?: false | string[]\n ): Promise<void> {\n validateNamespace(namespace);\n await this.batch<[PutOperation]>([{ namespace, key, value, index }]);\n }\n\n /**\n * Delete an item from the store.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n */\n async delete(namespace: string[], key: string): Promise<void> {\n await this.batch<[PutOperation]>([{ namespace, key, value: null }]);\n }\n\n /**\n * List and filter namespaces in the store.\n * Used to explore data organization and navigate the namespace hierarchy.\n *\n * @param options Options for listing namespaces\n * @returns Promise resolving to list of namespace paths\n *\n * @example\n * // List all namespaces under \"documents\"\n * await store.listNamespaces({\n * prefix: [\"documents\"],\n * maxDepth: 2\n * });\n *\n * // List namespaces ending with \"v1\"\n * await store.listNamespaces({\n * suffix: [\"v1\"],\n * limit: 50\n * });\n */\n async listNamespaces(\n options: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n } = {}\n ): Promise<string[][]> {\n const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;\n\n const matchConditions: MatchCondition[] = [];\n if (prefix) {\n matchConditions.push({ matchType: \"prefix\", path: prefix });\n }\n if (suffix) {\n matchConditions.push({ matchType: \"suffix\", path: suffix });\n }\n\n return (\n await this.batch<[ListNamespacesOperation]>([\n {\n matchConditions: matchConditions.length ? matchConditions : undefined,\n maxDepth,\n limit,\n offset,\n },\n ])\n )[0];\n }\n\n /**\n * Start the store. Override if initialization is needed.\n */\n start(): void | Promise<void> {}\n\n /**\n * Stop the store. Override if cleanup is needed.\n */\n stop(): void | Promise<void> {}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base.cjs","names":[],"sources":["../../src/store/base.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Embeddings } from \"@langchain/core/embeddings\";\n\n/**\n * Error thrown when an invalid namespace is provided.\n */\nexport class InvalidNamespaceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidNamespaceError\";\n }\n}\n\n/**\n * Validates the provided namespace.\n * @param namespace The namespace to validate.\n * @param options.allowEmpty When true, an empty namespace is permitted\n * (`search([])` means \"search everything\"). Put still rejects empty.\n * @param options.allowReservedRoot When true, the `langgraph` root label is\n * permitted. Put still rejects it; search may read a namespace written via\n * `batch()` which bypasses put validation.\n * @throws {InvalidNamespaceError} If the namespace is invalid.\n */\nexport function validateNamespace(\n namespace: string[],\n options: { allowEmpty?: boolean; allowReservedRoot?: boolean } = {}\n): void {\n if (namespace.length === 0) {\n if (options.allowEmpty) {\n return;\n }\n throw new InvalidNamespaceError(\"Namespace cannot be empty.\");\n }\n for (const label of namespace) {\n if (typeof label !== \"string\") {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels ` +\n `must be strings, but got ${typeof label}.`\n );\n }\n if (label.includes(\".\")) {\n throw new InvalidNamespaceError(\n `Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`\n );\n }\n if (label === \"\") {\n throw new InvalidNamespaceError(\n `Namespace labels cannot be empty strings. Got ${label} in ${namespace}`\n );\n }\n }\n\n if (namespace[0] === \"langgraph\" && !options.allowReservedRoot) {\n throw new InvalidNamespaceError(\n `Root label for namespace cannot be \"langgraph\". Got: ${namespace}`\n );\n }\n}\n\n/**\n * Represents a stored item with metadata.\n */\nexport interface Item {\n /**\n * The stored data as an object. Keys are filterable.\n */\n value: Record<string, any>;\n /**\n * Unique identifier within the namespace.\n */\n key: string;\n /**\n * Hierarchical path defining the collection in which this document resides.\n * Represented as an array of strings, allowing for nested categorization.\n * For example: [\"documents\", \"user123\"]\n */\n namespace: string[];\n /**\n * Timestamp of item creation.\n */\n createdAt: Date;\n /**\n * Timestamp of last update.\n */\n updatedAt: Date;\n}\n\n/**\n * Represents a search result item with relevance score.\n * Extends the base Item interface with an optional similarity score.\n */\nexport interface SearchItem extends Item {\n /**\n * Relevance/similarity score if from a ranked operation.\n * Higher scores indicate better matches.\n *\n * This is typically a cosine similarity score between -1 and 1,\n * where 1 indicates identical vectors and -1 indicates opposite vectors.\n */\n score?: number;\n}\n\n/**\n * Operation to retrieve an item by namespace and ID.\n */\nexport interface GetOperation {\n /**\n * Hierarchical path for the item.\n *\n * @example\n * // Get a user profile\n * namespace: [\"users\", \"profiles\"]\n */\n namespace: string[];\n\n /**\n * Unique identifier within the namespace.\n * Together with namespace forms the complete path to the item.\n *\n * @example\n * key: \"user123\" // For a user profile\n * key: \"doc456\" // For a document\n */\n key: string;\n}\n\n/**\n * Operation to search for items within a namespace prefix.\n */\nexport interface SearchOperation {\n /**\n * Hierarchical path prefix to search within.\n * Only items under this prefix will be searched.\n *\n * @example\n * // Search all user documents\n * namespacePrefix: [\"users\", \"documents\"]\n *\n * // Search everything\n * namespacePrefix: []\n */\n namespacePrefix: string[];\n\n /**\n * Key-value pairs to filter results based on exact matches or comparison operators.\n *\n * Supports both exact matches and operator-based comparisons:\n * - $eq: Equal to (same as direct value comparison)\n * - $ne: Not equal to\n * - $gt: Greater than\n * - $gte: Greater than or equal to\n * - $lt: Less than\n * - $lte: Less than or equal to\n *\n * @example\n * // Exact match\n * filter: { status: \"active\" }\n *\n * // With operators\n * filter: { score: { $gt: 4.99 } }\n *\n * // Multiple conditions\n * filter: {\n * score: { $gte: 3.0 },\n * color: \"red\"\n * }\n */\n filter?: Record<string, any>;\n\n /**\n * Maximum number of items to return.\n * @default 10\n */\n limit?: number;\n\n /**\n * Number of items to skip before returning results.\n * Useful for pagination.\n * @default 0\n */\n offset?: number;\n\n /**\n * Natural language search query for semantic search.\n * When provided, results will be ranked by relevance to this query\n * using vector similarity search.\n *\n * @example\n * // Find technical documentation about APIs\n * query: \"technical documentation about REST APIs\"\n *\n * // Find recent ML papers\n * query: \"machine learning papers from 2023\"\n */\n query?: string;\n}\n\n/**\n * Operation to store, update, or delete an item.\n */\nexport interface PutOperation {\n /**\n * Hierarchical path for the item.\n * Acts as a folder-like structure to organize items.\n * Each element represents one level in the hierarchy.\n *\n * @example\n * // Root level documents\n * namespace: [\"documents\"]\n *\n * // User-specific documents\n * namespace: [\"documents\", \"user123\"]\n *\n * // Nested cache structure\n * namespace: [\"cache\", \"docs\", \"v1\"]\n */\n namespace: string[];\n\n /**\n * Unique identifier for the document within its namespace.\n * Together with namespace forms the complete path to the item.\n *\n * Example: If namespace is [\"documents\", \"user123\"] and key is \"report1\",\n * the full path would effectively be \"documents/user123/report1\"\n */\n key: string;\n\n /**\n * Data to be stored, or null to delete the item.\n * Must be a JSON-serializable object with string keys.\n * Setting to null signals that the item should be deleted.\n *\n * @example\n * {\n * field1: \"string value\",\n * field2: 123,\n * nested: { can: \"contain\", any: \"serializable data\" }\n * }\n */\n value: Record<string, any> | null;\n\n /**\n * Controls how the item's fields are indexed for search operations.\n *\n * - undefined: Uses store's default indexing configuration\n * - false: Disables indexing for this item\n * - string[]: List of field paths to index\n *\n * Path syntax supports:\n * - Nested fields: \"metadata.title\"\n * - Array access: \"chapters[*].content\" (each indexed separately)\n * - Specific indices: \"authors[0].name\"\n *\n * @example\n * // Index specific fields\n * index: [\"metadata.title\", \"chapters[*].content\"]\n *\n * // Disable indexing\n * index: false\n */\n index?: false | string[];\n}\n\n/**\n * Operation to list and filter namespaces in the store.\n */\nexport interface ListNamespacesOperation {\n matchConditions?: MatchCondition[];\n maxDepth?: number;\n limit: number;\n offset: number;\n}\n\nexport type NameSpacePath = (string | \"*\")[];\n\nexport type NamespaceMatchType = \"prefix\" | \"suffix\";\n\nexport interface MatchCondition {\n matchType: NamespaceMatchType;\n path: NameSpacePath;\n}\n\nexport type Operation =\n | GetOperation\n | SearchOperation\n | PutOperation\n | ListNamespacesOperation;\n\nexport type OperationResults<Tuple extends readonly Operation[]> = {\n [K in keyof Tuple]: Tuple[K] extends PutOperation\n ? void\n : Tuple[K] extends SearchOperation\n ? SearchItem[]\n : Tuple[K] extends GetOperation\n ? Item | null\n : Tuple[K] extends ListNamespacesOperation\n ? string[][]\n : never;\n};\n\n/**\n * Configuration for indexing documents for semantic search in the store.\n *\n * This configures how documents are embedded and indexed for vector similarity search.\n */\nexport interface IndexConfig {\n /**\n * Number of dimensions in the embedding vectors.\n *\n * Common embedding model dimensions:\n * - OpenAI text-embedding-3-large: 256, 1024, or 3072\n * - OpenAI text-embedding-3-small: 512 or 1536\n * - OpenAI text-embedding-ada-002: 1536\n * - Cohere embed-english-v3.0: 1024\n * - Cohere embed-english-light-v3.0: 384\n * - Cohere embed-multilingual-v3.0: 1024\n * - Cohere embed-multilingual-light-v3.0: 384\n */\n dims: number;\n\n /**\n * The embeddings model to use for generating vectors.\n * This should be a LangChain Embeddings implementation.\n */\n embeddings: Embeddings;\n\n /**\n * Fields to extract text from for embedding generation.\n *\n * Path syntax supports:\n * - Simple field access: \"field\"\n * - Nested fields: \"metadata.title\"\n * - Array indexing:\n * - All elements: \"chapters[*].content\"\n * - Specific index: \"authors[0].name\"\n * - Last element: \"array[-1]\"\n *\n * @default [\"$\"] Embeds the entire document as one vector\n */\n fields?: string[];\n}\n\n/**\n * Utility function to get text at a specific JSON path\n */\nexport function getTextAtPath(obj: any, path: string): string[] {\n const parts = path.split(\".\");\n let current: any = obj;\n\n for (const part of parts) {\n if (part.includes(\"[\")) {\n const [arrayName, indexStr] = part.split(\"[\");\n const index = indexStr.replace(\"]\", \"\");\n\n if (!current[arrayName]) return [];\n\n if (index === \"*\") {\n const results: string[] = [];\n for (const item of current[arrayName]) {\n if (typeof item === \"string\") results.push(item);\n }\n return results;\n }\n\n const idx = parseInt(index, 10);\n if (Number.isNaN(idx)) return [];\n current = current[arrayName][idx];\n } else {\n current = current[part];\n }\n\n if (current === undefined) return [];\n }\n\n return typeof current === \"string\" ? [current] : [];\n}\n\n/**\n * Tokenizes a JSON path into parts\n */\nexport function tokenizePath(path: string): string[] {\n return path.split(\".\");\n}\n\n/**\n * Abstract base class for persistent key-value stores.\n *\n * Stores enable persistence and memory that can be shared across threads,\n * scoped to user IDs, assistant IDs, or other arbitrary namespaces.\n *\n * Features:\n * - Hierarchical namespaces for organization\n * - Key-value storage with metadata\n * - Vector similarity search (if configured)\n * - Filtering and pagination\n */\nexport abstract class BaseStore {\n /**\n * Execute multiple operations in a single batch.\n * This is more efficient than executing operations individually.\n *\n * @param operations Array of operations to execute\n * @returns Promise resolving to results matching the operations\n */\n abstract batch<Op extends Operation[]>(\n operations: Op\n ): Promise<OperationResults<Op>>;\n\n /**\n * Retrieve a single item by its namespace and key.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n * @returns Promise resolving to the item or null if not found\n */\n async get(namespace: string[], key: string): Promise<Item | null> {\n validateNamespace(namespace, { allowEmpty: true, allowReservedRoot: true });\n return (await this.batch<[GetOperation]>([{ namespace, key }]))[0];\n }\n\n /**\n * Search for items within a namespace prefix.\n * Supports both metadata filtering and vector similarity search.\n *\n * @param namespacePrefix Hierarchical path prefix to search within\n * @param options Search options for filtering and pagination\n * @returns Promise resolving to list of matching items with relevance scores\n *\n * @example\n * // Search with filters\n * await store.search([\"documents\"], {\n * filter: { type: \"report\", status: \"active\" },\n * limit: 5,\n * offset: 10\n * });\n *\n * // Vector similarity search\n * await store.search([\"users\", \"content\"], {\n * query: \"technical documentation about APIs\",\n * limit: 20\n * });\n */\n async search(\n namespacePrefix: string[],\n options: {\n filter?: Record<string, any>;\n limit?: number;\n offset?: number;\n query?: string;\n } = {}\n ): Promise<SearchItem[]> {\n validateNamespace(namespacePrefix, {\n allowEmpty: true,\n allowReservedRoot: true,\n });\n const { filter, limit = 10, offset = 0, query } = options;\n return (\n await this.batch<[SearchOperation]>([\n {\n namespacePrefix,\n filter,\n limit,\n offset,\n query,\n },\n ])\n )[0];\n }\n\n /**\n * Store or update an item.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n * @param value Object containing the item's data\n * @param index Optional indexing configuration\n *\n * @example\n * // Simple storage\n * await store.put([\"docs\"], \"report\", { title: \"Annual Report\" });\n *\n * // With specific field indexing\n * await store.put(\n * [\"docs\"],\n * \"report\",\n * {\n * title: \"Q4 Report\",\n * chapters: [{ content: \"...\" }, { content: \"...\" }]\n * },\n * [\"title\", \"chapters[*].content\"]\n * );\n */\n async put(\n namespace: string[],\n key: string,\n value: Record<string, any>,\n index?: false | string[]\n ): Promise<void> {\n validateNamespace(namespace);\n await this.batch<[PutOperation]>([{ namespace, key, value, index }]);\n }\n\n /**\n * Delete an item from the store.\n *\n * @param namespace Hierarchical path for the item\n * @param key Unique identifier within the namespace\n */\n async delete(namespace: string[], key: string): Promise<void> {\n validateNamespace(namespace, { allowEmpty: true, allowReservedRoot: true });\n await this.batch<[PutOperation]>([{ namespace, key, value: null }]);\n }\n\n /**\n * List and filter namespaces in the store.\n * Used to explore data organization and navigate the namespace hierarchy.\n *\n * @param options Options for listing namespaces\n * @returns Promise resolving to list of namespace paths\n *\n * @example\n * // List all namespaces under \"documents\"\n * await store.listNamespaces({\n * prefix: [\"documents\"],\n * maxDepth: 2\n * });\n *\n * // List namespaces ending with \"v1\"\n * await store.listNamespaces({\n * suffix: [\"v1\"],\n * limit: 50\n * });\n */\n async listNamespaces(\n options: {\n prefix?: string[];\n suffix?: string[];\n maxDepth?: number;\n limit?: number;\n offset?: number;\n } = {}\n ): Promise<string[][]> {\n const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;\n\n const matchConditions: MatchCondition[] = [];\n if (prefix) {\n validateNamespace(prefix, { allowEmpty: true, allowReservedRoot: true });\n matchConditions.push({ matchType: \"prefix\", path: prefix });\n }\n if (suffix) {\n validateNamespace(suffix, { allowEmpty: true, allowReservedRoot: true });\n matchConditions.push({ matchType: \"suffix\", path: suffix });\n }\n\n return (\n await this.batch<[ListNamespacesOperation]>([\n {\n matchConditions: matchConditions.length ? matchConditions : undefined,\n maxDepth,\n limit,\n offset,\n },\n ])\n )[0];\n }\n\n /**\n * Start the store. Override if initialization is needed.\n */\n start(): void | Promise<void> {}\n\n /**\n * Stop the store. Override if cleanup is needed.\n */\n stop(): void | Promise<void> {}\n}\n"],"mappings":";;;;AAMA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;;;;;;;AAchB,SAAgB,kBACd,WACA,UAAiE,EAAE,EAC7D;AACN,KAAI,UAAU,WAAW,GAAG;AAC1B,MAAI,QAAQ,WACV;AAEF,QAAM,IAAI,sBAAsB,6BAA6B;;AAE/D,MAAK,MAAM,SAAS,WAAW;AAC7B,MAAI,OAAO,UAAU,SACnB,OAAM,IAAI,sBACR,4BAA4B,MAAM,aAAa,UAAU,8CAC3B,OAAO,MAAM,GAC5C;AAEH,MAAI,MAAM,SAAS,IAAI,CACrB,OAAM,IAAI,sBACR,4BAA4B,MAAM,aAAa,UAAU,kDAC1D;AAEH,MAAI,UAAU,GACZ,OAAM,IAAI,sBACR,iDAAiD,MAAM,MAAM,YAC9D;;AAIL,KAAI,UAAU,OAAO,eAAe,CAAC,QAAQ,kBAC3C,OAAM,IAAI,sBACR,wDAAwD,YACzD;;;;;AAkSL,SAAgB,cAAc,KAAU,MAAwB;CAC9D,MAAM,QAAQ,KAAK,MAAM,IAAI;CAC7B,IAAI,UAAe;AAEnB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,SAAS,IAAI,EAAE;GACtB,MAAM,CAAC,WAAW,YAAY,KAAK,MAAM,IAAI;GAC7C,MAAM,QAAQ,SAAS,QAAQ,KAAK,GAAG;AAEvC,OAAI,CAAC,QAAQ,WAAY,QAAO,EAAE;AAElC,OAAI,UAAU,KAAK;IACjB,MAAM,UAAoB,EAAE;AAC5B,SAAK,MAAM,QAAQ,QAAQ,WACzB,KAAI,OAAO,SAAS,SAAU,SAAQ,KAAK,KAAK;AAElD,WAAO;;GAGT,MAAM,MAAM,SAAS,OAAO,GAAG;AAC/B,OAAI,OAAO,MAAM,IAAI,CAAE,QAAO,EAAE;AAChC,aAAU,QAAQ,WAAW;QAE7B,WAAU,QAAQ;AAGpB,MAAI,YAAY,KAAA,EAAW,QAAO,EAAE;;AAGtC,QAAO,OAAO,YAAY,WAAW,CAAC,QAAQ,GAAG,EAAE;;;;;AAMrD,SAAgB,aAAa,MAAwB;AACnD,QAAO,KAAK,MAAM,IAAI;;;;;;;;;;;;;;AAexB,IAAsB,YAAtB,MAAgC;;;;;;;;CAmB9B,MAAM,IAAI,WAAqB,KAAmC;AAChE,oBAAkB,WAAW;GAAE,YAAY;GAAM,mBAAmB;GAAM,CAAC;AAC3E,UAAQ,MAAM,KAAK,MAAsB,CAAC;GAAE;GAAW;GAAK,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAyBlE,MAAM,OACJ,iBACA,UAKI,EAAE,EACiB;AACvB,oBAAkB,iBAAiB;GACjC,YAAY;GACZ,mBAAmB;GACpB,CAAC;EACF,MAAM,EAAE,QAAQ,QAAQ,IAAI,SAAS,GAAG,UAAU;AAClD,UACE,MAAM,KAAK,MAAyB,CAClC;GACE;GACA;GACA;GACA;GACA;GACD,CACF,CAAC,EACF;;;;;;;;;;;;;;;;;;;;;;;;;CA0BJ,MAAM,IACJ,WACA,KACA,OACA,OACe;AACf,oBAAkB,UAAU;AAC5B,QAAM,KAAK,MAAsB,CAAC;GAAE;GAAW;GAAK;GAAO;GAAO,CAAC,CAAC;;;;;;;;CAStE,MAAM,OAAO,WAAqB,KAA4B;AAC5D,oBAAkB,WAAW;GAAE,YAAY;GAAM,mBAAmB;GAAM,CAAC;AAC3E,QAAM,KAAK,MAAsB,CAAC;GAAE;GAAW;GAAK,OAAO;GAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;CAuBrE,MAAM,eACJ,UAMI,EAAE,EACe;EACrB,MAAM,EAAE,QAAQ,QAAQ,UAAU,QAAQ,KAAK,SAAS,MAAM;EAE9D,MAAM,kBAAoC,EAAE;AAC5C,MAAI,QAAQ;AACV,qBAAkB,QAAQ;IAAE,YAAY;IAAM,mBAAmB;IAAM,CAAC;AACxE,mBAAgB,KAAK;IAAE,WAAW;IAAU,MAAM;IAAQ,CAAC;;AAE7D,MAAI,QAAQ;AACV,qBAAkB,QAAQ;IAAE,YAAY;IAAM,mBAAmB;IAAM,CAAC;AACxE,mBAAgB,KAAK;IAAE,WAAW;IAAU,MAAM;IAAQ,CAAC;;AAG7D,UACE,MAAM,KAAK,MAAiC,CAC1C;GACE,iBAAiB,gBAAgB,SAAS,kBAAkB,KAAA;GAC5D;GACA;GACA;GACD,CACF,CAAC,EACF;;;;;CAMJ,QAA8B;;;;CAK9B,OAA6B"}
|
package/dist/store/base.d.cts
CHANGED
|
@@ -7,6 +7,20 @@ import { Embeddings } from "@langchain/core/embeddings";
|
|
|
7
7
|
declare class InvalidNamespaceError extends Error {
|
|
8
8
|
constructor(message: string);
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates the provided namespace.
|
|
12
|
+
* @param namespace The namespace to validate.
|
|
13
|
+
* @param options.allowEmpty When true, an empty namespace is permitted
|
|
14
|
+
* (`search([])` means "search everything"). Put still rejects empty.
|
|
15
|
+
* @param options.allowReservedRoot When true, the `langgraph` root label is
|
|
16
|
+
* permitted. Put still rejects it; search may read a namespace written via
|
|
17
|
+
* `batch()` which bypasses put validation.
|
|
18
|
+
* @throws {InvalidNamespaceError} If the namespace is invalid.
|
|
19
|
+
*/
|
|
20
|
+
declare function validateNamespace(namespace: string[], options?: {
|
|
21
|
+
allowEmpty?: boolean;
|
|
22
|
+
allowReservedRoot?: boolean;
|
|
23
|
+
}): void;
|
|
10
24
|
/**
|
|
11
25
|
* Represents a stored item with metadata.
|
|
12
26
|
*/
|
|
@@ -387,5 +401,5 @@ declare abstract class BaseStore {
|
|
|
387
401
|
stop(): void | Promise<void>;
|
|
388
402
|
}
|
|
389
403
|
//#endregion
|
|
390
|
-
export { BaseStore, GetOperation, IndexConfig, InvalidNamespaceError, Item, ListNamespacesOperation, MatchCondition, NameSpacePath, NamespaceMatchType, Operation, OperationResults, PutOperation, SearchItem, SearchOperation, getTextAtPath, tokenizePath };
|
|
404
|
+
export { BaseStore, GetOperation, IndexConfig, InvalidNamespaceError, Item, ListNamespacesOperation, MatchCondition, NameSpacePath, NamespaceMatchType, Operation, OperationResults, PutOperation, SearchItem, SearchOperation, getTextAtPath, tokenizePath, validateNamespace };
|
|
391
405
|
//# sourceMappingURL=base.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/store/base.ts"],"mappings":";;;;;AAMA;cAAa,qBAAA,SAA8B,KAAA;EACzC,WAAA,CAAY,OAAA;AAAA
|
|
1
|
+
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/store/base.ts"],"mappings":";;;;;AAMA;cAAa,qBAAA,SAA8B,KAAA;EACzC,WAAA,CAAY,OAAA;AAAA;;;;;;AAgBd;;;;;iBAAgB,iBAAA,CACd,SAAA,YACA,OAAA;EAAW,UAAA;EAAsB,iBAAA;AAAA;;AAqCnC;;UAAiB,IAAA;EAIR;;;EAAP,KAAA,EAAO,MAAA;EAkBQ;;;EAdf,GAAA;EAMA;;;;;EAAA,SAAA;EAQe;AAOjB;;EAXE,SAAA,EAAW,IAAA;EAWuB;;AAcpC;EArBE,SAAA,EAAW,IAAA;AAAA;;;AA6Cb;;UAtCiB,UAAA,SAAmB,IAAA;EA4EnB;;;;;;;EApEf,KAAA;AAAA;AAqGF;;;AAAA,UA/FiB,YAAA;EA+Gf;;;;;;;EAvGA,SAAA;EAyJsC;;;;;;;;EA/ItC,GAAA;AAAA;AAsJF;;;AAAA,UAhJiB,eAAA;EAgJQ;AAEzB;;;;;AAEA;;;;;EAxIE,eAAA;EA0IA;;;;AAGF;;;;;;;;;;;;;;;AAMA;;;;;EAzHE,MAAA,GAAS,MAAA;EA0HiB;;;;EApH1B,KAAA;EAuHM;;;;;EAhHN,MAAA;EAmHc;;;;;;;;;;;;EArGd,KAAA;AAAA;;;;UAMe,YAAA;EA8FP;;;;;;AAWV;;;;;;;;;EAzFE,SAAA;EAiIc;;;;;AAmChB;;EA3JE,GAAA;EA2J2B;;AAgB7B;;;;;;;;;;EA7JE,KAAA,EAAO,MAAA;EAmNI;;;;;;;;;;;;;;;;;;;EA9LX,KAAA;AAAA;;;;UAMe,uBAAA;EACf,eAAA,GAAkB,cAAA;EAClB,QAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA;AAAA,KAEA,kBAAA;AAAA,UAEK,cAAA;EACf,SAAA,EAAW,kBAAA;EACX,IAAA,EAAM,aAAA;AAAA;AAAA,KAGI,SAAA,GACR,YAAA,GACA,eAAA,GACA,YAAA,GACA,uBAAA;AAAA,KAEQ,gBAAA,wBAAwC,SAAA,oBACtC,KAAA,GAAQ,KAAA,CAAM,CAAA,UAAW,YAAA,UAEjC,KAAA,CAAM,CAAA,UAAW,eAAA,GACf,UAAA,KACA,KAAA,CAAM,CAAA,UAAW,YAAA,GACf,IAAA,UACA,KAAA,CAAM,CAAA,UAAW,uBAAA;;;;;;UAUV,WAAA;EAuOX;;;;;;;;;;;;EA1NJ,IAAA;;;;;EAMA,UAAA,EAAY,UAAA;;;;;;;;;;;;;;EAeZ,MAAA;AAAA;;;;iBAMc,aAAA,CAAc,GAAA,OAAU,IAAA;;;;iBAmCxB,YAAA,CAAa,IAAA;;;;;;;;;;;;;uBAgBP,SAAA;;;;;;;;WAQX,KAAA,YAAiB,SAAA,GAAA,CACxB,UAAA,EAAY,EAAA,GACX,OAAA,CAAQ,gBAAA,CAAiB,EAAA;;;;;;;;EAStB,GAAA,CAAI,SAAA,YAAqB,GAAA,WAAc,OAAA,CAAQ,IAAA;;;;;;;;;;;;;;;;;;;;;;;EA2B/C,MAAA,CACJ,eAAA,YACA,OAAA;IACE,MAAA,GAAS,MAAA;IACT,KAAA;IACA,MAAA;IACA,KAAA;EAAA,IAED,OAAA,CAAQ,UAAA;;;;;;;;;;;;;;;;;;;;;;;;EA0CL,GAAA,CACJ,SAAA,YACA,GAAA,UACA,KAAA,EAAO,MAAA,eACP,KAAA,sBACC,OAAA;;;;;;;EAWG,MAAA,CAAO,SAAA,YAAqB,GAAA,WAAc,OAAA;;;;;;;;;;;;;;;;;;;;;EAyB1C,cAAA,CACJ,OAAA;IACE,MAAA;IACA,MAAA;IACA,QAAA;IACA,KAAA;IACA,MAAA;EAAA,IAED,OAAA;;;;EA4BH,KAAA,CAAA,UAAgB,OAAA;;;;EAKhB,IAAA,CAAA,UAAe,OAAA;AAAA"}
|
package/dist/store/base.d.ts
CHANGED
|
@@ -7,6 +7,20 @@ import { Embeddings } from "@langchain/core/embeddings";
|
|
|
7
7
|
declare class InvalidNamespaceError extends Error {
|
|
8
8
|
constructor(message: string);
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates the provided namespace.
|
|
12
|
+
* @param namespace The namespace to validate.
|
|
13
|
+
* @param options.allowEmpty When true, an empty namespace is permitted
|
|
14
|
+
* (`search([])` means "search everything"). Put still rejects empty.
|
|
15
|
+
* @param options.allowReservedRoot When true, the `langgraph` root label is
|
|
16
|
+
* permitted. Put still rejects it; search may read a namespace written via
|
|
17
|
+
* `batch()` which bypasses put validation.
|
|
18
|
+
* @throws {InvalidNamespaceError} If the namespace is invalid.
|
|
19
|
+
*/
|
|
20
|
+
declare function validateNamespace(namespace: string[], options?: {
|
|
21
|
+
allowEmpty?: boolean;
|
|
22
|
+
allowReservedRoot?: boolean;
|
|
23
|
+
}): void;
|
|
10
24
|
/**
|
|
11
25
|
* Represents a stored item with metadata.
|
|
12
26
|
*/
|
|
@@ -387,5 +401,5 @@ declare abstract class BaseStore {
|
|
|
387
401
|
stop(): void | Promise<void>;
|
|
388
402
|
}
|
|
389
403
|
//#endregion
|
|
390
|
-
export { BaseStore, GetOperation, IndexConfig, InvalidNamespaceError, Item, ListNamespacesOperation, MatchCondition, NameSpacePath, NamespaceMatchType, Operation, OperationResults, PutOperation, SearchItem, SearchOperation, getTextAtPath, tokenizePath };
|
|
404
|
+
export { BaseStore, GetOperation, IndexConfig, InvalidNamespaceError, Item, ListNamespacesOperation, MatchCondition, NameSpacePath, NamespaceMatchType, Operation, OperationResults, PutOperation, SearchItem, SearchOperation, getTextAtPath, tokenizePath, validateNamespace };
|
|
391
405
|
//# sourceMappingURL=base.d.ts.map
|
package/dist/store/base.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/store/base.ts"],"mappings":";;;;;AAMA;cAAa,qBAAA,SAA8B,KAAA;EACzC,WAAA,CAAY,OAAA;AAAA
|
|
1
|
+
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/store/base.ts"],"mappings":";;;;;AAMA;cAAa,qBAAA,SAA8B,KAAA;EACzC,WAAA,CAAY,OAAA;AAAA;;;;;;AAgBd;;;;;iBAAgB,iBAAA,CACd,SAAA,YACA,OAAA;EAAW,UAAA;EAAsB,iBAAA;AAAA;;AAqCnC;;UAAiB,IAAA;EAIR;;;EAAP,KAAA,EAAO,MAAA;EAkBQ;;;EAdf,GAAA;EAMA;;;;;EAAA,SAAA;EAQe;AAOjB;;EAXE,SAAA,EAAW,IAAA;EAWuB;;AAcpC;EArBE,SAAA,EAAW,IAAA;AAAA;;;AA6Cb;;UAtCiB,UAAA,SAAmB,IAAA;EA4EnB;;;;;;;EApEf,KAAA;AAAA;AAqGF;;;AAAA,UA/FiB,YAAA;EA+Gf;;;;;;;EAvGA,SAAA;EAyJsC;;;;;;;;EA/ItC,GAAA;AAAA;AAsJF;;;AAAA,UAhJiB,eAAA;EAgJQ;AAEzB;;;;;AAEA;;;;;EAxIE,eAAA;EA0IA;;;;AAGF;;;;;;;;;;;;;;;AAMA;;;;;EAzHE,MAAA,GAAS,MAAA;EA0HiB;;;;EApH1B,KAAA;EAuHM;;;;;EAhHN,MAAA;EAmHc;;;;;;;;;;;;EArGd,KAAA;AAAA;;;;UAMe,YAAA;EA8FP;;;;;;AAWV;;;;;;;;;EAzFE,SAAA;EAiIc;;;;;AAmChB;;EA3JE,GAAA;EA2J2B;;AAgB7B;;;;;;;;;;EA7JE,KAAA,EAAO,MAAA;EAmNI;;;;;;;;;;;;;;;;;;;EA9LX,KAAA;AAAA;;;;UAMe,uBAAA;EACf,eAAA,GAAkB,cAAA;EAClB,QAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,KAGU,aAAA;AAAA,KAEA,kBAAA;AAAA,UAEK,cAAA;EACf,SAAA,EAAW,kBAAA;EACX,IAAA,EAAM,aAAA;AAAA;AAAA,KAGI,SAAA,GACR,YAAA,GACA,eAAA,GACA,YAAA,GACA,uBAAA;AAAA,KAEQ,gBAAA,wBAAwC,SAAA,oBACtC,KAAA,GAAQ,KAAA,CAAM,CAAA,UAAW,YAAA,UAEjC,KAAA,CAAM,CAAA,UAAW,eAAA,GACf,UAAA,KACA,KAAA,CAAM,CAAA,UAAW,YAAA,GACf,IAAA,UACA,KAAA,CAAM,CAAA,UAAW,uBAAA;;;;;;UAUV,WAAA;EAuOX;;;;;;;;;;;;EA1NJ,IAAA;;;;;EAMA,UAAA,EAAY,UAAA;;;;;;;;;;;;;;EAeZ,MAAA;AAAA;;;;iBAMc,aAAA,CAAc,GAAA,OAAU,IAAA;;;;iBAmCxB,YAAA,CAAa,IAAA;;;;;;;;;;;;;uBAgBP,SAAA;;;;;;;;WAQX,KAAA,YAAiB,SAAA,GAAA,CACxB,UAAA,EAAY,EAAA,GACX,OAAA,CAAQ,gBAAA,CAAiB,EAAA;;;;;;;;EAStB,GAAA,CAAI,SAAA,YAAqB,GAAA,WAAc,OAAA,CAAQ,IAAA;;;;;;;;;;;;;;;;;;;;;;;EA2B/C,MAAA,CACJ,eAAA,YACA,OAAA;IACE,MAAA,GAAS,MAAA;IACT,KAAA;IACA,MAAA;IACA,KAAA;EAAA,IAED,OAAA,CAAQ,UAAA;;;;;;;;;;;;;;;;;;;;;;;;EA0CL,GAAA,CACJ,SAAA,YACA,GAAA,UACA,KAAA,EAAO,MAAA,eACP,KAAA,sBACC,OAAA;;;;;;;EAWG,MAAA,CAAO,SAAA,YAAqB,GAAA,WAAc,OAAA;;;;;;;;;;;;;;;;;;;;;EAyB1C,cAAA,CACJ,OAAA;IACE,MAAA;IACA,MAAA;IACA,QAAA;IACA,KAAA;IACA,MAAA;EAAA,IAED,OAAA;;;;EA4BH,KAAA,CAAA,UAAgB,OAAA;;;;EAKhB,IAAA,CAAA,UAAe,OAAA;AAAA"}
|
package/dist/store/base.js
CHANGED
|
@@ -11,16 +11,24 @@ var InvalidNamespaceError = class extends Error {
|
|
|
11
11
|
/**
|
|
12
12
|
* Validates the provided namespace.
|
|
13
13
|
* @param namespace The namespace to validate.
|
|
14
|
+
* @param options.allowEmpty When true, an empty namespace is permitted
|
|
15
|
+
* (`search([])` means "search everything"). Put still rejects empty.
|
|
16
|
+
* @param options.allowReservedRoot When true, the `langgraph` root label is
|
|
17
|
+
* permitted. Put still rejects it; search may read a namespace written via
|
|
18
|
+
* `batch()` which bypasses put validation.
|
|
14
19
|
* @throws {InvalidNamespaceError} If the namespace is invalid.
|
|
15
20
|
*/
|
|
16
|
-
function validateNamespace(namespace) {
|
|
17
|
-
if (namespace.length === 0)
|
|
21
|
+
function validateNamespace(namespace, options = {}) {
|
|
22
|
+
if (namespace.length === 0) {
|
|
23
|
+
if (options.allowEmpty) return;
|
|
24
|
+
throw new InvalidNamespaceError("Namespace cannot be empty.");
|
|
25
|
+
}
|
|
18
26
|
for (const label of namespace) {
|
|
19
27
|
if (typeof label !== "string") throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels must be strings, but got ${typeof label}.`);
|
|
20
28
|
if (label.includes(".")) throw new InvalidNamespaceError(`Invalid namespace label '${label}' found in ${namespace}. Namespace labels cannot contain periods ('.').`);
|
|
21
29
|
if (label === "") throw new InvalidNamespaceError(`Namespace labels cannot be empty strings. Got ${label} in ${namespace}`);
|
|
22
30
|
}
|
|
23
|
-
if (namespace[0] === "langgraph") throw new InvalidNamespaceError(`Root label for namespace cannot be "langgraph". Got: ${namespace}`);
|
|
31
|
+
if (namespace[0] === "langgraph" && !options.allowReservedRoot) throw new InvalidNamespaceError(`Root label for namespace cannot be "langgraph". Got: ${namespace}`);
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Utility function to get text at a specific JSON path
|
|
@@ -73,6 +81,10 @@ var BaseStore = class {
|
|
|
73
81
|
* @returns Promise resolving to the item or null if not found
|
|
74
82
|
*/
|
|
75
83
|
async get(namespace, key) {
|
|
84
|
+
validateNamespace(namespace, {
|
|
85
|
+
allowEmpty: true,
|
|
86
|
+
allowReservedRoot: true
|
|
87
|
+
});
|
|
76
88
|
return (await this.batch([{
|
|
77
89
|
namespace,
|
|
78
90
|
key
|
|
@@ -101,6 +113,10 @@ var BaseStore = class {
|
|
|
101
113
|
* });
|
|
102
114
|
*/
|
|
103
115
|
async search(namespacePrefix, options = {}) {
|
|
116
|
+
validateNamespace(namespacePrefix, {
|
|
117
|
+
allowEmpty: true,
|
|
118
|
+
allowReservedRoot: true
|
|
119
|
+
});
|
|
104
120
|
const { filter, limit = 10, offset = 0, query } = options;
|
|
105
121
|
return (await this.batch([{
|
|
106
122
|
namespacePrefix,
|
|
@@ -149,6 +165,10 @@ var BaseStore = class {
|
|
|
149
165
|
* @param key Unique identifier within the namespace
|
|
150
166
|
*/
|
|
151
167
|
async delete(namespace, key) {
|
|
168
|
+
validateNamespace(namespace, {
|
|
169
|
+
allowEmpty: true,
|
|
170
|
+
allowReservedRoot: true
|
|
171
|
+
});
|
|
152
172
|
await this.batch([{
|
|
153
173
|
namespace,
|
|
154
174
|
key,
|
|
@@ -178,14 +198,26 @@ var BaseStore = class {
|
|
|
178
198
|
async listNamespaces(options = {}) {
|
|
179
199
|
const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;
|
|
180
200
|
const matchConditions = [];
|
|
181
|
-
if (prefix)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
if (prefix) {
|
|
202
|
+
validateNamespace(prefix, {
|
|
203
|
+
allowEmpty: true,
|
|
204
|
+
allowReservedRoot: true
|
|
205
|
+
});
|
|
206
|
+
matchConditions.push({
|
|
207
|
+
matchType: "prefix",
|
|
208
|
+
path: prefix
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (suffix) {
|
|
212
|
+
validateNamespace(suffix, {
|
|
213
|
+
allowEmpty: true,
|
|
214
|
+
allowReservedRoot: true
|
|
215
|
+
});
|
|
216
|
+
matchConditions.push({
|
|
217
|
+
matchType: "suffix",
|
|
218
|
+
path: suffix
|
|
219
|
+
});
|
|
220
|
+
}
|
|
189
221
|
return (await this.batch([{
|
|
190
222
|
matchConditions: matchConditions.length ? matchConditions : void 0,
|
|
191
223
|
maxDepth,
|
|
@@ -203,6 +235,6 @@ var BaseStore = class {
|
|
|
203
235
|
stop() {}
|
|
204
236
|
};
|
|
205
237
|
//#endregion
|
|
206
|
-
export { BaseStore, InvalidNamespaceError, getTextAtPath, tokenizePath };
|
|
238
|
+
export { BaseStore, InvalidNamespaceError, getTextAtPath, tokenizePath, validateNamespace };
|
|
207
239
|
|
|
208
240
|
//# sourceMappingURL=base.js.map
|