@langchain/langgraph-checkpoint 0.0.6 → 0.0.8-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -22,3 +22,4 @@ __exportStar(require("./id.cjs"), exports);
22
22
  __exportStar(require("./types.cjs"), exports);
23
23
  __exportStar(require("./serde/base.cjs"), exports);
24
24
  __exportStar(require("./serde/types.cjs"), exports);
25
+ __exportStar(require("./store/index.cjs"), exports);
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./id.js";
4
4
  export * from "./types.js";
5
5
  export * from "./serde/base.js";
6
6
  export * from "./serde/types.js";
7
+ export * from "./store/index.js";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export * from "./id.js";
4
4
  export * from "./types.js";
5
5
  export * from "./serde/base.js";
6
6
  export * from "./serde/types.js";
7
+ export * from "./store/index.js";
package/dist/memory.cjs CHANGED
@@ -129,61 +129,67 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
129
129
  const threadIds = config.configurable?.thread_id
130
130
  ? [config.configurable?.thread_id]
131
131
  : Object.keys(this.storage);
132
- const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
132
+ const configCheckpointNamespace = config.configurable?.checkpoint_ns;
133
133
  for (const threadId of threadIds) {
134
- const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
135
- const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
136
- for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
137
- // Filter by checkpoint ID
138
- if (before &&
139
- before.configurable?.checkpoint_id &&
140
- checkpointId >= before.configurable.checkpoint_id) {
134
+ for (const checkpointNamespace of Object.keys(this.storage[threadId])) {
135
+ if (configCheckpointNamespace !== undefined &&
136
+ checkpointNamespace !== configCheckpointNamespace) {
141
137
  continue;
142
138
  }
143
- // Parse metadata
144
- const metadata = (await this.serde.loadsTyped("json", metadataStr));
145
- // Limit search results
146
- if (limit !== undefined) {
147
- if (limit <= 0)
148
- break;
149
- // eslint-disable-next-line no-param-reassign
150
- limit -= 1;
151
- }
152
- const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
153
- const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
154
- const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
155
- return [
156
- taskId,
157
- channel,
158
- await this.serde.loadsTyped("json", value),
159
- ];
160
- }));
161
- const deserializedCheckpoint = {
162
- ...(await this.serde.loadsTyped("json", checkpoint)),
163
- pending_sends,
164
- };
165
- const checkpointTuple = {
166
- config: {
167
- configurable: {
168
- thread_id: threadId,
169
- checkpoint_ns: checkpointNamespace,
170
- checkpoint_id: checkpointId,
171
- },
172
- },
173
- checkpoint: deserializedCheckpoint,
174
- metadata,
175
- pendingWrites,
176
- };
177
- if (parentCheckpointId !== undefined) {
178
- checkpointTuple.parentConfig = {
179
- configurable: {
180
- thread_id: threadId,
181
- checkpoint_ns: checkpointNamespace,
182
- checkpoint_id: parentCheckpointId,
139
+ const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
140
+ const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
141
+ for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
142
+ // Filter by checkpoint ID
143
+ if (before &&
144
+ before.configurable?.checkpoint_id &&
145
+ checkpointId >= before.configurable.checkpoint_id) {
146
+ continue;
147
+ }
148
+ // Parse metadata
149
+ const metadata = (await this.serde.loadsTyped("json", metadataStr));
150
+ // Limit search results
151
+ if (limit !== undefined) {
152
+ if (limit <= 0)
153
+ break;
154
+ // eslint-disable-next-line no-param-reassign
155
+ limit -= 1;
156
+ }
157
+ const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
158
+ const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
159
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
160
+ return [
161
+ taskId,
162
+ channel,
163
+ await this.serde.loadsTyped("json", value),
164
+ ];
165
+ }));
166
+ const deserializedCheckpoint = {
167
+ ...(await this.serde.loadsTyped("json", checkpoint)),
168
+ pending_sends,
169
+ };
170
+ const checkpointTuple = {
171
+ config: {
172
+ configurable: {
173
+ thread_id: threadId,
174
+ checkpoint_ns: checkpointNamespace,
175
+ checkpoint_id: checkpointId,
176
+ },
183
177
  },
178
+ checkpoint: deserializedCheckpoint,
179
+ metadata,
180
+ pendingWrites,
184
181
  };
