@langchain/langgraph-checkpoint 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/base.cjs CHANGED
@@ -12,7 +12,7 @@ function deepCopy(obj) {
12
12
  function emptyCheckpoint() {
13
13
  return {
14
14
  v: 4,
15
- id: require_id.uuid6(-2),
15
+ id: require_id.uuid6(0),
16
16
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17
17
  channel_values: {},
18
18
  channel_versions: {},
package/dist/base.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"base.cjs","names":["uuid6","JsonPlusSerializer","ERROR","SCHEDULED","INTERRUPT","RESUME"],"sources":["../src/base.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport { uuid6 } from \"./id.js\";\nimport type {\n PendingWrite,\n CheckpointPendingWrite,\n CheckpointMetadata,\n} from \"./types.js\";\nimport { ERROR, INTERRUPT, RESUME, SCHEDULED } from \"./serde/types.js\";\nimport { JsonPlusSerializer } from \"./serde/jsonplus.js\";\n\n/** @inline */\ntype ChannelVersion = number | string;\n\nexport type ChannelVersions = Record<string, ChannelVersion>;\n\nexport interface Checkpoint<\n N extends string = string,\n C extends string = string,\n> {\n /**\n * The version of the checkpoint format. Currently 4\n */\n v: number;\n /**\n * Checkpoint ID {uuid6}\n */\n id: string;\n /**\n * Timestamp {new Date().toISOString()}\n */\n ts: string;\n /**\n * @default {}\n */\n channel_values: Record<C, unknown>;\n /**\n * @default {}\n */\n channel_versions: Record<C, ChannelVersion>;\n /**\n * @default {}\n */\n versions_seen: Record<N, Record<C, ChannelVersion>>;\n}\n\nexport interface ReadonlyCheckpoint extends Readonly<Checkpoint> {\n readonly channel_values: Readonly<Record<string, unknown>>;\n readonly channel_versions: Readonly<Record<string, ChannelVersion>>;\n readonly versions_seen: Readonly<\n Record<string, Readonly<Record<string, ChannelVersion>>>\n >;\n}\n\nexport function deepCopy<T>(obj: T): T {\n if (typeof obj !== \"object\" || obj === null) {\n return obj;\n }\n\n const newObj = Array.isArray(obj) ? [] : {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n (newObj as Record<PropertyKey, unknown>)[key] = deepCopy(\n (obj as Record<string, unknown>)[key]\n );\n }\n }\n\n return newObj as T;\n}\n\n/** @hidden */\nexport function emptyCheckpoint(): Checkpoint {\n return {\n v: 4,\n id: uuid6(-2),\n ts: new Date().toISOString(),\n channel_values: {},\n channel_versions: {},\n versions_seen: {},\n };\n}\n\n/** @hidden */\nexport function copyCheckpoint(checkpoint: ReadonlyCheckpoint): Checkpoint {\n return {\n v: checkpoint.v,\n id: checkpoint.id,\n ts: checkpoint.ts,\n channel_values: { ...checkpoint.channel_values },\n channel_versions: { ...checkpoint.channel_versions },\n versions_seen: deepCopy(checkpoint.versions_seen),\n };\n}\n\nexport interface CheckpointTuple {\n config: RunnableConfig;\n checkpoint: Checkpoint;\n metadata?: CheckpointMetadata;\n parentConfig?: RunnableConfig;\n pendingWrites?: CheckpointPendingWrite[];\n}\n\nexport type CheckpointListOptions = {\n limit?: number;\n before?: RunnableConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n filter?: Record<string, any>;\n};\n\nexport abstract class BaseCheckpointSaver<V extends string | number = number> {\n serde: SerializerProtocol = new JsonPlusSerializer();\n\n constructor(serde?: SerializerProtocol) {\n this.serde = serde || this.serde;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const value = await this.getTuple(config);\n return value ? value.checkpoint : undefined;\n }\n\n abstract getTuple(\n config: RunnableConfig\n ): Promise<CheckpointTuple | undefined>;\n\n abstract list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple>;\n\n abstract put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n newVersions: ChannelVersions\n ): Promise<RunnableConfig>;\n\n /**\n * Store intermediate writes linked to a checkpoint.\n */\n abstract putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void>;\n\n /**\n * Delete all checkpoints and writes associated with a specific thread ID.\n * @param threadId The thread ID whose checkpoints should be deleted.\n */\n abstract deleteThread(threadId: string): Promise<void>;\n\n /**\n * Generate the next version ID for a channel.\n *\n * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,\n * as long as they are monotonically increasing.\n */\n getNextVersion(current: V | undefined): V {\n if (typeof current === \"string\") {\n throw new Error(\"Please override this method to use string versions.\");\n }\n return (\n current !== undefined && typeof current === \"number\" ? current + 1 : 1\n ) as V;\n }\n}\n\nexport function compareChannelVersions(\n a: ChannelVersion,\n b: ChannelVersion\n): number {\n if (typeof a === \"number\" && typeof b === \"number\") {\n return Math.sign(a - b);\n }\n\n return String(a).localeCompare(String(b));\n}\n\nexport function maxChannelVersion(\n ...versions: ChannelVersion[]\n): ChannelVersion {\n return versions.reduce((max, version, idx) => {\n if (idx === 0) return version;\n return compareChannelVersions(max, version) >= 0 ? max : version;\n });\n}\n\n/**\n * Mapping from error type to error index.\n * Regular writes just map to their index in the list of writes being saved.\n * Special writes (e.g. errors) map to negative indices, to avoid those writes from\n * conflicting with regular writes.\n * Each Checkpointer implementation should use this mapping in put_writes.\n */\nexport const WRITES_IDX_MAP: Record<string, number> = {\n [ERROR]: -1,\n [SCHEDULED]: -2,\n [INTERRUPT]: -3,\n [RESUME]: -4,\n};\n\nexport function getCheckpointId(config: RunnableConfig): string {\n return (\n config.configurable?.checkpoint_id || config.configurable?.thread_ts || \"\"\n );\n}\n"],"mappings":";;;;AAsDA,SAAgB,SAAY,KAAW;AACrC,KAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,QAAO;CAGT,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,EAAE,GAAG,EAAE;AAE3C,MAAK,MAAM,OAAO,IAChB,KAAI,OAAO,UAAU,eAAe,KAAK,KAAK,IAAI,CAC/C,QAAwC,OAAO,SAC7C,IAAgC,KAClC;AAIL,QAAO;;;AAIT,SAAgB,kBAA8B;AAC5C,QAAO;EACL,GAAG;EACH,IAAIA,WAAAA,MAAM,GAAG;EACb,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB,EAAE;EAClB,kBAAkB,EAAE;EACpB,eAAe,EAAE;EAClB;;;AAIH,SAAgB,eAAe,YAA4C;AACzE,QAAO;EACL,GAAG,WAAW;EACd,IAAI,WAAW;EACf,IAAI,WAAW;EACf,gBAAgB,EAAE,GAAG,WAAW,gBAAgB;EAChD,kBAAkB,EAAE,GAAG,WAAW,kBAAkB;EACpD,eAAe,SAAS,WAAW,cAAc;EAClD;;AAkBH,IAAsB,sBAAtB,MAA8E;CAC5E,QAA4B,IAAIC,iBAAAA,oBAAoB;CAEpD,YAAY,OAA4B;AACtC,OAAK,QAAQ,SAAS,KAAK;;CAG7B,MAAM,IAAI,QAAyD;EACjE,MAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AACzC,SAAO,QAAQ,MAAM,aAAa,KAAA;;;;;;;;CAwCpC,eAAe,SAA2B;AACxC,MAAI,OAAO,YAAY,SACrB,OAAM,IAAI,MAAM,sDAAsD;AAExE,SACE,YAAY,KAAA,KAAa,OAAO,YAAY,WAAW,UAAU,IAAI;;;AAK3E,SAAgB,uBACd,GACA,GACQ;AACR,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO,KAAK,KAAK,IAAI,EAAE;AAGzB,QAAO,OAAO,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;;AAG3C,SAAgB,kBACd,GAAG,UACa;AAChB,QAAO,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAC5C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,uBAAuB,KAAK,QAAQ,IAAI,IAAI,MAAM;GACzD;;;;;;;;;AAUJ,MAAa,iBAAyC;EACnDC,cAAAA,QAAQ;EACRC,cAAAA,YAAY;EACZC,cAAAA,YAAY;EACZC,cAAAA,SAAS;CACX;AAED,SAAgB,gBAAgB,QAAgC;AAC9D,QACE,OAAO,cAAc,iBAAiB,OAAO,cAAc,aAAa"}
1
+ {"version":3,"file":"base.cjs","names":["uuid6","JsonPlusSerializer","ERROR","SCHEDULED","INTERRUPT","RESUME"],"sources":["../src/base.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport { uuid6 } from \"./id.js\";\nimport type {\n PendingWrite,\n CheckpointPendingWrite,\n CheckpointMetadata,\n} from \"./types.js\";\nimport { ERROR, INTERRUPT, RESUME, SCHEDULED } from \"./serde/types.js\";\nimport { JsonPlusSerializer } from \"./serde/jsonplus.js\";\n\n/** @inline */\ntype ChannelVersion = number | string;\n\nexport type ChannelVersions = Record<string, ChannelVersion>;\n\nexport interface Checkpoint<\n N extends string = string,\n C extends string = string,\n> {\n /**\n * The version of the checkpoint format. Currently 4\n */\n v: number;\n /**\n * Checkpoint ID {uuid6}\n */\n id: string;\n /**\n * Timestamp {new Date().toISOString()}\n */\n ts: string;\n /**\n * @default {}\n */\n channel_values: Record<C, unknown>;\n /**\n * @default {}\n */\n channel_versions: Record<C, ChannelVersion>;\n /**\n * @default {}\n */\n versions_seen: Record<N, Record<C, ChannelVersion>>;\n}\n\nexport interface ReadonlyCheckpoint extends Readonly<Checkpoint> {\n readonly channel_values: Readonly<Record<string, unknown>>;\n readonly channel_versions: Readonly<Record<string, ChannelVersion>>;\n readonly versions_seen: Readonly<\n Record<string, Readonly<Record<string, ChannelVersion>>>\n >;\n}\n\nexport function deepCopy<T>(obj: T): T {\n if (typeof obj !== \"object\" || obj === null) {\n return obj;\n }\n\n const newObj = Array.isArray(obj) ? [] : {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n (newObj as Record<PropertyKey, unknown>)[key] = deepCopy(\n (obj as Record<string, unknown>)[key]\n );\n }\n }\n\n return newObj as T;\n}\n\n/** @hidden */\nexport function emptyCheckpoint(): Checkpoint {\n return {\n v: 4,\n id: uuid6(0),\n ts: new Date().toISOString(),\n channel_values: {},\n channel_versions: {},\n versions_seen: {},\n };\n}\n\n/** @hidden */\nexport function copyCheckpoint(checkpoint: ReadonlyCheckpoint): Checkpoint {\n return {\n v: checkpoint.v,\n id: checkpoint.id,\n ts: checkpoint.ts,\n channel_values: { ...checkpoint.channel_values },\n channel_versions: { ...checkpoint.channel_versions },\n versions_seen: deepCopy(checkpoint.versions_seen),\n };\n}\n\nexport interface CheckpointTuple {\n config: RunnableConfig;\n checkpoint: Checkpoint;\n metadata?: CheckpointMetadata;\n parentConfig?: RunnableConfig;\n pendingWrites?: CheckpointPendingWrite[];\n}\n\nexport type CheckpointListOptions = {\n limit?: number;\n before?: RunnableConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n filter?: Record<string, any>;\n};\n\nexport abstract class BaseCheckpointSaver<V extends string | number = number> {\n serde: SerializerProtocol = new JsonPlusSerializer();\n\n constructor(serde?: SerializerProtocol) {\n this.serde = serde || this.serde;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const value = await this.getTuple(config);\n return value ? value.checkpoint : undefined;\n }\n\n abstract getTuple(\n config: RunnableConfig\n ): Promise<CheckpointTuple | undefined>;\n\n abstract list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple>;\n\n abstract put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n newVersions: ChannelVersions\n ): Promise<RunnableConfig>;\n\n /**\n * Store intermediate writes linked to a checkpoint.\n */\n abstract putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void>;\n\n /**\n * Delete all checkpoints and writes associated with a specific thread ID.\n * @param threadId The thread ID whose checkpoints should be deleted.\n */\n abstract deleteThread(threadId: string): Promise<void>;\n\n /**\n * Generate the next version ID for a channel.\n *\n * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,\n * as long as they are monotonically increasing.\n */\n getNextVersion(current: V | undefined): V {\n if (typeof current === \"string\") {\n throw new Error(\"Please override this method to use string versions.\");\n }\n return (\n current !== undefined && typeof current === \"number\" ? current + 1 : 1\n ) as V;\n }\n}\n\nexport function compareChannelVersions(\n a: ChannelVersion,\n b: ChannelVersion\n): number {\n if (typeof a === \"number\" && typeof b === \"number\") {\n return Math.sign(a - b);\n }\n\n return String(a).localeCompare(String(b));\n}\n\nexport function maxChannelVersion(\n ...versions: ChannelVersion[]\n): ChannelVersion {\n return versions.reduce((max, version, idx) => {\n if (idx === 0) return version;\n return compareChannelVersions(max, version) >= 0 ? max : version;\n });\n}\n\n/**\n * Mapping from error type to error index.\n * Regular writes just map to their index in the list of writes being saved.\n * Special writes (e.g. errors) map to negative indices, to avoid those writes from\n * conflicting with regular writes.\n * Each Checkpointer implementation should use this mapping in put_writes.\n */\nexport const WRITES_IDX_MAP: Record<string, number> = {\n [ERROR]: -1,\n [SCHEDULED]: -2,\n [INTERRUPT]: -3,\n [RESUME]: -4,\n};\n\nexport function getCheckpointId(config: RunnableConfig): string {\n return (\n config.configurable?.checkpoint_id || config.configurable?.thread_ts || \"\"\n );\n}\n"],"mappings":";;;;AAsDA,SAAgB,SAAY,KAAW;AACrC,KAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,QAAO;CAGT,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,EAAE,GAAG,EAAE;AAE3C,MAAK,MAAM,OAAO,IAChB,KAAI,OAAO,UAAU,eAAe,KAAK,KAAK,IAAI,CAC/C,QAAwC,OAAO,SAC7C,IAAgC,KAClC;AAIL,QAAO;;;AAIT,SAAgB,kBAA8B;AAC5C,QAAO;EACL,GAAG;EACH,IAAIA,WAAAA,MAAM,EAAE;EACZ,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB,EAAE;EAClB,kBAAkB,EAAE;EACpB,eAAe,EAAE;EAClB;;;AAIH,SAAgB,eAAe,YAA4C;AACzE,QAAO;EACL,GAAG,WAAW;EACd,IAAI,WAAW;EACf,IAAI,WAAW;EACf,gBAAgB,EAAE,GAAG,WAAW,gBAAgB;EAChD,kBAAkB,EAAE,GAAG,WAAW,kBAAkB;EACpD,eAAe,SAAS,WAAW,cAAc;EAClD;;AAkBH,IAAsB,sBAAtB,MAA8E;CAC5E,QAA4B,IAAIC,iBAAAA,oBAAoB;CAEpD,YAAY,OAA4B;AACtC,OAAK,QAAQ,SAAS,KAAK;;CAG7B,MAAM,IAAI,QAAyD;EACjE,MAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AACzC,SAAO,QAAQ,MAAM,aAAa,KAAA;;;;;;;;CAwCpC,eAAe,SAA2B;AACxC,MAAI,OAAO,YAAY,SACrB,OAAM,IAAI,MAAM,sDAAsD;AAExE,SACE,YAAY,KAAA,KAAa,OAAO,YAAY,WAAW,UAAU,IAAI;;;AAK3E,SAAgB,uBACd,GACA,GACQ;AACR,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO,KAAK,KAAK,IAAI,EAAE;AAGzB,QAAO,OAAO,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;;AAG3C,SAAgB,kBACd,GAAG,UACa;AAChB,QAAO,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAC5C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,uBAAuB,KAAK,QAAQ,IAAI,IAAI,MAAM;GACzD;;;;;;;;;AAUJ,MAAa,iBAAyC;EACnDC,cAAAA,QAAQ;EACRC,cAAAA,YAAY;EACZC,cAAAA,YAAY;EACZC,cAAAA,SAAS;CACX;AAED,SAAgB,gBAAgB,QAAgC;AAC9D,QACE,OAAO,cAAc,iBAAiB,OAAO,cAAc,aAAa"}
package/dist/base.js CHANGED
@@ -12,7 +12,7 @@ function deepCopy(obj) {
12
12
  function emptyCheckpoint() {
13
13
  return {
14
14
  v: 4,
15
- id: uuid6(-2),
15
+ id: uuid6(0),
16
16
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17
17
  channel_values: {},
18
18
  channel_versions: {},
package/dist/base.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../src/base.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport { uuid6 } from \"./id.js\";\nimport type {\n PendingWrite,\n CheckpointPendingWrite,\n CheckpointMetadata,\n} from \"./types.js\";\nimport { ERROR, INTERRUPT, RESUME, SCHEDULED } from \"./serde/types.js\";\nimport { JsonPlusSerializer } from \"./serde/jsonplus.js\";\n\n/** @inline */\ntype ChannelVersion = number | string;\n\nexport type ChannelVersions = Record<string, ChannelVersion>;\n\nexport interface Checkpoint<\n N extends string = string,\n C extends string = string,\n> {\n /**\n * The version of the checkpoint format. Currently 4\n */\n v: number;\n /**\n * Checkpoint ID {uuid6}\n */\n id: string;\n /**\n * Timestamp {new Date().toISOString()}\n */\n ts: string;\n /**\n * @default {}\n */\n channel_values: Record<C, unknown>;\n /**\n * @default {}\n */\n channel_versions: Record<C, ChannelVersion>;\n /**\n * @default {}\n */\n versions_seen: Record<N, Record<C, ChannelVersion>>;\n}\n\nexport interface ReadonlyCheckpoint extends Readonly<Checkpoint> {\n readonly channel_values: Readonly<Record<string, unknown>>;\n readonly channel_versions: Readonly<Record<string, ChannelVersion>>;\n readonly versions_seen: Readonly<\n Record<string, Readonly<Record<string, ChannelVersion>>>\n >;\n}\n\nexport function deepCopy<T>(obj: T): T {\n if (typeof obj !== \"object\" || obj === null) {\n return obj;\n }\n\n const newObj = Array.isArray(obj) ? [] : {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n (newObj as Record<PropertyKey, unknown>)[key] = deepCopy(\n (obj as Record<string, unknown>)[key]\n );\n }\n }\n\n return newObj as T;\n}\n\n/** @hidden */\nexport function emptyCheckpoint(): Checkpoint {\n return {\n v: 4,\n id: uuid6(-2),\n ts: new Date().toISOString(),\n channel_values: {},\n channel_versions: {},\n versions_seen: {},\n };\n}\n\n/** @hidden */\nexport function copyCheckpoint(checkpoint: ReadonlyCheckpoint): Checkpoint {\n return {\n v: checkpoint.v,\n id: checkpoint.id,\n ts: checkpoint.ts,\n channel_values: { ...checkpoint.channel_values },\n channel_versions: { ...checkpoint.channel_versions },\n versions_seen: deepCopy(checkpoint.versions_seen),\n };\n}\n\nexport interface CheckpointTuple {\n config: RunnableConfig;\n checkpoint: Checkpoint;\n metadata?: CheckpointMetadata;\n parentConfig?: RunnableConfig;\n pendingWrites?: CheckpointPendingWrite[];\n}\n\nexport type CheckpointListOptions = {\n limit?: number;\n before?: RunnableConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n filter?: Record<string, any>;\n};\n\nexport abstract class BaseCheckpointSaver<V extends string | number = number> {\n serde: SerializerProtocol = new JsonPlusSerializer();\n\n constructor(serde?: SerializerProtocol) {\n this.serde = serde || this.serde;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const value = await this.getTuple(config);\n return value ? value.checkpoint : undefined;\n }\n\n abstract getTuple(\n config: RunnableConfig\n ): Promise<CheckpointTuple | undefined>;\n\n abstract list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple>;\n\n abstract put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n newVersions: ChannelVersions\n ): Promise<RunnableConfig>;\n\n /**\n * Store intermediate writes linked to a checkpoint.\n */\n abstract putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void>;\n\n /**\n * Delete all checkpoints and writes associated with a specific thread ID.\n * @param threadId The thread ID whose checkpoints should be deleted.\n */\n abstract deleteThread(threadId: string): Promise<void>;\n\n /**\n * Generate the next version ID for a channel.\n *\n * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,\n * as long as they are monotonically increasing.\n */\n getNextVersion(current: V | undefined): V {\n if (typeof current === \"string\") {\n throw new Error(\"Please override this method to use string versions.\");\n }\n return (\n current !== undefined && typeof current === \"number\" ? current + 1 : 1\n ) as V;\n }\n}\n\nexport function compareChannelVersions(\n a: ChannelVersion,\n b: ChannelVersion\n): number {\n if (typeof a === \"number\" && typeof b === \"number\") {\n return Math.sign(a - b);\n }\n\n return String(a).localeCompare(String(b));\n}\n\nexport function maxChannelVersion(\n ...versions: ChannelVersion[]\n): ChannelVersion {\n return versions.reduce((max, version, idx) => {\n if (idx === 0) return version;\n return compareChannelVersions(max, version) >= 0 ? max : version;\n });\n}\n\n/**\n * Mapping from error type to error index.\n * Regular writes just map to their index in the list of writes being saved.\n * Special writes (e.g. errors) map to negative indices, to avoid those writes from\n * conflicting with regular writes.\n * Each Checkpointer implementation should use this mapping in put_writes.\n */\nexport const WRITES_IDX_MAP: Record<string, number> = {\n [ERROR]: -1,\n [SCHEDULED]: -2,\n [INTERRUPT]: -3,\n [RESUME]: -4,\n};\n\nexport function getCheckpointId(config: RunnableConfig): string {\n return (\n config.configurable?.checkpoint_id || config.configurable?.thread_ts || \"\"\n );\n}\n"],"mappings":";;;;AAsDA,SAAgB,SAAY,KAAW;AACrC,KAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,QAAO;CAGT,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,EAAE,GAAG,EAAE;AAE3C,MAAK,MAAM,OAAO,IAChB,KAAI,OAAO,UAAU,eAAe,KAAK,KAAK,IAAI,CAC/C,QAAwC,OAAO,SAC7C,IAAgC,KAClC;AAIL,QAAO;;;AAIT,SAAgB,kBAA8B;AAC5C,QAAO;EACL,GAAG;EACH,IAAI,MAAM,GAAG;EACb,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB,EAAE;EAClB,kBAAkB,EAAE;EACpB,eAAe,EAAE;EAClB;;;AAIH,SAAgB,eAAe,YAA4C;AACzE,QAAO;EACL,GAAG,WAAW;EACd,IAAI,WAAW;EACf,IAAI,WAAW;EACf,gBAAgB,EAAE,GAAG,WAAW,gBAAgB;EAChD,kBAAkB,EAAE,GAAG,WAAW,kBAAkB;EACpD,eAAe,SAAS,WAAW,cAAc;EAClD;;AAkBH,IAAsB,sBAAtB,MAA8E;CAC5E,QAA4B,IAAI,oBAAoB;CAEpD,YAAY,OAA4B;AACtC,OAAK,QAAQ,SAAS,KAAK;;CAG7B,MAAM,IAAI,QAAyD;EACjE,MAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AACzC,SAAO,QAAQ,MAAM,aAAa,KAAA;;;;;;;;CAwCpC,eAAe,SAA2B;AACxC,MAAI,OAAO,YAAY,SACrB,OAAM,IAAI,MAAM,sDAAsD;AAExE,SACE,YAAY,KAAA,KAAa,OAAO,YAAY,WAAW,UAAU,IAAI;;;AAK3E,SAAgB,uBACd,GACA,GACQ;AACR,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO,KAAK,KAAK,IAAI,EAAE;AAGzB,QAAO,OAAO,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;;AAG3C,SAAgB,kBACd,GAAG,UACa;AAChB,QAAO,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAC5C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,uBAAuB,KAAK,QAAQ,IAAI,IAAI,MAAM;GACzD;;;;;;;;;AAUJ,MAAa,iBAAyC;EACnD,QAAQ;EACR,YAAY;EACZ,YAAY;EACZ,SAAS;CACX;AAED,SAAgB,gBAAgB,QAAgC;AAC9D,QACE,OAAO,cAAc,iBAAiB,OAAO,cAAc,aAAa"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../src/base.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport { uuid6 } from \"./id.js\";\nimport type {\n PendingWrite,\n CheckpointPendingWrite,\n CheckpointMetadata,\n} from \"./types.js\";\nimport { ERROR, INTERRUPT, RESUME, SCHEDULED } from \"./serde/types.js\";\nimport { JsonPlusSerializer } from \"./serde/jsonplus.js\";\n\n/** @inline */\ntype ChannelVersion = number | string;\n\nexport type ChannelVersions = Record<string, ChannelVersion>;\n\nexport interface Checkpoint<\n N extends string = string,\n C extends string = string,\n> {\n /**\n * The version of the checkpoint format. Currently 4\n */\n v: number;\n /**\n * Checkpoint ID {uuid6}\n */\n id: string;\n /**\n * Timestamp {new Date().toISOString()}\n */\n ts: string;\n /**\n * @default {}\n */\n channel_values: Record<C, unknown>;\n /**\n * @default {}\n */\n channel_versions: Record<C, ChannelVersion>;\n /**\n * @default {}\n */\n versions_seen: Record<N, Record<C, ChannelVersion>>;\n}\n\nexport interface ReadonlyCheckpoint extends Readonly<Checkpoint> {\n readonly channel_values: Readonly<Record<string, unknown>>;\n readonly channel_versions: Readonly<Record<string, ChannelVersion>>;\n readonly versions_seen: Readonly<\n Record<string, Readonly<Record<string, ChannelVersion>>>\n >;\n}\n\nexport function deepCopy<T>(obj: T): T {\n if (typeof obj !== \"object\" || obj === null) {\n return obj;\n }\n\n const newObj = Array.isArray(obj) ? [] : {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n (newObj as Record<PropertyKey, unknown>)[key] = deepCopy(\n (obj as Record<string, unknown>)[key]\n );\n }\n }\n\n return newObj as T;\n}\n\n/** @hidden */\nexport function emptyCheckpoint(): Checkpoint {\n return {\n v: 4,\n id: uuid6(0),\n ts: new Date().toISOString(),\n channel_values: {},\n channel_versions: {},\n versions_seen: {},\n };\n}\n\n/** @hidden */\nexport function copyCheckpoint(checkpoint: ReadonlyCheckpoint): Checkpoint {\n return {\n v: checkpoint.v,\n id: checkpoint.id,\n ts: checkpoint.ts,\n channel_values: { ...checkpoint.channel_values },\n channel_versions: { ...checkpoint.channel_versions },\n versions_seen: deepCopy(checkpoint.versions_seen),\n };\n}\n\nexport interface CheckpointTuple {\n config: RunnableConfig;\n checkpoint: Checkpoint;\n metadata?: CheckpointMetadata;\n parentConfig?: RunnableConfig;\n pendingWrites?: CheckpointPendingWrite[];\n}\n\nexport type CheckpointListOptions = {\n limit?: number;\n before?: RunnableConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n filter?: Record<string, any>;\n};\n\nexport abstract class BaseCheckpointSaver<V extends string | number = number> {\n serde: SerializerProtocol = new JsonPlusSerializer();\n\n constructor(serde?: SerializerProtocol) {\n this.serde = serde || this.serde;\n }\n\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const value = await this.getTuple(config);\n return value ? value.checkpoint : undefined;\n }\n\n abstract getTuple(\n config: RunnableConfig\n ): Promise<CheckpointTuple | undefined>;\n\n abstract list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple>;\n\n abstract put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n newVersions: ChannelVersions\n ): Promise<RunnableConfig>;\n\n /**\n * Store intermediate writes linked to a checkpoint.\n */\n abstract putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void>;\n\n /**\n * Delete all checkpoints and writes associated with a specific thread ID.\n * @param threadId The thread ID whose checkpoints should be deleted.\n */\n abstract deleteThread(threadId: string): Promise<void>;\n\n /**\n * Generate the next version ID for a channel.\n *\n * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,\n * as long as they are monotonically increasing.\n */\n getNextVersion(current: V | undefined): V {\n if (typeof current === \"string\") {\n throw new Error(\"Please override this method to use string versions.\");\n }\n return (\n current !== undefined && typeof current === \"number\" ? current + 1 : 1\n ) as V;\n }\n}\n\nexport function compareChannelVersions(\n a: ChannelVersion,\n b: ChannelVersion\n): number {\n if (typeof a === \"number\" && typeof b === \"number\") {\n return Math.sign(a - b);\n }\n\n return String(a).localeCompare(String(b));\n}\n\nexport function maxChannelVersion(\n ...versions: ChannelVersion[]\n): ChannelVersion {\n return versions.reduce((max, version, idx) => {\n if (idx === 0) return version;\n return compareChannelVersions(max, version) >= 0 ? max : version;\n });\n}\n\n/**\n * Mapping from error type to error index.\n * Regular writes just map to their index in the list of writes being saved.\n * Special writes (e.g. errors) map to negative indices, to avoid those writes from\n * conflicting with regular writes.\n * Each Checkpointer implementation should use this mapping in put_writes.\n */\nexport const WRITES_IDX_MAP: Record<string, number> = {\n [ERROR]: -1,\n [SCHEDULED]: -2,\n [INTERRUPT]: -3,\n [RESUME]: -4,\n};\n\nexport function getCheckpointId(config: RunnableConfig): string {\n return (\n config.configurable?.checkpoint_id || config.configurable?.thread_ts || \"\"\n );\n}\n"],"mappings":";;;;AAsDA,SAAgB,SAAY,KAAW;AACrC,KAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,QAAO;CAGT,MAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,EAAE,GAAG,EAAE;AAE3C,MAAK,MAAM,OAAO,IAChB,KAAI,OAAO,UAAU,eAAe,KAAK,KAAK,IAAI,CAC/C,QAAwC,OAAO,SAC7C,IAAgC,KAClC;AAIL,QAAO;;;AAIT,SAAgB,kBAA8B;AAC5C,QAAO;EACL,GAAG;EACH,IAAI,MAAM,EAAE;EACZ,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB,EAAE;EAClB,kBAAkB,EAAE;EACpB,eAAe,EAAE;EAClB;;;AAIH,SAAgB,eAAe,YAA4C;AACzE,QAAO;EACL,GAAG,WAAW;EACd,IAAI,WAAW;EACf,IAAI,WAAW;EACf,gBAAgB,EAAE,GAAG,WAAW,gBAAgB;EAChD,kBAAkB,EAAE,GAAG,WAAW,kBAAkB;EACpD,eAAe,SAAS,WAAW,cAAc;EAClD;;AAkBH,IAAsB,sBAAtB,MAA8E;CAC5E,QAA4B,IAAI,oBAAoB;CAEpD,YAAY,OAA4B;AACtC,OAAK,QAAQ,SAAS,KAAK;;CAG7B,MAAM,IAAI,QAAyD;EACjE,MAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AACzC,SAAO,QAAQ,MAAM,aAAa,KAAA;;;;;;;;CAwCpC,eAAe,SAA2B;AACxC,MAAI,OAAO,YAAY,SACrB,OAAM,IAAI,MAAM,sDAAsD;AAExE,SACE,YAAY,KAAA,KAAa,OAAO,YAAY,WAAW,UAAU,IAAI;;;AAK3E,SAAgB,uBACd,GACA,GACQ;AACR,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO,KAAK,KAAK,IAAI,EAAE;AAGzB,QAAO,OAAO,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;;AAG3C,SAAgB,kBACd,GAAG,UACa;AAChB,QAAO,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAC5C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,uBAAuB,KAAK,QAAQ,IAAI,IAAI,MAAM;GACzD;;;;;;;;;AAUJ,MAAa,iBAAyC;EACnD,QAAQ;EACR,YAAY;EACZ,YAAY;EACZ,SAAS;CACX;AAED,SAAgB,gBAAgB,QAAgC;AAC9D,QACE,OAAO,cAAc,iBAAiB,OAAO,cAAc,aAAa"}
package/dist/id.cjs CHANGED
@@ -1,7 +1,22 @@
1
1
  let uuid = require("uuid");
2
2
  //#region src/id.ts
3
+ let lastMsecs = 0;
4
+ let lastNsecs = 0;
3
5
  function uuid6(clockseq) {
4
- return (0, uuid.v6)({ clockseq });
6
+ let msecs = Date.now();
7
+ if (msecs <= lastMsecs) {
8
+ lastNsecs += 1;
9
+ if (lastNsecs >= 1e4) {
10
+ lastNsecs = 0;
11
+ msecs = lastMsecs + 1;
12
+ }
13
+ } else lastNsecs = 0;
14
+ lastMsecs = msecs;
15
+ return (0, uuid.v6)({
16
+ clockseq,
17
+ msecs,
18
+ nsecs: lastNsecs
19
+ });
5
20
  }
6
21
  function uuid5(name, namespace) {
7
22
  const namespaceBytes = namespace.replace(/-/g, "").match(/.{2}/g).map((byte) => parseInt(byte, 16));
package/dist/id.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"id.cjs","names":[],"sources":["../src/id.ts"],"sourcesContent":["import { v5, v6 } from \"uuid\";\n\nexport function uuid6(clockseq: number): string {\n return v6({ clockseq });\n}\n\n// Skip UUID validation check, since UUID6s\n// generated with negative clockseq are not\n// technically compliant, but still work.\n// See: https://github.com/uuidjs/uuid/issues/511\nexport function uuid5(name: string, namespace: string): string {\n const namespaceBytes = namespace\n .replace(/-/g, \"\")\n .match(/.{2}/g)!\n .map((byte) => parseInt(byte, 16));\n return v5(name, new Uint8Array(namespaceBytes));\n}\n"],"mappings":";;AAEA,SAAgB,MAAM,UAA0B;AAC9C,SAAA,GAAA,KAAA,IAAU,EAAE,UAAU,CAAC;;AAOzB,SAAgB,MAAM,MAAc,WAA2B;CAC7D,MAAM,iBAAiB,UACpB,QAAQ,MAAM,GAAG,CACjB,MAAM,QAAQ,CACd,KAAK,SAAS,SAAS,MAAM,GAAG,CAAC;AACpC,SAAA,GAAA,KAAA,IAAU,MAAM,IAAI,WAAW,eAAe,CAAC"}
1
+ {"version":3,"file":"id.cjs","names":[],"sources":["../src/id.ts"],"sourcesContent":["import { v5, v6 } from \"uuid\";\n\n// Monotonic timestamp state, mirroring uuid@10's internal v1 clock handling.\n// uuid@11+ dropped the sub-millisecond `nsecs` counter when an explicit\n// `clockseq` is provided, so successive `v6({ clockseq })` calls within the\n// same millisecond produce identical time bits and would only be ordered by\n// `clockseq`. Checkpoint IDs are sorted lexicographically, so we must keep the\n// time component strictly increasing across calls to preserve checkpoint\n// ordering regardless of the `clockseq` value passed in.\nlet lastMsecs = 0;\nlet lastNsecs = 0;\n\nexport function uuid6(clockseq: number): string {\n let msecs = Date.now();\n if (msecs <= lastMsecs) {\n // Clock did not advance; bump the 100ns-resolution counter so the\n // generated time bits remain strictly monotonic.\n lastNsecs += 1;\n if (lastNsecs >= 10000) {\n lastNsecs = 0;\n msecs = lastMsecs + 1;\n }\n } else {\n lastNsecs = 0;\n }\n lastMsecs = msecs;\n return v6({ clockseq, msecs, nsecs: lastNsecs });\n}\n\nexport function uuid5(name: string, namespace: string): string {\n const namespaceBytes = namespace\n .replace(/-/g, \"\")\n .match(/.{2}/g)!\n .map((byte) => parseInt(byte, 16));\n return v5(name, new Uint8Array(namespaceBytes));\n}\n"],"mappings":";;AASA,IAAI,YAAY;AAChB,IAAI,YAAY;AAEhB,SAAgB,MAAM,UAA0B;CAC9C,IAAI,QAAQ,KAAK,KAAK;AACtB,KAAI,SAAS,WAAW;AAGtB,eAAa;AACb,MAAI,aAAa,KAAO;AACtB,eAAY;AACZ,WAAQ,YAAY;;OAGtB,aAAY;AAEd,aAAY;AACZ,SAAA,GAAA,KAAA,IAAU;EAAE;EAAU;EAAO,OAAO;EAAW,CAAC;;AAGlD,SAAgB,MAAM,MAAc,WAA2B;CAC7D,MAAM,iBAAiB,UACpB,QAAQ,MAAM,GAAG,CACjB,MAAM,QAAQ,CACd,KAAK,SAAS,SAAS,MAAM,GAAG,CAAC;AACpC,SAAA,GAAA,KAAA,IAAU,MAAM,IAAI,WAAW,eAAe,CAAC"}
package/dist/id.d.cts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"id.d.cts","names":[],"sources":["../src/id.ts"],"mappings":";iBAEgB,KAAA,CAAM,QAAA;AAAA,iBAQN,KAAA,CAAM,IAAA,UAAc,SAAA"}
1
+ {"version":3,"file":"id.d.cts","names":[],"sources":["../src/id.ts"],"mappings":";iBAYgB,KAAA,CAAM,QAAA;AAAA,iBAiBN,KAAA,CAAM,IAAA,UAAc,SAAA"}
package/dist/id.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"id.d.ts","names":[],"sources":["../src/id.ts"],"mappings":";iBAEgB,KAAA,CAAM,QAAA;AAAA,iBAQN,KAAA,CAAM,IAAA,UAAc,SAAA"}
1
+ {"version":3,"file":"id.d.ts","names":[],"sources":["../src/id.ts"],"mappings":";iBAYgB,KAAA,CAAM,QAAA;AAAA,iBAiBN,KAAA,CAAM,IAAA,UAAc,SAAA"}
package/dist/id.js CHANGED
@@ -1,7 +1,22 @@
1
1
  import { v5, v6 } from "uuid";