182
+ if (parentCheckpointId !== undefined) {
183
+ checkpointTuple.parentConfig = {
184
+ configurable: {
185
+ thread_id: threadId,
186
+ checkpoint_ns: checkpointNamespace,
187
+ checkpoint_id: parentCheckpointId,
188
+ },
189
+ };
190
+ }
191
+ yield checkpointTuple;
185
192
  }
186
- yield checkpointTuple;
187
193
  }
188
194
  }
189
195
  }
package/dist/memory.js CHANGED
@@ -126,61 +126,67 @@ export class MemorySaver extends BaseCheckpointSaver {
126
126
  const threadIds = config.configurable?.thread_id
127
127
  ? [config.configurable?.thread_id]
128
128
  : Object.keys(this.storage);
129
- const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
129
+ const configCheckpointNamespace = config.configurable?.checkpoint_ns;
130
130
  for (const threadId of threadIds) {
131
- const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
132
- const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
133
- for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
134
- // Filter by checkpoint ID
135
- if (before &&
136
- before.configurable?.checkpoint_id &&
137
- checkpointId >= before.configurable.checkpoint_id) {
131
+ for (const checkpointNamespace of Object.keys(this.storage[threadId])) {
132
+ if (configCheckpointNamespace !== undefined &&
133
+ checkpointNamespace !== configCheckpointNamespace) {
138
134
  continue;
139
135
  }
140
- // Parse metadata
141
- const metadata = (await this.serde.loadsTyped("json", metadataStr));
142
- // Limit search results
143
- if (limit !== undefined) {
144
- if (limit <= 0)
145
- break;
146
- // eslint-disable-next-line no-param-reassign
147
- limit -= 1;
148
- }
149
- const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
150
- const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
151
- const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
152
- return [
153
- taskId,
154
- channel,
155
- await this.serde.loadsTyped("json", value),
156
- ];
157
- }));
158
- const deserializedCheckpoint = {
159
- ...(await this.serde.loadsTyped("json", checkpoint)),
160
- pending_sends,
161
- };
162
- const checkpointTuple = {
163
- config: {
164
- configurable: {
165
- thread_id: threadId,
166
- checkpoint_ns: checkpointNamespace,
167
- checkpoint_id: checkpointId,
168
- },
169
- },
170
- checkpoint: deserializedCheckpoint,
171
- metadata,
172
- pendingWrites,
173
- };
174
- if (parentCheckpointId !== undefined) {
175
- checkpointTuple.parentConfig = {
176
- configurable: {
177
- thread_id: threadId,
178
- checkpoint_ns: checkpointNamespace,
179
- checkpoint_id: parentCheckpointId,
136
+ const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
137
+ const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
138
+ for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
139
+ // Filter by checkpoint ID
140
+ if (before &&
141
+ before.configurable?.checkpoint_id &&
142
+ checkpointId >= before.configurable.checkpoint_id) {
143
+ continue;
144
+ }
145
+ // Parse metadata
146
+ const metadata = (await this.serde.loadsTyped("json", metadataStr));
147
+ // Limit search results
148
+ if (limit !== undefined) {
149
+ if (limit <= 0)
150
+ break;
151
+ // eslint-disable-next-line no-param-reassign
152
+ limit -= 1;
153
+ }
154
+ const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
155
+ const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
156
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
157
+ return [
158
+ taskId,
159
+ channel,
160
+ await this.serde.loadsTyped("json", value),
161
+ ];
162
+ }));
163
+ const deserializedCheckpoint = {
164
+ ...(await this.serde.loadsTyped("json", checkpoint)),
165
+ pending_sends,
166
+ };
167
+ const checkpointTuple = {
168
+ config: {
169
+ configurable: {
170
+ thread_id: threadId,
171
+ checkpoint_ns: checkpointNamespace,
172
+ checkpoint_id: checkpointId,
173
+ },
180
174
  },
175
+ checkpoint: deserializedCheckpoint,
176
+ metadata,
177
+ pendingWrites,
181
178
  };
179
+ if (parentCheckpointId !== undefined) {
180
+ checkpointTuple.parentConfig = {
181
+ configurable: {
182
+ thread_id: threadId,
183
+ checkpoint_ns: checkpointNamespace,
184
+ checkpoint_id: parentCheckpointId,
185
+ },
186
+ };
187
+ }
188
+ yield checkpointTuple;
182
189
  }