2
2
  //#region src/id.ts
3
+ let lastMsecs = 0;
4
+ let lastNsecs = 0;
3
5
  function uuid6(clockseq) {
4
- return v6({ clockseq });
6
+ let msecs = Date.now();
7
+ if (msecs <= lastMsecs) {
8
+ lastNsecs += 1;
9
+ if (lastNsecs >= 1e4) {
10
+ lastNsecs = 0;
11
+ msecs = lastMsecs + 1;
12
+ }
13
+ } else lastNsecs = 0;
14
+ lastMsecs = msecs;
15
+ return v6({
16
+ clockseq,
17
+ msecs,
18
+ nsecs: lastNsecs
19
+ });
5
20
  }
6
21
  function uuid5(name, namespace) {
7
22
  const namespaceBytes = namespace.replace(/-/g, "").match(/.{2}/g).map((byte) => parseInt(byte, 16));
package/dist/id.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"id.js","names":[],"sources":["../src/id.ts"],"sourcesContent":["import { v5, v6 } from \"uuid\";\n\nexport function uuid6(clockseq: number): string {\n return v6({ clockseq });\n}\n\n// Skip UUID validation check, since UUID6s\n// generated with negative clockseq are not\n// technically compliant, but still work.\n// See: https://github.com/uuidjs/uuid/issues/511\nexport function uuid5(name: string, namespace: string): string {\n const namespaceBytes = namespace\n .replace(/-/g, \"\")\n .match(/.{2}/g)!\n .map((byte) => parseInt(byte, 16));\n return v5(name, new Uint8Array(namespaceBytes));\n}\n"],"mappings":";;AAEA,SAAgB,MAAM,UAA0B;AAC9C,QAAO,GAAG,EAAE,UAAU,CAAC;;AAOzB,SAAgB,MAAM,MAAc,WAA2B;CAC7D,MAAM,iBAAiB,UACpB,QAAQ,MAAM,GAAG,CACjB,MAAM,QAAQ,CACd,KAAK,SAAS,SAAS,MAAM,GAAG,CAAC;AACpC,QAAO,GAAG,MAAM,IAAI,WAAW,eAAe,CAAC"}
1
+ {"version":3,"file":"id.js","names":[],"sources":["../src/id.ts"],"sourcesContent":["import { v5, v6 } from \"uuid\";\n\n// Monotonic timestamp state, mirroring uuid@10's internal v1 clock handling.\n// uuid@11+ dropped the sub-millisecond `nsecs` counter when an explicit\n// `clockseq` is provided, so successive `v6({ clockseq })` calls within the\n// same millisecond produce identical time bits and would only be ordered by\n// `clockseq`. Checkpoint IDs are sorted lexicographically, so we must keep the\n// time component strictly increasing across calls to preserve checkpoint\n// ordering regardless of the `clockseq` value passed in.\nlet lastMsecs = 0;\nlet lastNsecs = 0;\n\nexport function uuid6(clockseq: number): string {\n let msecs = Date.now();\n if (msecs <= lastMsecs) {\n // Clock did not advance; bump the 100ns-resolution counter so the\n // generated time bits remain strictly monotonic.\n lastNsecs += 1;\n if (lastNsecs >= 10000) {\n lastNsecs = 0;\n msecs = lastMsecs + 1;\n }\n } else {\n lastNsecs = 0;\n }\n lastMsecs = msecs;\n return v6({ clockseq, msecs, nsecs: lastNsecs });\n}\n\nexport function uuid5(name: string, namespace: string): string {\n const namespaceBytes = namespace\n .replace(/-/g, \"\")\n .match(/.{2}/g)!\n .map((byte) => parseInt(byte, 16));\n return v5(name, new Uint8Array(namespaceBytes));\n}\n"],"mappings":";;AASA,IAAI,YAAY;AAChB,IAAI,YAAY;AAEhB,SAAgB,MAAM,UAA0B;CAC9C,IAAI,QAAQ,KAAK,KAAK;AACtB,KAAI,SAAS,WAAW;AAGtB,eAAa;AACb,MAAI,aAAa,KAAO;AACtB,eAAY;AACZ,WAAQ,YAAY;;OAGtB,aAAY;AAEd,aAAY;AACZ,QAAO,GAAG;EAAE;EAAU;EAAO,OAAO;EAAW,CAAC;;AAGlD,SAAgB,MAAM,MAAc,WAA2B;CAC7D,MAAM,iBAAiB,UACpB,QAAQ,MAAM,GAAG,CACjB,MAAM,QAAQ,CACd,KAAK,SAAS,SAAS,MAAM,GAAG,CAAC;AACpC,QAAO,GAAG,MAAM,IAAI,WAAW,eAAe,CAAC"}
package/dist/memory.cjs CHANGED
@@ -1,6 +1,57 @@
1
1
  const require_types = require("./serde/types.cjs");
2
2
  const require_base = require("./base.cjs");
3
3
  //#region src/memory.ts
4
+ /**
5
+ * Keys that, when written into a plain JavaScript object via bracket
6
+ * notation, traverse the prototype chain and mutate `Object.prototype`
7
+ * (or the constructor) instead of creating a new own property. Any of
8
+ * the three reaches `Object.prototype` and pollutes every object in
9
+ * the running process. CWE-1321 (Prototype Pollution).
10
+ */
11
+ const POLLUTION_KEYS = new Set([
12
+ "__proto__",
13
+ "constructor",
14
+ "prototype"
15
+ ]);
16
+ /**
17
+ * Asserts that a value sourced from {@link RunnableConfig.configurable} (or
18
+ * any other caller-influenced position) is safe to use as a property key
19
+ * on the in-memory checkpoint store.
20
+ *
21
+ * `MemorySaver` keeps state in two nested plain objects (`storage` and
22
+ * `writes`) and writes to them with bracket notation:
23
+ *
24
+ * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...
25
+ *
26
+ * Without this guard a `threadId` of `"__proto__"` (or `"constructor"`)
27
+ * resolves through the prototype chain, and the subsequent assignment
28
+ * mutates `Object.prototype`. From that point every plain object in the
29
+ * process inherits the injected property: `for...in` loops over unrelated
30
+ * objects iterate it, framework code that does `if (obj[x])` short-circuits
31
+ * unexpectedly, and downstream serializers may emit it. In a Node.js
32
+ * server this is a stepping stone to remote code execution.
33
+ *
34
+ * `MemorySaver` is the default saver used by every quickstart, every
35
+ * tutorial, and most test fixtures, so this guard runs in the hot path
36
+ * for the most common LangGraph configuration.
37
+ *
38
+ * @param field Name of the configurable field, used in the error message.
39
+ * @param value Value to validate. Must be a non-empty string that is not
40
+ * one of the three prototype-pollution keys.
41
+ * @param options.allowEmpty When true the empty string is accepted, used
42
+ * for the documented empty `checkpoint_ns`
43
+ * default; otherwise an empty string is
44
+ * rejected the same way as a non-string.
45
+ */
46
+ function assertSafeStorageKey(field, value, options = {}) {
47
+ const { allowEmpty = false } = options;
48
+ if (typeof value !== "string") {
49
+ const observed = value === null ? "null" : value === void 0 ? "undefined" : Array.isArray(value) ? "array" : typeof value;
50
+ throw new Error(`Invalid configurable value for key "${field}": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`);
51
+ }
52
+ if (!allowEmpty && value === "") throw new Error(`Invalid configurable value for key "${field}": empty string is not permitted as an in-memory storage key.`);
53
+ if (POLLUTION_KEYS.has(value)) throw new Error(`Invalid configurable value for key "${field}": value "${value}" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`);
54
+ }
4
55
  function _generateKey(threadId, checkpointNamespace, checkpointId) {
5
56
  return JSON.stringify([
6
57
  threadId,
@@ -17,8 +68,8 @@ function _parseKey(key) {
17
68
  };
18
69
  }
19
70
  var MemorySaver = class extends require_base.BaseCheckpointSaver {
20
- storage = {};
21
- writes = {};
71
+ storage = Object.create(null);
72
+ writes = Object.create(null);
22
73
  constructor(serde) {
23
74
  super(serde);
24
75
  }
@@ -36,6 +87,9 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
36
87
  const thread_id = config.configurable?.thread_id;
37
88
  const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
38
89
  let checkpoint_id = require_base.getCheckpointId(config);
90
+ if (thread_id !== void 0) assertSafeStorageKey("thread_id", thread_id);
91
+ assertSafeStorageKey("checkpoint_ns", checkpoint_ns, { allowEmpty: true });
92
+ if (checkpoint_id) assertSafeStorageKey("checkpoint_id", checkpoint_id);
39
93
  if (checkpoint_id) {
40
94
  const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
41
95
  if (saved !== void 0) {
@@ -99,6 +153,10 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
99
153
  }
100
154
  async *list(config, options) {
101
155
  let { before, limit, filter } = options ?? {};
156
+ if (config.configurable?.thread_id !== void 0) assertSafeStorageKey("thread_id", config.configurable.thread_id);
157
+ if (config.configurable?.checkpoint_ns !== void 0) assertSafeStorageKey("checkpoint_ns", config.configurable.checkpoint_ns, { allowEmpty: true });
158
+ if (config.configurable?.checkpoint_id) assertSafeStorageKey("checkpoint_id", config.configurable.checkpoint_id);
159
+ if (before?.configurable?.checkpoint_id) assertSafeStorageKey("checkpoint_id", before.configurable.checkpoint_id);
102
160
  const threadIds = config.configurable?.thread_id ? [config.configurable?.thread_id] : Object.keys(this.storage);
103
161
  const configCheckpointNamespace = config.configurable?.checkpoint_ns;
104
162
  const configCheckpointId = config.configurable?.checkpoint_id;
@@ -150,8 +208,11 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
150
208
  const threadId = config.configurable?.thread_id;
151
209
  const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
152
210
  if (threadId === void 0) throw new Error("Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })");
153
- if (!this.storage[threadId]) this.storage[threadId] = {};
154
- if (!this.storage[threadId][checkpointNamespace]) this.storage[threadId][checkpointNamespace] = {};
211
+ assertSafeStorageKey("thread_id", threadId);
212
+ assertSafeStorageKey("checkpoint_ns", checkpointNamespace, { allowEmpty: true });
213
+ assertSafeStorageKey("checkpoint_id", checkpoint.id);
214
+ if (!this.storage[threadId]) this.storage[threadId] = Object.create(null);
215
+ if (!this.storage[threadId][checkpointNamespace]) this.storage[threadId][checkpointNamespace] = Object.create(null);
155
216
  const [[, serializedCheckpoint], [, serializedMetadata]] = await Promise.all([this.serde.dumpsTyped(preparedCheckpoint), this.serde.dumpsTyped(metadata)]);
156
217
  this.storage[threadId][checkpointNamespace][checkpoint.id] = [
157
218
  serializedCheckpoint,
@@ -170,9 +231,13 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
170
231
  const checkpointId = config.configurable?.checkpoint_id;
171
232
  if (threadId === void 0) throw new Error("Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })");
172
233
  if (checkpointId === void 0) throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
234
+ assertSafeStorageKey("thread_id", threadId);
235
+ assertSafeStorageKey("checkpoint_ns", checkpointNamespace, { allowEmpty: true });
236
+ assertSafeStorageKey("checkpoint_id", checkpointId);
237
+ assertSafeStorageKey("task_id", taskId);
173
238
  const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);
174
239
  const outerWrites_ = this.writes[outerKey];
175
- if (this.writes[outerKey] === void 0) this.writes[outerKey] = {};
240
+ if (this.writes[outerKey] === void 0) this.writes[outerKey] = Object.create(null);
176
241
  await Promise.all(writes.map(async ([channel, value], idx) => {
177
242
  const [, serializedValue] = await this.serde.dumpsTyped(value);
178
243
  const innerKey = [taskId, require_base.WRITES_IDX_MAP[channel] || idx];
@@ -186,6 +251,7 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
186
251
  }));
187
252
  }
188
253
  async deleteThread(threadId) {
254
+ assertSafeStorageKey("thread_id", threadId);
189
255
  delete this.storage[threadId];
190
256
  for (const key of Object.keys(this.writes)) if (_parseKey(key).threadId === threadId) delete this.writes[key];
191
257
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory.cjs","names":["BaseCheckpointSaver","TASKS","maxChannelVersion","getCheckpointId","copyCheckpoint","WRITES_IDX_MAP"],"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 PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\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 storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = {};\n\n writes: Record<string, Record<string, [string, string, Uint8Array]>> = {};\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 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 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 if (!this.storage[threadId]) {\n this.storage[threadId] = {};\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = {};\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 const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = {};\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 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"],"mappings":"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"}
1
+ {"version":3,"file":"memory.cjs","names":["BaseCheckpointSaver","TASKS","maxChannelVersion","getCheckpointId","copyCheckpoint","WRITES_IDX_MAP"],"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 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"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.cts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAgCa,WAAA,SAAoB,mBAAA;EAE/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAEvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAyHzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAyHZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EAwCL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EAsCG,YAAA,CAAa,QAAA,WAAmB,OAAA;AAAA"}
1
+ {"version":3,"file":"memory.d.cts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAsGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAGvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAuIzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAuIZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA4CG,YAAA,CAAa,QAAA,WAAmB,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAgCa,WAAA,SAAoB,mBAAA;EAE/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAEvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAyHzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAyHZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EAwCL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EAsCG,YAAA,CAAa,QAAA,WAAmB,OAAA;AAAA"}
1
+ {"version":3,"file":"memory.d.ts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAsGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAGvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAuIzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAuIZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA4CG,YAAA,CAAa,QAAA,WAAmB,OAAA;AAAA"}
package/dist/memory.js CHANGED
@@ -1,6 +1,57 @@
1
1
  import { TASKS } from "./serde/types.js";
2
2
  import { BaseCheckpointSaver, WRITES_IDX_MAP, copyCheckpoint, getCheckpointId, maxChannelVersion } from "./base.js";
3
3
  //#region src/memory.ts
4
+ /**
5
+ * Keys that, when written into a plain JavaScript object via bracket
6
+ * notation, traverse the prototype chain and mutate `Object.prototype`
7
+ * (or the constructor) instead of creating a new own property. Any of
8
+ * the three reaches `Object.prototype` and pollutes every object in
9
+ * the running process. CWE-1321 (Prototype Pollution).
10
+ */
11
+ const POLLUTION_KEYS = new Set([
12
+ "__proto__",
13
+ "constructor",
14
+ "prototype"
15
+ ]);
16
+ /**
17
+ * Asserts that a value sourced from {@link RunnableConfig.configurable} (or
18
+ * any other caller-influenced position) is safe to use as a property key
19
+ * on the in-memory checkpoint store.
20
+ *
21
+ * `MemorySaver` keeps state in two nested plain objects (`storage` and
22
+ * `writes`) and writes to them with bracket notation:
23
+ *
24
+ * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...
25
+ *
26
+ * Without this guard a `threadId` of `"__proto__"` (or `"constructor"`)
27
+ * resolves through the prototype chain, and the subsequent assignment
28
+ * mutates `Object.prototype`. From that point every plain object in the
29
+ * process inherits the injected property: `for...in` loops over unrelated
30
+ * objects iterate it, framework code that does `if (obj[x])` short-circuits
31
+ * unexpectedly, and downstream serializers may emit it. In a Node.js
32
+ * server this is a stepping stone to remote code execution.
33
+ *
34
+ * `MemorySaver` is the default saver used by every quickstart, every
35
+ * tutorial, and most test fixtures, so this guard runs in the hot path
36
+ * for the most common LangGraph configuration.
37
+ *
38
+ * @param field Name of the configurable field, used in the error message.
39
+ * @param value Value to validate. Must be a non-empty string that is not
40
+ * one of the three prototype-pollution keys.
41
+ * @param options.allowEmpty When true the empty string is accepted, used
42
+ * for the documented empty `checkpoint_ns`
43
+ * default; otherwise an empty string is
44
+ * rejected the same way as a non-string.
45
+ */
46
+ function assertSafeStorageKey(field, value, options = {}) {
47
+ const { allowEmpty = false } = options;
48
+ if (typeof value !== "string") {
49
+ const observed = value === null ? "null" : value === void 0 ? "undefined" : Array.isArray(value) ? "array" : typeof value;
50
+ throw new Error(`Invalid configurable value for key "${field}": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`);
51
+ }
52
+ if (!allowEmpty && value === "") throw new Error(`Invalid configurable value for key "${field}": empty string is not permitted as an in-memory storage key.`);
53
+ if (POLLUTION_KEYS.has(value)) throw new Error(`Invalid configurable value for key "${field}": value "${value}" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`);
54
+ }
4
55
  function _generateKey(threadId, checkpointNamespace, checkpointId) {
5
56
  return JSON.stringify([
6
57
  threadId,
@@ -17,8 +68,8 @@ function _parseKey(key) {
17
68
  };
18
69
  }
19
70
  var MemorySaver = class extends BaseCheckpointSaver {
20
- storage = {};
21
- writes = {};
71
+ storage = Object.create(null);
72
+ writes = Object.create(null);
22
73
  constructor(serde) {
23
74
  super(serde);
24
75
  }
@@ -36,6 +87,9 @@ var MemorySaver = class extends BaseCheckpointSaver {
36
87
  const thread_id = config.configurable?.thread_id;
37
88
  const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
38
89
  let checkpoint_id = getCheckpointId(config);
90
+ if (thread_id !== void 0) assertSafeStorageKey("thread_id", thread_id);
91
+ assertSafeStorageKey("checkpoint_ns", checkpoint_ns, { allowEmpty: true });
92
+ if (checkpoint_id) assertSafeStorageKey("checkpoint_id", checkpoint_id);
39
93
  if (checkpoint_id) {
40
94
  const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
41
95
  if (saved !== void 0) {
@@ -99,6 +153,10 @@ var MemorySaver = class extends BaseCheckpointSaver {
99
153
  }
100
154
  async *list(config, options) {
101
155
  let { before, limit, filter } = options ?? {};
156
+ if (config.configurable?.thread_id !== void 0) assertSafeStorageKey("thread_id", config.configurable.thread_id);
157
+ if (config.configurable?.checkpoint_ns !== void 0) assertSafeStorageKey("checkpoint_ns", config.configurable.checkpoint_ns, { allowEmpty: true });
158
+ if (config.configurable?.checkpoint_id) assertSafeStorageKey("checkpoint_id", config.configurable.checkpoint_id);
159
+ if (before?.configurable?.checkpoint_id) assertSafeStorageKey("checkpoint_id", before.configurable.checkpoint_id);
102
160
  const threadIds = config.configurable?.thread_id ? [config.configurable?.thread_id] : Object.keys(this.storage);
103
161
  const configCheckpointNamespace = config.configurable?.checkpoint_ns;
104
162
  const configCheckpointId = config.configurable?.checkpoint_id;
@@ -150,8 +208,11 @@ var MemorySaver = class extends BaseCheckpointSaver {
150
208
  const threadId = config.configurable?.thread_id;
151
209
  const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
152
210
  if (threadId === void 0) throw new Error("Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })");
153
- if (!this.storage[threadId]) this.storage[threadId] = {};
154
- if (!this.storage[threadId][checkpointNamespace]) this.storage[threadId][checkpointNamespace] = {};
211
+ assertSafeStorageKey("thread_id", threadId);
212
+ assertSafeStorageKey("checkpoint_ns", checkpointNamespace, { allowEmpty: true });
213
+ assertSafeStorageKey("checkpoint_id", checkpoint.id);
214
+ if (!this.storage[threadId]) this.storage[threadId] = Object.create(null);
215
+ if (!this.storage[threadId][checkpointNamespace]) this.storage[threadId][checkpointNamespace] = Object.create(null);
155
216
  const [[, serializedCheckpoint], [, serializedMetadata]] = await Promise.all([this.serde.dumpsTyped(preparedCheckpoint), this.serde.dumpsTyped(metadata)]);
156
217
  this.storage[threadId][checkpointNamespace][checkpoint.id] = [
157
218
  serializedCheckpoint,
@@ -170,9 +231,13 @@ var MemorySaver = class extends BaseCheckpointSaver {
170
231
  const checkpointId = config.configurable?.checkpoint_id;
171
232
  if (threadId === void 0) throw new Error("Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })");
172
233
  if (checkpointId === void 0) throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
234
+ assertSafeStorageKey("thread_id", threadId);
235
+ assertSafeStorageKey("checkpoint_ns", checkpointNamespace, { allowEmpty: true });
236
+ assertSafeStorageKey("checkpoint_id", checkpointId);
237
+ assertSafeStorageKey("task_id", taskId);
173
238
  const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);
174
239
  const outerWrites_ = this.writes[outerKey];
175
- if (this.writes[outerKey] === void 0) this.writes[outerKey] = {};
240
+ if (this.writes[outerKey] === void 0) this.writes[outerKey] = Object.create(null);
176
241
  await Promise.all(writes.map(async ([channel, value], idx) => {
177
242
  const [, serializedValue] = await this.serde.dumpsTyped(value);
178
243
  const innerKey = [taskId, WRITES_IDX_MAP[channel] || idx];
@@ -186,6 +251,7 @@ var MemorySaver = class extends BaseCheckpointSaver {
186
251
  }));
187
252
  }
188
253
  async deleteThread(threadId) {
254
+ assertSafeStorageKey("thread_id", threadId);
189
255
  delete this.storage[threadId];
190
256
  for (const key of Object.keys(this.writes)) if (_parseKey(key).threadId === threadId) delete this.writes[key];
191
257
  }
@@ -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 PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\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 storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = {};\n\n writes: Record<string, Record<string, [string, string, Uint8Array]>> = {};\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 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 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 if (!this.storage[threadId]) {\n this.storage[threadId] = {};\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = {};\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 const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = {};\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 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"],"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 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 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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-checkpoint",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Library with base interfaces for LangGraph checkpoint savers.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -16,7 +16,7 @@
16
16
  "author": "LangChain",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "uuid": "^10.0.0"
19
+ "uuid": "^14.0.0"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "@langchain/core": "^1.1.44"