183
- yield checkpointTuple;
184
190
  }
185
191
  }
186
192
  }
@@ -4,101 +4,71 @@ exports.JsonPlusSerializer = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
5
5
  /* eslint-disable no-instanceof/no-instanceof */
6
6
  const load_1 = require("@langchain/core/load");
7
- function isLangChainSerializable(value) {
8
- return (typeof value.lc_serializable === "boolean" && Array.isArray(value.lc_id));
9
- }
7
+ const index_js_1 = require("./utils/fast-safe-stringify/index.cjs");
10
8
  function isLangChainSerializedObject(value) {
11
9
  return (value !== null &&
12
10
  value.lc === 1 &&
13
11
  value.type === "constructor" &&
14
12
  Array.isArray(value.id));
15
13
  }
16
- const _serialize = (value, seen = new WeakSet()) => {
17
- const defaultValue = _default("", value);
18
- if (defaultValue === null) {
19
- return "null";
20
- }
21
- else if (typeof defaultValue === "string") {
22
- return JSON.stringify(defaultValue);
23
- }
24
- else if (typeof defaultValue === "number" ||
25
- typeof defaultValue === "boolean") {
26
- return defaultValue.toString();
27
- }
28
- else if (typeof defaultValue === "object") {
29
- if (seen.has(defaultValue)) {
30
- throw new TypeError("Circular reference detected");
31
- }
32
- seen.add(defaultValue);
33
- if (Array.isArray(defaultValue)) {
34
- const result = `[${defaultValue
35
- .map((item) => _serialize(item, seen))
36
- .join(",")}]`;
37
- seen.delete(defaultValue);
38
- return result;
39
- }
40
- else if (isLangChainSerializable(defaultValue)) {
41
- return JSON.stringify(defaultValue);
42
- }
43
- else {
44
- const entries = Object.entries(defaultValue).map(([k, v]) => `${JSON.stringify(k)}:${_serialize(v, seen)}`);
45
- const result = `{${entries.join(",")}}`;
46
- seen.delete(defaultValue);
47
- return result;
48
- }
49
- }
50
- // Only be reached for functions or symbols
51
- return JSON.stringify(defaultValue);
52
- };
14
+ /**
15
+ * The replacer in stringify does not allow delegation to built-in LangChain
16
+ * serialization methods, and instead immediately calls `.toJSON()` and
17
+ * continues to stringify subfields.
18
+ *
19
+ * We therefore must start from the most nested elements in the input and
20
+ * deserialize upwards rather than top-down.
21
+ */
53
22
  async function _reviver(value) {
54
23
  if (value && typeof value === "object") {
55
- if (value.lc === 2 && value.type === "undefined") {
56
- return undefined;
57
- }
58
- else if (value.lc === 2 &&
59
- value.type === "constructor" &&
60
- Array.isArray(value.id)) {
61
- try {
62
- const constructorName = value.id[value.id.length - 1];
63
- let constructor;
64
- switch (constructorName) {
65
- case "Set":
66
- constructor = Set;
67
- break;
68
- case "Map":
69
- constructor = Map;
70
- break;
71
- case "RegExp":
72
- constructor = RegExp;
73
- break;
74
- case "Error":
75
- constructor = Error;
76
- break;
77
- default:
78
- return value;
79
- }
80
- if (value.method) {
81
- return constructor[value.method](...(value.args || []));
82
- }
83
- else {
84
- return new constructor(...(value.args || []));
85
- }
86
- }
87
- catch (error) {
88
- return value;
89
- }
90
- }
91
- else if (isLangChainSerializedObject(value)) {
92
- return (0, load_1.load)(JSON.stringify(value));
93
- }
94
- else if (Array.isArray(value)) {
95
- return Promise.all(value.map((item) => _reviver(item)));
24
+ if (Array.isArray(value)) {
25
+ const revivedArray = await Promise.all(value.map((item) => _reviver(item)));
26
+ return revivedArray;
96
27
  }
97
28
  else {
98
29
  const revivedObj = {};
99
30
  for (const [k, v] of Object.entries(value)) {
100
31
  revivedObj[k] = await _reviver(v);
101
32
  }
33
+ if (revivedObj.lc === 2 && revivedObj.type === "undefined") {
34
+ return undefined;
35
+ }
36
+ else if (revivedObj.lc === 2 &&
37
+ revivedObj.type === "constructor" &&
38
+ Array.isArray(revivedObj.id)) {
39
+ try {
40
+ const constructorName = revivedObj.id[revivedObj.id.length - 1];
41
+ let constructor;
42
+ switch (constructorName) {
43
+ case "Set":
44
+ constructor = Set;
45
+ break;
46
+ case "Map":
47
+ constructor = Map;
48
+ break;
49
+ case "RegExp":
50
+ constructor = RegExp;
51
+ break;
52
+ case "Error":
53
+ constructor = Error;
54
+ break;
55
+ default:
56
+ return revivedObj;
57
+ }
58
+ if (revivedObj.method) {
59
+ return constructor[revivedObj.method](...(revivedObj.args || []));
60
+ }
61
+ else {
62
+ return new constructor(...(revivedObj.args || []));
63
+ }
64
+ }
65
+ catch (error) {
66
+ return revivedObj;
67
+ }
68
+ }
69
+ else if (isLangChainSerializedObject(revivedObj)) {
70
+ return (0, load_1.load)(JSON.stringify(revivedObj));
71
+ }
102
72
  return revivedObj;
103
73
  }
104
74
  }
@@ -116,7 +86,7 @@ constructor, method, args, kwargs) {
116
86
  kwargs: kwargs ?? {},
117
87
  };
118
88
  }
119
- function _default(_key, obj) {
89
+ function _default(obj) {
120
90
  if (obj === undefined) {
121
91
  return {
122
92
  lc: 2,
@@ -148,7 +118,9 @@ function _default(_key, obj) {
148
118
  class JsonPlusSerializer {
149
119
  _dumps(obj) {
150
120
  const encoder = new TextEncoder();
151
- return encoder.encode(_serialize(obj));
121
+ return encoder.encode((0, index_js_1.stringify)(obj, (_, value) => {
122
+ return _default(value);
123
+ }));
152
124
  }
153
125
  dumpsTyped(obj) {
154
126
  if (obj instanceof Uint8Array) {
@@ -1,101 +1,71 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable no-instanceof/no-instanceof */
3
3
  import { load } from "@langchain/core/load";
4
- function isLangChainSerializable(value) {
5
- return (typeof value.lc_serializable === "boolean" && Array.isArray(value.lc_id));
6
- }
4
+ import { stringify } from "./utils/fast-safe-stringify/index.js";
7
5
  function isLangChainSerializedObject(value) {
8
6
  return (value !== null &&
9
7
  value.lc === 1 &&
10
8
  value.type === "constructor" &&
11
9
  Array.isArray(value.id));
12
10
  }
13
- const _serialize = (value, seen = new WeakSet()) => {
14
- const defaultValue = _default("", value);
15
- if (defaultValue === null) {
16
- return "null";
17
- }
18
- else if (typeof defaultValue === "string") {
19
- return JSON.stringify(defaultValue);
20
- }
21
- else if (typeof defaultValue === "number" ||
22
- typeof defaultValue === "boolean") {
23
- return defaultValue.toString();
24
- }
25
- else if (typeof defaultValue === "object") {
26
- if (seen.has(defaultValue)) {
27
- throw new TypeError("Circular reference detected");
28
- }
29
- seen.add(defaultValue);
30
- if (Array.isArray(defaultValue)) {
31
- const result = `[${defaultValue
32
- .map((item) => _serialize(item, seen))
33
- .join(",")}]`;
34
- seen.delete(defaultValue);
35
- return result;
36
- }
37
- else if (isLangChainSerializable(defaultValue)) {
38
- return JSON.stringify(defaultValue);
39
- }
40
- else {
41
- const entries = Object.entries(defaultValue).map(([k, v]) => `${JSON.stringify(k)}:${_serialize(v, seen)}`);
42
- const result = `{${entries.join(",")}}`;
43
- seen.delete(defaultValue);
44
- return result;
45
- }
46
- }
47
- // Only be reached for functions or symbols
48
- return JSON.stringify(defaultValue);
49
- };
11
+ /**
12
+ * The replacer in stringify does not allow delegation to built-in LangChain
13
+ * serialization methods, and instead immediately calls `.toJSON()` and
14
+ * continues to stringify subfields.
15
+ *
16
+ * We therefore must start from the most nested elements in the input and
17
+ * deserialize upwards rather than top-down.
18
+ */
50
19
  async function _reviver(value) {
51
20
  if (value && typeof value === "object") {
52
- if (value.lc === 2 && value.type === "undefined") {
53
- return undefined;
54
- }
55
- else if (value.lc === 2 &&
56
- value.type === "constructor" &&
57
- Array.isArray(value.id)) {
58
- try {
59
- const constructorName = value.id[value.id.length - 1];
60
- let constructor;
61
- switch (constructorName) {
62
- case "Set":
63
- constructor = Set;
64
- break;
65
- case "Map":
66
- constructor = Map;
67
- break;
68
- case "RegExp":
69
- constructor = RegExp;
70
- break;
71
- case "Error":
72
- constructor = Error;
73
- break;
74
- default:
75
- return value;
76
- }
77
- if (value.method) {
78
- return constructor[value.method](...(value.args || []));
79
- }
80
- else {
81
- return new constructor(...(value.args || []));
82
- }
83
- }
84
- catch (error) {
85
- return value;
86
- }
87
- }
88
- else if (isLangChainSerializedObject(value)) {
89
- return load(JSON.stringify(value));
90
- }
91
- else if (Array.isArray(value)) {
92
- return Promise.all(value.map((item) => _reviver(item)));
21
+ if (Array.isArray(value)) {
22
+ const revivedArray = await Promise.all(value.map((item) => _reviver(item)));
23
+ return revivedArray;
93
24
  }
94
25
  else {
95
26
  const revivedObj = {};
96
27
  for (const [k, v] of Object.entries(value)) {
97
28
  revivedObj[k] = await _reviver(v);
98
29
  }
30
+ if (revivedObj.lc === 2 && revivedObj.type === "undefined") {
31
+ return undefined;
32
+ }
33
+ else if (revivedObj.lc === 2 &&
34
+ revivedObj.type === "constructor" &&
35
+ Array.isArray(revivedObj.id)) {
36
+ try {
37
+ const constructorName = revivedObj.id[revivedObj.id.length - 1];
38
+ let constructor;
39
+ switch (constructorName) {
40
+ case "Set":
41
+ constructor = Set;
42
+ break;
43
+ case "Map":
44
+ constructor = Map;
45
+ break;
46
+ case "RegExp":
47
+ constructor = RegExp;
48
+ break;
49
+ case "Error":
50
+ constructor = Error;
51
+ break;
52
+ default:
53
+ return revivedObj;
54
+ }
55
+ if (revivedObj.method) {
56
+ return constructor[revivedObj.method](...(revivedObj.args || []));
57
+ }
58
+ else {
59
+ return new constructor(...(revivedObj.args || []));
60
+ }
61
+ }
62
+ catch (error) {
63
+ return revivedObj;
64
+ }
65
+ }
66
+ else if (isLangChainSerializedObject(revivedObj)) {
67
+ return load(JSON.stringify(revivedObj));
68
+ }
99
69
  return revivedObj;
100
70
  }
101
71
  }
@@ -113,7 +83,7 @@ constructor, method, args, kwargs) {
113
83
  kwargs: kwargs ?? {},
114
84
  };
115
85
  }
116
- function _default(_key, obj) {
86
+ function _default(obj) {
117
87
  if (obj === undefined) {
118
88
  return {
119
89
  lc: 2,
@@ -145,7 +115,9 @@ function _default(_key, obj) {
145
115
  export class JsonPlusSerializer {
146
116
  _dumps(obj) {
147
117
  const encoder = new TextEncoder();
148
- return encoder.encode(_serialize(obj));
118
+ return encoder.encode(stringify(obj, (_, value) => {
119
+ return _default(value);
120
+ }));
149
121
  }
150
122
  dumpsTyped(obj) {
151
123
  if (obj instanceof Uint8Array) {