@langchain/langgraph 1.3.7 → 1.4.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.
Files changed (199) hide show
  1. package/dist/channels/base.cjs +78 -2
  2. package/dist/channels/base.cjs.map +1 -1
  3. package/dist/channels/base.d.cts +35 -2
  4. package/dist/channels/base.d.cts.map +1 -1
  5. package/dist/channels/base.d.ts +35 -2
  6. package/dist/channels/base.d.ts.map +1 -1
  7. package/dist/channels/base.js +77 -4
  8. package/dist/channels/base.js.map +1 -1
  9. package/dist/channels/delta.cjs +136 -0
  10. package/dist/channels/delta.cjs.map +1 -0
  11. package/dist/channels/delta.d.cts +99 -0
  12. package/dist/channels/delta.d.cts.map +1 -0
  13. package/dist/channels/delta.d.ts +99 -0
  14. package/dist/channels/delta.d.ts.map +1 -0
  15. package/dist/channels/delta.js +136 -0
  16. package/dist/channels/delta.js.map +1 -0
  17. package/dist/channels/index.cjs +5 -0
  18. package/dist/channels/index.d.cts +3 -2
  19. package/dist/channels/index.d.ts +3 -2
  20. package/dist/channels/index.js +3 -2
  21. package/dist/constants.cjs +62 -4
  22. package/dist/constants.cjs.map +1 -1
  23. package/dist/constants.d.cts +33 -2
  24. package/dist/constants.d.cts.map +1 -1
  25. package/dist/constants.d.ts +33 -2
  26. package/dist/constants.d.ts.map +1 -1
  27. package/dist/constants.js +59 -5
  28. package/dist/constants.js.map +1 -1
  29. package/dist/errors.cjs +128 -0
  30. package/dist/errors.cjs.map +1 -1
  31. package/dist/errors.d.cts +86 -1
  32. package/dist/errors.d.cts.map +1 -1
  33. package/dist/errors.d.ts +86 -1
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/errors.js +123 -1
  36. package/dist/errors.js.map +1 -1
  37. package/dist/func/index.cjs +9 -2
  38. package/dist/func/index.cjs.map +1 -1
  39. package/dist/func/index.d.cts +14 -0
  40. package/dist/func/index.d.cts.map +1 -1
  41. package/dist/func/index.d.ts +14 -0
  42. package/dist/func/index.d.ts.map +1 -1
  43. package/dist/func/index.js +9 -2
  44. package/dist/func/index.js.map +1 -1
  45. package/dist/graph/graph.cjs +44 -7
  46. package/dist/graph/graph.cjs.map +1 -1
  47. package/dist/graph/graph.d.cts +24 -2
  48. package/dist/graph/graph.d.cts.map +1 -1
  49. package/dist/graph/graph.d.ts +24 -2
  50. package/dist/graph/graph.d.ts.map +1 -1
  51. package/dist/graph/graph.js +44 -7
  52. package/dist/graph/graph.js.map +1 -1
  53. package/dist/graph/index.d.ts +3 -3
  54. package/dist/graph/messages_reducer.cjs +55 -0
  55. package/dist/graph/messages_reducer.cjs.map +1 -1
  56. package/dist/graph/messages_reducer.d.cts +28 -1
  57. package/dist/graph/messages_reducer.d.cts.map +1 -1
  58. package/dist/graph/messages_reducer.d.ts +28 -1
  59. package/dist/graph/messages_reducer.d.ts.map +1 -1
  60. package/dist/graph/messages_reducer.js +56 -2
  61. package/dist/graph/messages_reducer.js.map +1 -1
  62. package/dist/graph/state.cjs +174 -7
  63. package/dist/graph/state.cjs.map +1 -1
  64. package/dist/graph/state.d.cts +193 -17
  65. package/dist/graph/state.d.cts.map +1 -1
  66. package/dist/graph/state.d.ts +193 -17
  67. package/dist/graph/state.d.ts.map +1 -1
  68. package/dist/graph/state.js +175 -8
  69. package/dist/graph/state.js.map +1 -1
  70. package/dist/graph/zod/schema.cjs +5 -0
  71. package/dist/graph/zod/schema.cjs.map +1 -1
  72. package/dist/graph/zod/schema.d.cts.map +1 -1
  73. package/dist/graph/zod/schema.d.ts.map +1 -1
  74. package/dist/graph/zod/schema.js +5 -0
  75. package/dist/graph/zod/schema.js.map +1 -1
  76. package/dist/index.cjs +11 -0
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.d.cts +11 -8
  79. package/dist/index.d.ts +11 -8
  80. package/dist/index.js +5 -3
  81. package/dist/index.js.map +1 -1
  82. package/dist/prebuilt/react_agent_executor.d.cts +1 -1
  83. package/dist/pregel/algo.cjs +182 -21
  84. package/dist/pregel/algo.cjs.map +1 -1
  85. package/dist/pregel/algo.d.cts +1 -1
  86. package/dist/pregel/algo.d.cts.map +1 -1
  87. package/dist/pregel/algo.d.ts +1 -1
  88. package/dist/pregel/algo.d.ts.map +1 -1
  89. package/dist/pregel/algo.js +185 -25
  90. package/dist/pregel/algo.js.map +1 -1
  91. package/dist/pregel/call.cjs +2 -1
  92. package/dist/pregel/call.cjs.map +1 -1
  93. package/dist/pregel/call.js +2 -1
  94. package/dist/pregel/call.js.map +1 -1
  95. package/dist/pregel/index.cjs +15 -3
  96. package/dist/pregel/index.cjs.map +1 -1
  97. package/dist/pregel/index.d.cts.map +1 -1
  98. package/dist/pregel/index.d.ts.map +1 -1
  99. package/dist/pregel/index.js +17 -5
  100. package/dist/pregel/index.js.map +1 -1
  101. package/dist/pregel/loop.cjs +362 -41
  102. package/dist/pregel/loop.cjs.map +1 -1
  103. package/dist/pregel/loop.js +365 -44
  104. package/dist/pregel/loop.js.map +1 -1
  105. package/dist/pregel/messages-v2.cjs +1 -1
  106. package/dist/pregel/messages-v2.js +1 -1
  107. package/dist/pregel/messages.cjs +1 -1
  108. package/dist/pregel/messages.js +1 -1
  109. package/dist/pregel/read.cjs +15 -5
  110. package/dist/pregel/read.cjs.map +1 -1
  111. package/dist/pregel/read.d.cts +9 -0
  112. package/dist/pregel/read.d.cts.map +1 -1
  113. package/dist/pregel/read.d.ts +9 -0
  114. package/dist/pregel/read.d.ts.map +1 -1
  115. package/dist/pregel/read.js +15 -5
  116. package/dist/pregel/read.js.map +1 -1
  117. package/dist/pregel/remote-run-stream.cjs +107 -0
  118. package/dist/pregel/remote-run-stream.cjs.map +1 -0
  119. package/dist/pregel/remote-run-stream.d.cts +33 -0
  120. package/dist/pregel/remote-run-stream.d.cts.map +1 -0
  121. package/dist/pregel/remote-run-stream.d.ts +33 -0
  122. package/dist/pregel/remote-run-stream.d.ts.map +1 -0
  123. package/dist/pregel/remote-run-stream.js +107 -0
  124. package/dist/pregel/remote-run-stream.js.map +1 -0
  125. package/dist/pregel/remote.cjs +61 -1
  126. package/dist/pregel/remote.cjs.map +1 -1
  127. package/dist/pregel/remote.d.cts +17 -0
  128. package/dist/pregel/remote.d.cts.map +1 -1
  129. package/dist/pregel/remote.d.ts +17 -0
  130. package/dist/pregel/remote.d.ts.map +1 -1
  131. package/dist/pregel/remote.js +61 -1
  132. package/dist/pregel/remote.js.map +1 -1
  133. package/dist/pregel/replay.cjs +62 -0
  134. package/dist/pregel/replay.cjs.map +1 -0
  135. package/dist/pregel/replay.js +62 -0
  136. package/dist/pregel/replay.js.map +1 -0
  137. package/dist/pregel/retry.cjs +8 -6
  138. package/dist/pregel/retry.cjs.map +1 -1
  139. package/dist/pregel/retry.js +8 -6
  140. package/dist/pregel/retry.js.map +1 -1
  141. package/dist/pregel/runnable_types.d.cts +20 -0
  142. package/dist/pregel/runnable_types.d.cts.map +1 -1
  143. package/dist/pregel/runnable_types.d.ts +20 -0
  144. package/dist/pregel/runnable_types.d.ts.map +1 -1
  145. package/dist/pregel/runner.cjs +48 -7
  146. package/dist/pregel/runner.cjs.map +1 -1
  147. package/dist/pregel/runner.js +50 -9
  148. package/dist/pregel/runner.js.map +1 -1
  149. package/dist/pregel/runtime.cjs +64 -0
  150. package/dist/pregel/runtime.cjs.map +1 -0
  151. package/dist/pregel/runtime.d.cts +57 -0
  152. package/dist/pregel/runtime.d.cts.map +1 -0
  153. package/dist/pregel/runtime.d.ts +57 -0
  154. package/dist/pregel/runtime.d.ts.map +1 -0
  155. package/dist/pregel/runtime.js +64 -0
  156. package/dist/pregel/runtime.js.map +1 -0
  157. package/dist/pregel/stream.cjs +2 -0
  158. package/dist/pregel/stream.cjs.map +1 -1
  159. package/dist/pregel/stream.js +2 -0
  160. package/dist/pregel/stream.js.map +1 -1
  161. package/dist/pregel/timeout.cjs +216 -0
  162. package/dist/pregel/timeout.cjs.map +1 -0
  163. package/dist/pregel/timeout.js +216 -0
  164. package/dist/pregel/timeout.js.map +1 -0
  165. package/dist/pregel/types.cjs +3 -1
  166. package/dist/pregel/types.cjs.map +1 -1
  167. package/dist/pregel/types.d.cts +13 -0
  168. package/dist/pregel/types.d.cts.map +1 -1
  169. package/dist/pregel/types.d.ts +14 -1
  170. package/dist/pregel/types.d.ts.map +1 -1
  171. package/dist/pregel/types.js +3 -1
  172. package/dist/pregel/types.js.map +1 -1
  173. package/dist/pregel/utils/config.cjs +3 -1
  174. package/dist/pregel/utils/config.cjs.map +1 -1
  175. package/dist/pregel/utils/config.d.cts.map +1 -1
  176. package/dist/pregel/utils/config.d.ts.map +1 -1
  177. package/dist/pregel/utils/config.js +3 -1
  178. package/dist/pregel/utils/config.js.map +1 -1
  179. package/dist/pregel/utils/index.cjs +1 -0
  180. package/dist/pregel/utils/index.cjs.map +1 -1
  181. package/dist/pregel/utils/index.d.cts +6 -1
  182. package/dist/pregel/utils/index.d.cts.map +1 -1
  183. package/dist/pregel/utils/index.d.ts +6 -1
  184. package/dist/pregel/utils/index.d.ts.map +1 -1
  185. package/dist/pregel/utils/index.js +1 -0
  186. package/dist/pregel/utils/index.js.map +1 -1
  187. package/dist/pregel/utils/timeout.cjs +34 -0
  188. package/dist/pregel/utils/timeout.cjs.map +1 -0
  189. package/dist/pregel/utils/timeout.d.cts +45 -0
  190. package/dist/pregel/utils/timeout.d.cts.map +1 -0
  191. package/dist/pregel/utils/timeout.d.ts +45 -0
  192. package/dist/pregel/utils/timeout.d.ts.map +1 -0
  193. package/dist/pregel/utils/timeout.js +34 -0
  194. package/dist/pregel/utils/timeout.js.map +1 -0
  195. package/dist/web.cjs +11 -0
  196. package/dist/web.d.cts +11 -8
  197. package/dist/web.d.ts +11 -8
  198. package/dist/web.js +5 -3
  199. package/package.json +5 -5
@@ -1,6 +1,14 @@
1
+ const require_constants = require("../constants.cjs");
1
2
  const require_errors = require("../errors.cjs");
2
3
  let _langchain_langgraph_checkpoint = require("@langchain/langgraph-checkpoint");
3
4
  //#region src/channels/base.ts
5
+ /**
6
+ * Structural check for a {@link DeltaChannel} without importing it (avoids an
7
+ * import cycle: `delta.ts` imports `base.ts`).
8
+ */
9
+ function isDeltaChannel(channel) {
10
+ return channel != null && channel.lc_graph_name === "DeltaChannel";
11
+ }
4
12
  function isBaseChannel(obj) {
5
13
  return obj != null && obj.lg_is_channel === true;
6
14
  }
@@ -78,15 +86,45 @@ function emptyChannels(channels, checkpoint) {
78
86
  Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });
79
87
  return newChannels;
80
88
  }
89
+ /**
90
+ * Return the set of {@link DeltaChannel} names that should snapshot now.
91
+ *
92
+ * A channel snapshots when EITHER its accumulated update count reaches
93
+ * `snapshotFrequency` OR the total supersteps since its last snapshot reaches
94
+ * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.
95
+ */
96
+ function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot) {
97
+ const result = /* @__PURE__ */ new Set();
98
+ const maxSupersteps = require_constants.getDeltaMaxSuperstepsSinceSnapshot();
99
+ for (const name in channels) {
100
+ if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;
101
+ const ch = channels[name];
102
+ if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;
103
+ const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];
104
+ if (updates >= ch.snapshotFrequency || supersteps >= maxSupersteps) result.add(name);
105
+ }
106
+ return result;
107
+ }
81
108
  function createCheckpoint(checkpoint, channels, step, options) {
109
+ const channelsToSnapshot = options?.channelsToSnapshot ?? /* @__PURE__ */ new Set();
110
+ const { updatedChannels, getNextVersion } = options ?? {};
82
111
  let values;
112
+ let channelVersions = checkpoint.channel_versions;
83
113
  if (channels === void 0) values = checkpoint.channel_values;
84
114
  else {
85
115
  values = {};
116
+ channelVersions = { ...checkpoint.channel_versions };
86
117
  for (const k in channels) {
87
118
  if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;
119
+ const channel = channels[k];
120
+ if (channelsToSnapshot.has(k)) {
121
+ if (getNextVersion !== void 0 && (updatedChannels === void 0 || !updatedChannels.has(k))) channelVersions[k] = getNextVersion(channelVersions[k]);
122
+ values[k] = new _langchain_langgraph_checkpoint.DeltaSnapshot(channel.get());
123
+ continue;
124
+ }
125
+ if (isDeltaChannel(channel)) continue;
88
126
  try {
89
- values[k] = channels[k].checkpoint();
127
+ values[k] = channel.checkpoint();
90
128
  } catch (error) {
91
129
  if (error.name === require_errors.EmptyChannelError.unminifiable_name) {} else throw error;
92
130
  }
@@ -97,15 +135,53 @@ function createCheckpoint(checkpoint, channels, step, options) {
97
135
  id: options?.id ?? (0, _langchain_langgraph_checkpoint.uuid6)(step),
98
136
  ts: (/* @__PURE__ */ new Date()).toISOString(),
99
137
  channel_values: values,
100
- channel_versions: checkpoint.channel_versions,
138
+ channel_versions: channelVersions,
101
139
  versions_seen: checkpoint.versions_seen
102
140
  };
103
141
  }
142
+ /**
143
+ * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
144
+ * whose value is absent from `channel_values` by replaying ancestor writes.
145
+ *
146
+ * For most channels (and for delta channels with a {@link DeltaSnapshot} or a
147
+ * migrated plain value in `channel_values`), {@link emptyChannels} is
148
+ * sufficient and no saver access is required. When a delta channel is absent
149
+ * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
150
+ * finds the nearest seed and accumulates the writes between it and the
151
+ * target. All delta channels needing replay are batched into a single saver
152
+ * call.
153
+ */
154
+ async function channelsFromCheckpoint(specs, checkpoint, options) {
155
+ const channels = emptyChannels(specs, checkpoint);
156
+ const { saver, config } = options ?? {};
157
+ const filteredSpecs = getOnlyChannels(specs);
158
+ const deltaKeys = [];
159
+ for (const k in filteredSpecs) {
160
+ if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
161
+ if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
162
+ }
163
+ if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
164
+ const histories = await saver.getDeltaChannelHistory({
165
+ config,
166
+ channels: deltaKeys
167
+ });
168
+ for (const k of deltaKeys) {
169
+ const history = histories[k];
170
+ if (history === void 0) continue;
171
+ const replayCh = filteredSpecs[k].fromCheckpoint(history.seed);
172
+ replayCh.replayWrites(history.writes);
173
+ channels[k] = replayCh;
174
+ }
175
+ return channels;
176
+ }
104
177
  //#endregion
105
178
  exports.BaseChannel = BaseChannel;
179
+ exports.channelsFromCheckpoint = channelsFromCheckpoint;
106
180
  exports.createCheckpoint = createCheckpoint;
181
+ exports.deltaChannelsToSnapshot = deltaChannelsToSnapshot;
107
182
  exports.emptyChannels = emptyChannels;
108
183
  exports.getOnlyChannels = getOnlyChannels;
109
184
  exports.isBaseChannel = isBaseChannel;
185
+ exports.isDeltaChannel = isDeltaChannel;
110
186
 
111
187
  //# sourceMappingURL=base.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.cjs","names":["EmptyChannelError"],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n} from \"@langchain/langgraph-checkpoint\";\nimport { EmptyChannelError } from \"../errors.js\";\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: { id?: string }\n): Checkpoint {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n try {\n values[k] = channels[k].checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: checkpoint.channel_versions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n"],"mappings":";;;AAOA,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAS,IAAoB,kBAAkB;;;AAI/D,IAAsB,cAAtB,MAIE;CACA;CAEA;;CAQA,gBAAgB;;;;;;;;CAkDhB,UAAmB;AACjB,SAAO;;;;;;;;CAST,SAAkB;AAChB,SAAO;;;;;;;CAQT,cAAuB;AACrB,MAAI;AACF,QAAK,KAAK;AACV,UAAO;WAEA,OAAY;AACnB,OAAI,MAAM,SAASA,eAAAA,kBAAkB,kBACnC,QAAO;AAET,SAAM;;;;;;;;;;;CAYV,OAAO,OAA6B;AAClC,SAAO,SAAS;;;AAIpB,MAAM,uBAAuB,OAAO,IAAI,0BAA0B;AAClE,SAAgB,gBACd,UAC6B;AAE7B,KAAI,SAAS,0BAA0B,KAAM,QAAO;CAEpD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;EACxD,MAAM,QAAQ,SAAS;AACvB,MAAI,cAAc,MAAM,CAAE,aAAY,KAAK;;AAG7C,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,cACd,UACA,YACI;CACJ,MAAM,mBAAmB,gBAAgB,SAAS;CAElD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,kBAAkB;AAChC,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,kBAAkB,EAAE,CAAE;EAChE,MAAM,eAAe,WAAW,eAAe;AAC/C,cAAY,KAAK,iBAAiB,GAAG,eAAe,aAAa;;AAEnE,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,iBACd,YACA,UACA,MACA,SACY;CAEZ,IAAI;AACJ,KAAI,aAAa,KAAA,EACf,UAAS,WAAW;MACf;AACL,WAAS,EAAE;AACX,OAAK,MAAM,KAAK,UAAU;AACxB,OAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;AACxD,OAAI;AACF,WAAO,KAAK,SAAS,GAAG,YAAY;YAE7B,OAAY;AACnB,QAAI,MAAM,SAASA,eAAAA,kBAAkB,mBAAmB,OAGtD,OAAM;;;;AAMd,QAAO;EACL,GAAG;EACH,IAAI,SAAS,OAAA,GAAA,gCAAA,OAAY,KAAK;EAC9B,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB;EAChB,kBAAkB,WAAW;EAC7B,eAAe,WAAW;EAC3B"}
1
+ {"version":3,"file":"base.cjs","names":["EmptyChannelError","getDeltaMaxSuperstepsSinceSnapshot","DeltaSnapshot"],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: { saver?: BaseCheckpointSaver; config?: RunnableConfig }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys: string[] = [];\n for (const k in filteredSpecs) {\n if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;\n if (\n isDeltaChannel(filteredSpecs[k]) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n deltaKeys.push(k);\n }\n }\n\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,eAAe,SAA+B;AAC5D,QAAO,WAAW,QAAQ,QAAQ,kBAAkB;;AAGtD,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAS,IAAoB,kBAAkB;;;AAI/D,IAAsB,cAAtB,MAIE;CACA;CAEA;;CAQA,gBAAgB;;;;;;;;CAkDhB,UAAmB;AACjB,SAAO;;;;;;;;CAST,SAAkB;AAChB,SAAO;;;;;;;CAQT,cAAuB;AACrB,MAAI;AACF,QAAK,KAAK;AACV,UAAO;WAEA,OAAY;AACnB,OAAI,MAAM,SAASA,eAAAA,kBAAkB,kBACnC,QAAO;AAET,SAAM;;;;;;;;;;;CAYV,OAAO,OAA6B;AAClC,SAAO,SAAS;;;AAIpB,MAAM,uBAAuB,OAAO,IAAI,0BAA0B;AAClE,SAAgB,gBACd,UAC6B;AAE7B,KAAI,SAAS,0BAA0B,KAAM,QAAO;CAEpD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;EACxD,MAAM,QAAQ,SAAS;AACvB,MAAI,cAAc,MAAM,CAAE,aAAY,KAAK;;AAG7C,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,cACd,UACA,YACI;CACJ,MAAM,mBAAmB,gBAAgB,SAAS;CAElD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,kBAAkB;AAChC,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,kBAAkB,EAAE,CAAE;EAChE,MAAM,eAAe,WAAW,eAAe;AAC/C,cAAY,KAAK,iBAAiB,GAAG,eAAe,aAAa;;AAEnE,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;;;;;;;;AAmBT,SAAgB,wBACd,UACA,4BACa;CACb,MAAM,yBAAS,IAAI,KAAa;CAChC,MAAM,gBAAgBC,kBAAAA,oCAAoC;AAC1D,MAAK,MAAM,QAAQ,UAAU;AAC3B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,KAAK,CAAE;EAC3D,MAAM,KAAK,SAAS;AACpB,MAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,aAAa,CAAE;EAC9C,MAAM,CAAC,SAAS,cAAc,2BAA2B,SAAS,CAAC,GAAG,EAAE;AACxE,MACE,WAAY,GAAwB,qBACpC,cAAc,cAEd,QAAO,IAAI,KAAK;;AAGpB,QAAO;;AAGT,SAAgB,iBACd,YACA,UACA,MACA,SAMY;CACZ,MAAM,qBAAqB,SAAS,sCAAsB,IAAI,KAAa;CAC3E,MAAM,EAAE,iBAAiB,mBAAmB,WAAW,EAAE;CAEzD,IAAI;CACJ,IAAI,kBACF,WAAW;AACb,KAAI,aAAa,KAAA,EACf,UAAS,WAAW;MACf;AACL,WAAS,EAAE;AACX,oBAAkB,EAAE,GAAG,WAAW,kBAAkB;AACpD,OAAK,MAAM,KAAK,UAAU;AACxB,OAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;GACxD,MAAM,UAAU,SAAS;AACzB,OAAI,mBAAmB,IAAI,EAAE,EAAE;AAM7B,QACE,mBAAmB,KAAA,MAClB,oBAAoB,KAAA,KAAa,CAAC,gBAAgB,IAAI,EAAE,EAEzD,iBAAgB,KAAK,eAAe,gBAAgB,GAAG;AAEzD,WAAO,KAAK,IAAIC,gCAAAA,cAAc,QAAQ,KAAK,CAAC;AAC5C;;AAEF,OAAI,eAAe,QAAQ,CAEzB;AAEF,OAAI;AACF,WAAO,KAAK,QAAQ,YAAY;YAEzB,OAAY;AACnB,QAAI,MAAM,SAASF,eAAAA,kBAAkB,mBAAmB,OAGtD,OAAM;;;;AAMd,QAAO;EACL,GAAG;EACH,IAAI,SAAS,OAAA,GAAA,gCAAA,OAAY,KAAK;EAC9B,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB;EAChB,kBAAkB;EAClB,eAAe,WAAW;EAC3B;;;;;;;;;;;;;;AAeH,eAAsB,uBAGpB,OACA,YACA,SACa;CACb,MAAM,WAAW,cAAc,OAAO,WAAW;CACjD,MAAM,EAAE,OAAO,WAAW,WAAW,EAAE;CAEvC,MAAM,gBAAgB,gBAAgB,MAAM;CAC5C,MAAM,YAAsB,EAAE;AAC9B,MAAK,MAAM,KAAK,eAAe;AAC7B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,eAAe,EAAE,CAAE;AAC7D,MACE,eAAe,cAAc,GAAG,IAChC,CAAC,OAAO,UAAU,eAAe,KAAK,WAAW,gBAAgB,EAAE,CAEnE,WAAU,KAAK,EAAE;;AAIrB,KAAI,UAAU,WAAW,KAAK,UAAU,KAAA,KAAa,WAAW,KAAA,EAC9D,QAAO;CAGT,MAAM,YAAY,MAAM,MAAM,uBAAuB;EACnD;EACA,UAAU;EACX,CAAC;AACF,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,UAAU,UAAU;AAC1B,MAAI,YAAY,KAAA,EAAW;EAC3B,MAAM,WAAW,cAAc,GAAG,eAChC,QAAQ,KACT;AACD,WAAS,aAAa,QAAQ,OAAO;AACpC,WAAyC,KAAK;;AAEjD,QAAO"}
@@ -1,6 +1,12 @@
1
- import { Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
1
+ import { BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
2
+ import { RunnableConfig } from "@langchain/core/runnables";
2
3
 
3
4
  //#region src/channels/base.d.ts
5
+ /**
6
+ * Structural check for a {@link DeltaChannel} without importing it (avoids an
7
+ * import cycle: `delta.ts` imports `base.ts`).
8
+ */
9
+ declare function isDeltaChannel(channel: BaseChannel): boolean;
4
10
  /** @internal */
5
11
  declare abstract class BaseChannel<ValueType = unknown, UpdateType = unknown, CheckpointType = unknown> {
6
12
  ValueType: ValueType;
@@ -80,9 +86,36 @@ declare abstract class BaseChannel<ValueType = unknown, UpdateType = unknown, Ch
80
86
  equals(other: BaseChannel): boolean;
81
87
  }
82
88
  declare function emptyChannels<Cc extends Record<string, BaseChannel>>(channels: Cc, checkpoint: ReadonlyCheckpoint): Cc;
89
+ /**
90
+ * Return the set of {@link DeltaChannel} names that should snapshot now.
91
+ *
92
+ * A channel snapshots when EITHER its accumulated update count reaches
93
+ * `snapshotFrequency` OR the total supersteps since its last snapshot reaches
94
+ * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.
95
+ */
96
+ declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>): Set<string>;
83
97
  declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, channels: Record<string, BaseChannel<ValueType>> | undefined, step: number, options?: {
84
98
  id?: string;
99
+ channelsToSnapshot?: Set<string>;
100
+ updatedChannels?: Set<string>;
101
+ getNextVersion?: (current: number | string | undefined) => number | string;
85
102
  }): Checkpoint;
103
+ /**
104
+ * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
105
+ * whose value is absent from `channel_values` by replaying ancestor writes.
106
+ *
107
+ * For most channels (and for delta channels with a {@link DeltaSnapshot} or a
108
+ * migrated plain value in `channel_values`), {@link emptyChannels} is
109
+ * sufficient and no saver access is required. When a delta channel is absent
110
+ * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
111
+ * finds the nearest seed and accumulates the writes between it and the
112
+ * target. All delta channels needing replay are batched into a single saver
113
+ * call.
114
+ */
115
+ declare function channelsFromCheckpoint<Cc extends Record<string, BaseChannel>>(specs: Cc, checkpoint: ReadonlyCheckpoint, options?: {
116
+ saver?: BaseCheckpointSaver;
117
+ config?: RunnableConfig;
118
+ }): Promise<Cc>;
86
119
  //#endregion
87
- export { BaseChannel, createCheckpoint, emptyChannels };
120
+ export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, isDeltaChannel };
88
121
  //# sourceMappingURL=base.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.cts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;uBAYsB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AAcd;EArEE,MAAA,CAAA;EAqE8B;;;;;EA5D9B,WAAA,CAAA;EAiEW;;;;;;;;EA5CX,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;AAAA,iBAaa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EAAY,EAAA;AAAA,IACX,UAAA"}
1
+ {"version":3,"file":"base.d.cts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;AAgBA;;iBAAgB,cAAA,CAAe,OAAA,EAAS,WAAA;;uBASlB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AA8Bd;EArFE,MAAA,CAAA;;;;;;EASA,WAAA,CAAA;EA+EI;;;;;;;;EA1DJ,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;;;;;;;;iBA6Ba,uBAAA,CACd,QAAA,EAAU,MAAA,SAAe,WAAA,GACzB,0BAAA,EAA4B,MAAA,6BAC3B,GAAA;AAAA,iBAkBa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EACE,EAAA;EACA,kBAAA,GAAqB,GAAA;EACrB,eAAA,GAAkB,GAAA;EAClB,cAAA,IAAkB,OAAA;AAAA,IAEnB,UAAA;;;;;;;;;;;;;iBAqEmB,sBAAA,YACT,MAAA,SAAe,WAAA,EAAA,CAE1B,KAAA,EAAO,EAAA,EACP,UAAA,EAAY,kBAAA,EACZ,OAAA;EAAY,KAAA,GAAQ,mBAAA;EAAqB,MAAA,GAAS,cAAA;AAAA,IACjD,OAAA,CAAQ,EAAA"}
@@ -1,6 +1,12 @@
1
- import { Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
1
+ import { BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
2
+ import { RunnableConfig } from "@langchain/core/runnables";
2
3
 
3
4
  //#region src/channels/base.d.ts
5
+ /**
6
+ * Structural check for a {@link DeltaChannel} without importing it (avoids an
7
+ * import cycle: `delta.ts` imports `base.ts`).
8
+ */
9
+ declare function isDeltaChannel(channel: BaseChannel): boolean;
4
10
  /** @internal */
5
11
  declare abstract class BaseChannel<ValueType = unknown, UpdateType = unknown, CheckpointType = unknown> {
6
12
  ValueType: ValueType;
@@ -80,9 +86,36 @@ declare abstract class BaseChannel<ValueType = unknown, UpdateType = unknown, Ch
80
86
  equals(other: BaseChannel): boolean;
81
87
  }
82
88
  declare function emptyChannels<Cc extends Record<string, BaseChannel>>(channels: Cc, checkpoint: ReadonlyCheckpoint): Cc;
89
+ /**
90
+ * Return the set of {@link DeltaChannel} names that should snapshot now.
91
+ *
92
+ * A channel snapshots when EITHER its accumulated update count reaches
93
+ * `snapshotFrequency` OR the total supersteps since its last snapshot reaches
94
+ * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.
95
+ */
96
+ declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>): Set<string>;
83
97
  declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, channels: Record<string, BaseChannel<ValueType>> | undefined, step: number, options?: {
84
98
  id?: string;
99
+ channelsToSnapshot?: Set<string>;
100
+ updatedChannels?: Set<string>;
101
+ getNextVersion?: (current: number | string | undefined) => number | string;
85
102
  }): Checkpoint;
103
+ /**
104
+ * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
105
+ * whose value is absent from `channel_values` by replaying ancestor writes.
106
+ *
107
+ * For most channels (and for delta channels with a {@link DeltaSnapshot} or a
108
+ * migrated plain value in `channel_values`), {@link emptyChannels} is
109
+ * sufficient and no saver access is required. When a delta channel is absent
110
+ * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
111
+ * finds the nearest seed and accumulates the writes between it and the
112
+ * target. All delta channels needing replay are batched into a single saver
113
+ * call.
114
+ */
115
+ declare function channelsFromCheckpoint<Cc extends Record<string, BaseChannel>>(specs: Cc, checkpoint: ReadonlyCheckpoint, options?: {
116
+ saver?: BaseCheckpointSaver;
117
+ config?: RunnableConfig;
118
+ }): Promise<Cc>;
86
119
  //#endregion
87
- export { BaseChannel, createCheckpoint, emptyChannels };
120
+ export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, isDeltaChannel };
88
121
  //# sourceMappingURL=base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;uBAYsB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AAcd;EArEE,MAAA,CAAA;EAqE8B;;;;;EA5D9B,WAAA,CAAA;EAiEW;;;;;;;;EA5CX,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;AAAA,iBAaa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EAAY,EAAA;AAAA,IACX,UAAA"}
1
+ {"version":3,"file":"base.d.ts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;AAgBA;;iBAAgB,cAAA,CAAe,OAAA,EAAS,WAAA;;uBASlB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AA8Bd;EArFE,MAAA,CAAA;;;;;;EASA,WAAA,CAAA;EA+EI;;;;;;;;EA1DJ,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;;;;;;;;iBA6Ba,uBAAA,CACd,QAAA,EAAU,MAAA,SAAe,WAAA,GACzB,0BAAA,EAA4B,MAAA,6BAC3B,GAAA;AAAA,iBAkBa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EACE,EAAA;EACA,kBAAA,GAAqB,GAAA;EACrB,eAAA,GAAkB,GAAA;EAClB,cAAA,IAAkB,OAAA;AAAA,IAEnB,UAAA;;;;;;;;;;;;;iBAqEmB,sBAAA,YACT,MAAA,SAAe,WAAA,EAAA,CAE1B,KAAA,EAAO,EAAA,EACP,UAAA,EAAY,kBAAA,EACZ,OAAA;EAAY,KAAA,GAAQ,mBAAA;EAAqB,MAAA,GAAS,cAAA;AAAA,IACjD,OAAA,CAAQ,EAAA"}
@@ -1,6 +1,14 @@
1
+ import { getDeltaMaxSuperstepsSinceSnapshot } from "../constants.js";
1
2
  import { EmptyChannelError } from "../errors.js";
2
- import { uuid6 } from "@langchain/langgraph-checkpoint";
3
+ import { DeltaSnapshot, uuid6 } from "@langchain/langgraph-checkpoint";
3
4
  //#region src/channels/base.ts
5
+ /**
6
+ * Structural check for a {@link DeltaChannel} without importing it (avoids an
7
+ * import cycle: `delta.ts` imports `base.ts`).
8
+ */
9
+ function isDeltaChannel(channel) {
10
+ return channel != null && channel.lc_graph_name === "DeltaChannel";
11
+ }
4
12
  function isBaseChannel(obj) {
5
13
  return obj != null && obj.lg_is_channel === true;
6
14
  }
@@ -78,15 +86,45 @@ function emptyChannels(channels, checkpoint) {
78
86
  Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });
79
87
  return newChannels;
80
88
  }
89
+ /**
90
+ * Return the set of {@link DeltaChannel} names that should snapshot now.
91
+ *
92
+ * A channel snapshots when EITHER its accumulated update count reaches
93
+ * `snapshotFrequency` OR the total supersteps since its last snapshot reaches
94
+ * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.
95
+ */
96
+ function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot) {
97
+ const result = /* @__PURE__ */ new Set();
98
+ const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();
99
+ for (const name in channels) {
100
+ if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;
101
+ const ch = channels[name];
102
+ if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;
103
+ const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];
104
+ if (updates >= ch.snapshotFrequency || supersteps >= maxSupersteps) result.add(name);
105
+ }
106
+ return result;
107
+ }
81
108
  function createCheckpoint(checkpoint, channels, step, options) {
109
+ const channelsToSnapshot = options?.channelsToSnapshot ?? /* @__PURE__ */ new Set();
110
+ const { updatedChannels, getNextVersion } = options ?? {};
82
111
  let values;
112
+ let channelVersions = checkpoint.channel_versions;
83
113
  if (channels === void 0) values = checkpoint.channel_values;
84
114
  else {
85
115
  values = {};
116
+ channelVersions = { ...checkpoint.channel_versions };
86
117
  for (const k in channels) {
87
118
  if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;
119
+ const channel = channels[k];
120
+ if (channelsToSnapshot.has(k)) {
121
+ if (getNextVersion !== void 0 && (updatedChannels === void 0 || !updatedChannels.has(k))) channelVersions[k] = getNextVersion(channelVersions[k]);
122
+ values[k] = new DeltaSnapshot(channel.get());
123
+ continue;
124
+ }
125
+ if (isDeltaChannel(channel)) continue;
88
126
  try {
89
- values[k] = channels[k].checkpoint();
127
+ values[k] = channel.checkpoint();
90
128
  } catch (error) {
91
129
  if (error.name === EmptyChannelError.unminifiable_name) {} else throw error;
92
130
  }
@@ -97,11 +135,46 @@ function createCheckpoint(checkpoint, channels, step, options) {
97
135
  id: options?.id ?? uuid6(step),
98
136
  ts: (/* @__PURE__ */ new Date()).toISOString(),
99
137
  channel_values: values,
100
- channel_versions: checkpoint.channel_versions,
138
+ channel_versions: channelVersions,
101
139
  versions_seen: checkpoint.versions_seen
102
140
  };
103
141
  }
142
+ /**
143
+ * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
144
+ * whose value is absent from `channel_values` by replaying ancestor writes.
145
+ *
146
+ * For most channels (and for delta channels with a {@link DeltaSnapshot} or a
147
+ * migrated plain value in `channel_values`), {@link emptyChannels} is
148
+ * sufficient and no saver access is required. When a delta channel is absent
149
+ * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
150
+ * finds the nearest seed and accumulates the writes between it and the
151
+ * target. All delta channels needing replay are batched into a single saver
152
+ * call.
153
+ */
154
+ async function channelsFromCheckpoint(specs, checkpoint, options) {
155
+ const channels = emptyChannels(specs, checkpoint);
156
+ const { saver, config } = options ?? {};
157
+ const filteredSpecs = getOnlyChannels(specs);
158
+ const deltaKeys = [];
159
+ for (const k in filteredSpecs) {
160
+ if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
161
+ if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
162
+ }
163
+ if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
164
+ const histories = await saver.getDeltaChannelHistory({
165
+ config,
166
+ channels: deltaKeys
167
+ });
168
+ for (const k of deltaKeys) {
169
+ const history = histories[k];
170
+ if (history === void 0) continue;
171
+ const replayCh = filteredSpecs[k].fromCheckpoint(history.seed);
172
+ replayCh.replayWrites(history.writes);
173
+ channels[k] = replayCh;
174
+ }
175
+ return channels;
176
+ }
104
177
  //#endregion
105
- export { BaseChannel, createCheckpoint, emptyChannels, getOnlyChannels, isBaseChannel };
178
+ export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, getOnlyChannels, isBaseChannel, isDeltaChannel };
106
179
 
107
180
  //# sourceMappingURL=base.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n} from \"@langchain/langgraph-checkpoint\";\nimport { EmptyChannelError } from \"../errors.js\";\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: { id?: string }\n): Checkpoint {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n try {\n values[k] = channels[k].checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: checkpoint.channel_versions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n"],"mappings":";;;AAOA,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAS,IAAoB,kBAAkB;;;AAI/D,IAAsB,cAAtB,MAIE;CACA;CAEA;;CAQA,gBAAgB;;;;;;;;CAkDhB,UAAmB;AACjB,SAAO;;;;;;;;CAST,SAAkB;AAChB,SAAO;;;;;;;CAQT,cAAuB;AACrB,MAAI;AACF,QAAK,KAAK;AACV,UAAO;WAEA,OAAY;AACnB,OAAI,MAAM,SAAS,kBAAkB,kBACnC,QAAO;AAET,SAAM;;;;;;;;;;;CAYV,OAAO,OAA6B;AAClC,SAAO,SAAS;;;AAIpB,MAAM,uBAAuB,OAAO,IAAI,0BAA0B;AAClE,SAAgB,gBACd,UAC6B;AAE7B,KAAI,SAAS,0BAA0B,KAAM,QAAO;CAEpD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;EACxD,MAAM,QAAQ,SAAS;AACvB,MAAI,cAAc,MAAM,CAAE,aAAY,KAAK;;AAG7C,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,cACd,UACA,YACI;CACJ,MAAM,mBAAmB,gBAAgB,SAAS;CAElD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,kBAAkB;AAChC,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,kBAAkB,EAAE,CAAE;EAChE,MAAM,eAAe,WAAW,eAAe;AAC/C,cAAY,KAAK,iBAAiB,GAAG,eAAe,aAAa;;AAEnE,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,iBACd,YACA,UACA,MACA,SACY;CAEZ,IAAI;AACJ,KAAI,aAAa,KAAA,EACf,UAAS,WAAW;MACf;AACL,WAAS,EAAE;AACX,OAAK,MAAM,KAAK,UAAU;AACxB,OAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;AACxD,OAAI;AACF,WAAO,KAAK,SAAS,GAAG,YAAY;YAE7B,OAAY;AACnB,QAAI,MAAM,SAAS,kBAAkB,mBAAmB,OAGtD,OAAM;;;;AAMd,QAAO;EACL,GAAG;EACH,IAAI,SAAS,MAAM,MAAM,KAAK;EAC9B,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB;EAChB,kBAAkB,WAAW;EAC7B,eAAe,WAAW;EAC3B"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: { saver?: BaseCheckpointSaver; config?: RunnableConfig }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys: string[] = [];\n for (const k in filteredSpecs) {\n if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;\n if (\n isDeltaChannel(filteredSpecs[k]) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n deltaKeys.push(k);\n }\n }\n\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ const require_constants = require("../constants.cjs");
2
+ const require_errors = require("../errors.cjs");
3
+ const require_base = require("./base.cjs");
4
+ let _langchain_langgraph_checkpoint = require("@langchain/langgraph-checkpoint");
5
+ //#region src/channels/delta.ts
6
+ const isDeltaChannel = (value) => {
7
+ return value != null && value.lc_graph_name === "DeltaChannel";
8
+ };
9
+ /**
10
+ * Reducer channel that stores only a sentinel in checkpoint blobs and
11
+ * reconstructs state by replaying ancestor writes through the reducer.
12
+ *
13
+ * `DeltaChannel` avoids re-serializing the full accumulated value at every
14
+ * step. Instead of writing the value into `channel_values`, the channel is
15
+ * omitted entirely and its state is reconstructed on read by walking the
16
+ * ancestor chain and replaying the per-step writes through the reducer (see
17
+ * {@link BaseCheckpointSaver.getDeltaChannelHistory}).
18
+ *
19
+ * Snapshot cadence is driven by two counters: a per-channel update count and
20
+ * the total supersteps since the last snapshot. A full {@link DeltaSnapshot}
21
+ * blob is written when EITHER the update count reaches `snapshotFrequency` OR
22
+ * the supersteps count reaches the system-wide
23
+ * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` bound (default 5000), bounding replay
24
+ * depth even for channels that stop receiving writes.
25
+ *
26
+ * @remarks Beta. The API and on-disk representation may change in future
27
+ * releases. Threads written with `DeltaChannel` today are expected to remain
28
+ * readable, but the surrounding contract (`getDeltaChannelHistory`, the
29
+ * `DeltaSnapshot` blob shape, the `counters_since_delta_snapshot` metadata
30
+ * field) is not yet stable.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { Annotation } from "@langchain/langgraph";
35
+ * import { DeltaChannel, messagesDeltaReducer } from "@langchain/langgraph";
36
+ *
37
+ * const State = Annotation.Root({
38
+ * messages: Annotation<BaseMessage[]>({
39
+ * reducer: () => [], // ignored; DeltaChannel is supplied below
40
+ * }),
41
+ * });
42
+ * ```
43
+ */
44
+ var DeltaChannel = class DeltaChannel extends require_base.BaseChannel {
45
+ lc_graph_name = "DeltaChannel";
46
+ /** `undefined` represents the Python `MISSING` sentinel (empty channel). */
47
+ value;
48
+ reducer;
49
+ snapshotFrequency;
50
+ initialValueFactory;
51
+ constructor(reducer, options) {
52
+ super();
53
+ const snapshotFrequency = options?.snapshotFrequency ?? 1e3;
54
+ if (!Number.isInteger(snapshotFrequency) || snapshotFrequency <= 0) throw new Error(`snapshotFrequency must be a positive integer, got ${snapshotFrequency}`);
55
+ this.reducer = reducer;
56
+ this.snapshotFrequency = snapshotFrequency;
57
+ this.initialValueFactory = options?.initialValueFactory ?? (() => []);
58
+ this.value = void 0;
59
+ }
60
+ fromCheckpoint(checkpoint) {
61
+ const empty = new DeltaChannel(this.reducer, {
62
+ snapshotFrequency: this.snapshotFrequency,
63
+ initialValueFactory: this.initialValueFactory
64
+ });
65
+ if (checkpoint === void 0) empty.value = this.initialValueFactory();
66
+ else if ((0, _langchain_langgraph_checkpoint.isDeltaSnapshot)(checkpoint)) empty.value = checkpoint.value;
67
+ else empty.value = checkpoint;
68
+ return empty;
69
+ }
70
+ /**
71
+ * Apply ancestor writes oldest-to-newest via a single reducer call.
72
+ *
73
+ * If any write is an Overwrite, the last one in the sequence acts as the
74
+ * reset point: its value becomes the new base and only writes after it are
75
+ * passed to the reducer.
76
+ */
77
+ replayWrites(writes) {
78
+ const values = writes.map((w) => w[2]);
79
+ if (values.length === 0) return;
80
+ let base = this.value;
81
+ let start = 0;
82
+ for (let i = 0; i < values.length; i += 1) {
83
+ const [isOverwrite, overwriteValue] = require_constants._getOverwriteValue(values[i]);
84
+ if (isOverwrite) {
85
+ base = overwriteValue !== void 0 && overwriteValue !== null ? overwriteValue : this.initialValueFactory();
86
+ start = i + 1;
87
+ }
88
+ }
89
+ const remaining = values.slice(start);
90
+ this.value = remaining.length > 0 ? this.reducer(base, remaining) : base;
91
+ }
92
+ update(values) {
93
+ if (values.length === 0) return false;
94
+ let overwriteIdx;
95
+ for (let i = 0; i < values.length; i += 1) if (require_constants._isOverwriteValue(values[i])) {
96
+ if (overwriteIdx !== void 0) throw new require_errors.InvalidUpdateError("Can receive only one Overwrite value per step.");
97
+ overwriteIdx = i;
98
+ }
99
+ if (overwriteIdx !== void 0) {
100
+ const [, overwriteValue] = require_constants._getOverwriteValue(values[overwriteIdx]);
101
+ const base = overwriteValue !== void 0 && overwriteValue !== null ? overwriteValue : this.initialValueFactory();
102
+ const remaining = values.slice(overwriteIdx + 1);
103
+ this.value = remaining.length > 0 ? this.reducer(base, remaining) : base;
104
+ return true;
105
+ }
106
+ const base = this.value === void 0 ? this.initialValueFactory() : this.value;
107
+ this.value = this.reducer(base, values);
108
+ return true;
109
+ }
110
+ get() {
111
+ if (this.value === void 0) throw new require_errors.EmptyChannelError();
112
+ return this.value;
113
+ }
114
+ /**
115
+ * Always returns `undefined` (the Python `MISSING` sentinel). Snapshot
116
+ * decisions live in `createCheckpoint`, which has the channel version and
117
+ * writes a {@link DeltaSnapshot} directly into `channel_values`. For
118
+ * non-snapshot steps the channel does not appear in `channel_values`;
119
+ * reconstruction walks ancestor writes via the saver's
120
+ * `getDeltaChannelHistory`.
121
+ */
122
+ checkpoint() {}
123
+ isAvailable() {
124
+ return this.value !== void 0;
125
+ }
126
+ equals(other) {
127
+ if (this === other) return true;
128
+ if (!isDeltaChannel(other)) return false;
129
+ if (this.snapshotFrequency !== other.snapshotFrequency) return false;
130
+ return this.reducer === other.reducer;
131
+ }
132
+ };
133
+ //#endregion
134
+ exports.DeltaChannel = DeltaChannel;
135
+
136
+ //# sourceMappingURL=delta.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.cjs","names":["BaseChannel","_getOverwriteValue","_isOverwriteValue","InvalidUpdateError","EmptyChannelError"],"sources":["../../src/channels/delta.ts"],"sourcesContent":["import {\n type CheckpointPendingWrite,\n DeltaSnapshot,\n isDeltaSnapshot,\n} from \"@langchain/langgraph-checkpoint\";\nimport {\n _getOverwriteValue,\n _isOverwriteValue,\n type OverwriteValue,\n} from \"../constants.js\";\nimport { EmptyChannelError, InvalidUpdateError } from \"../errors.js\";\nimport { BaseChannel } from \"./base.js\";\n\n/**\n * A batch reducer for use with {@link DeltaChannel}.\n *\n * Receives the current accumulated value and a batch of writes in one call,\n * returning the new accumulated value:\n * `reducer(state, [write1, write2, ...]) -> newState`.\n *\n * Reducers must be deterministic and batching-invariant (associative across\n * folds): applying two consecutive write batches separately must produce the\n * same state as applying their concatenation once:\n *\n * ```text\n * reducer(reducer(state, xs), ys) === reducer(state, xs.concat(ys))\n * ```\n *\n * This lets LangGraph replay checkpointed writes in larger batches than they\n * were originally produced without changing reconstructed state. If your\n * reducer is not associative, use {@link BinaryOperatorAggregate} instead —\n * `DeltaChannel` is not a drop-in replacement for every reducer.\n */\nexport type DeltaReducer<ValueType, UpdateType = unknown> = (\n state: ValueType,\n writes: UpdateType[]\n) => ValueType;\n\ntype OverwriteOrValue<ValueType, UpdateType> =\n | OverwriteValue<ValueType>\n | UpdateType;\n\nconst isDeltaChannel = (\n value: BaseChannel\n): value is DeltaChannel<unknown, unknown> => {\n return value != null && value.lc_graph_name === \"DeltaChannel\";\n};\n\n/**\n * Reducer channel that stores only a sentinel in checkpoint blobs and\n * reconstructs state by replaying ancestor writes through the reducer.\n *\n * `DeltaChannel` avoids re-serializing the full accumulated value at every\n * step. Instead of writing the value into `channel_values`, the channel is\n * omitted entirely and its state is reconstructed on read by walking the\n * ancestor chain and replaying the per-step writes through the reducer (see\n * {@link BaseCheckpointSaver.getDeltaChannelHistory}).\n *\n * Snapshot cadence is driven by two counters: a per-channel update count and\n * the total supersteps since the last snapshot. A full {@link DeltaSnapshot}\n * blob is written when EITHER the update count reaches `snapshotFrequency` OR\n * the supersteps count reaches the system-wide\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` bound (default 5000), bounding replay\n * depth even for channels that stop receiving writes.\n *\n * @remarks Beta. The API and on-disk representation may change in future\n * releases. Threads written with `DeltaChannel` today are expected to remain\n * readable, but the surrounding contract (`getDeltaChannelHistory`, the\n * `DeltaSnapshot` blob shape, the `counters_since_delta_snapshot` metadata\n * field) is not yet stable.\n *\n * @example\n * ```typescript\n * import { Annotation } from \"@langchain/langgraph\";\n * import { DeltaChannel, messagesDeltaReducer } from \"@langchain/langgraph\";\n *\n * const State = Annotation.Root({\n * messages: Annotation<BaseMessage[]>({\n * reducer: () => [], // ignored; DeltaChannel is supplied below\n * }),\n * });\n * ```\n */\nexport class DeltaChannel<\n ValueType = unknown,\n UpdateType = unknown,\n> extends BaseChannel<\n ValueType,\n OverwriteOrValue<ValueType, UpdateType>,\n undefined\n> {\n lc_graph_name = \"DeltaChannel\";\n\n /** `undefined` represents the Python `MISSING` sentinel (empty channel). */\n value: ValueType | undefined;\n\n reducer: DeltaReducer<ValueType, UpdateType>;\n\n snapshotFrequency: number;\n\n initialValueFactory: () => ValueType;\n\n constructor(\n reducer: DeltaReducer<ValueType, UpdateType>,\n options?: {\n snapshotFrequency?: number;\n initialValueFactory?: () => ValueType;\n }\n ) {\n super();\n const snapshotFrequency = options?.snapshotFrequency ?? 1000;\n if (!Number.isInteger(snapshotFrequency) || snapshotFrequency <= 0) {\n throw new Error(\n `snapshotFrequency must be a positive integer, got ${snapshotFrequency}`\n );\n }\n this.reducer = reducer;\n this.snapshotFrequency = snapshotFrequency;\n this.initialValueFactory =\n options?.initialValueFactory ?? (() => [] as ValueType);\n this.value = undefined;\n }\n\n public fromCheckpoint(checkpoint?: undefined | DeltaSnapshot | ValueType) {\n const empty = new DeltaChannel<ValueType, UpdateType>(this.reducer, {\n snapshotFrequency: this.snapshotFrequency,\n initialValueFactory: this.initialValueFactory,\n });\n if (checkpoint === undefined) {\n empty.value = this.initialValueFactory();\n } else if (isDeltaSnapshot(checkpoint)) {\n empty.value = checkpoint.value as ValueType;\n } else {\n empty.value = checkpoint as ValueType;\n }\n return empty as this;\n }\n\n /**\n * Apply ancestor writes oldest-to-newest via a single reducer call.\n *\n * If any write is an Overwrite, the last one in the sequence acts as the\n * reset point: its value becomes the new base and only writes after it are\n * passed to the reducer.\n */\n public replayWrites(writes: CheckpointPendingWrite[]): void {\n const values = writes.map((w) => w[2]);\n if (values.length === 0) return;\n let base = this.value as ValueType;\n let start = 0;\n for (let i = 0; i < values.length; i += 1) {\n const [isOverwrite, overwriteValue] = _getOverwriteValue<ValueType>(\n values[i]\n );\n if (isOverwrite) {\n base =\n overwriteValue !== undefined && overwriteValue !== null\n ? overwriteValue\n : this.initialValueFactory();\n start = i + 1;\n }\n }\n const remaining = values.slice(start) as UpdateType[];\n this.value = remaining.length > 0 ? this.reducer(base, remaining) : base;\n }\n\n public update(values: OverwriteOrValue<ValueType, UpdateType>[]): boolean {\n if (values.length === 0) return false;\n\n let overwriteIdx: number | undefined;\n for (let i = 0; i < values.length; i += 1) {\n if (_isOverwriteValue<ValueType>(values[i])) {\n if (overwriteIdx !== undefined) {\n throw new InvalidUpdateError(\n \"Can receive only one Overwrite value per step.\"\n );\n }\n overwriteIdx = i;\n }\n }\n\n if (overwriteIdx !== undefined) {\n const [, overwriteValue] = _getOverwriteValue<ValueType>(\n values[overwriteIdx]\n );\n const base =\n overwriteValue !== undefined && overwriteValue !== null\n ? overwriteValue\n : this.initialValueFactory();\n // Treat Overwrite as a hard reset: drop everything up to and including\n // the overwrite, keeping only writes that follow it. This mirrors\n // `replayWrites` so reconstruction from a checkpoint reproduces the live\n // state even when a plain write precedes the Overwrite in the same step.\n const remaining = values.slice(overwriteIdx + 1) as UpdateType[];\n this.value = remaining.length > 0 ? this.reducer(base, remaining) : base;\n return true;\n }\n\n const base =\n this.value === undefined ? this.initialValueFactory() : this.value;\n this.value = this.reducer(base, values as UpdateType[]);\n return true;\n }\n\n public get(): ValueType {\n if (this.value === undefined) {\n throw new EmptyChannelError();\n }\n return this.value;\n }\n\n /**\n * Always returns `undefined` (the Python `MISSING` sentinel). Snapshot\n * decisions live in `createCheckpoint`, which has the channel version and\n * writes a {@link DeltaSnapshot} directly into `channel_values`. For\n * non-snapshot steps the channel does not appear in `channel_values`;\n * reconstruction walks ancestor writes via the saver's\n * `getDeltaChannelHistory`.\n */\n public checkpoint(): undefined {\n return undefined;\n }\n\n isAvailable(): boolean {\n return this.value !== undefined;\n }\n\n equals(other: BaseChannel): boolean {\n if (this === other) return true;\n if (!isDeltaChannel(other)) return false;\n if (this.snapshotFrequency !== other.snapshotFrequency) return false;\n return this.reducer === other.reducer;\n }\n}\n"],"mappings":";;;;;AA0CA,MAAM,kBACJ,UAC4C;AAC5C,QAAO,SAAS,QAAQ,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsClD,IAAa,eAAb,MAAa,qBAGHA,aAAAA,YAIR;CACA,gBAAgB;;CAGhB;CAEA;CAEA;CAEA;CAEA,YACE,SACA,SAIA;AACA,SAAO;EACP,MAAM,oBAAoB,SAAS,qBAAqB;AACxD,MAAI,CAAC,OAAO,UAAU,kBAAkB,IAAI,qBAAqB,EAC/D,OAAM,IAAI,MACR,qDAAqD,oBACtD;AAEH,OAAK,UAAU;AACf,OAAK,oBAAoB;AACzB,OAAK,sBACH,SAAS,8BAA8B,EAAE;AAC3C,OAAK,QAAQ,KAAA;;CAGf,eAAsB,YAAoD;EACxE,MAAM,QAAQ,IAAI,aAAoC,KAAK,SAAS;GAClE,mBAAmB,KAAK;GACxB,qBAAqB,KAAK;GAC3B,CAAC;AACF,MAAI,eAAe,KAAA,EACjB,OAAM,QAAQ,KAAK,qBAAqB;gEACf,WAAW,CACpC,OAAM,QAAQ,WAAW;MAEzB,OAAM,QAAQ;AAEhB,SAAO;;;;;;;;;CAUT,aAAoB,QAAwC;EAC1D,MAAM,SAAS,OAAO,KAAK,MAAM,EAAE,GAAG;AACtC,MAAI,OAAO,WAAW,EAAG;EACzB,IAAI,OAAO,KAAK;EAChB,IAAI,QAAQ;AACZ,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;GACzC,MAAM,CAAC,aAAa,kBAAkBC,kBAAAA,mBACpC,OAAO,GACR;AACD,OAAI,aAAa;AACf,WACE,mBAAmB,KAAA,KAAa,mBAAmB,OAC/C,iBACA,KAAK,qBAAqB;AAChC,YAAQ,IAAI;;;EAGhB,MAAM,YAAY,OAAO,MAAM,MAAM;AACrC,OAAK,QAAQ,UAAU,SAAS,IAAI,KAAK,QAAQ,MAAM,UAAU,GAAG;;CAGtE,OAAc,QAA4D;AACxE,MAAI,OAAO,WAAW,EAAG,QAAO;EAEhC,IAAI;AACJ,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,EACtC,KAAIC,kBAAAA,kBAA6B,OAAO,GAAG,EAAE;AAC3C,OAAI,iBAAiB,KAAA,EACnB,OAAM,IAAIC,eAAAA,mBACR,iDACD;AAEH,kBAAe;;AAInB,MAAI,iBAAiB,KAAA,GAAW;GAC9B,MAAM,GAAG,kBAAkBF,kBAAAA,mBACzB,OAAO,cACR;GACD,MAAM,OACJ,mBAAmB,KAAA,KAAa,mBAAmB,OAC/C,iBACA,KAAK,qBAAqB;GAKhC,MAAM,YAAY,OAAO,MAAM,eAAe,EAAE;AAChD,QAAK,QAAQ,UAAU,SAAS,IAAI,KAAK,QAAQ,MAAM,UAAU,GAAG;AACpE,UAAO;;EAGT,MAAM,OACJ,KAAK,UAAU,KAAA,IAAY,KAAK,qBAAqB,GAAG,KAAK;AAC/D,OAAK,QAAQ,KAAK,QAAQ,MAAM,OAAuB;AACvD,SAAO;;CAGT,MAAwB;AACtB,MAAI,KAAK,UAAU,KAAA,EACjB,OAAM,IAAIG,eAAAA,mBAAmB;AAE/B,SAAO,KAAK;;;;;;;;;;CAWd,aAA+B;CAI/B,cAAuB;AACrB,SAAO,KAAK,UAAU,KAAA;;CAGxB,OAAO,OAA6B;AAClC,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,CAAC,eAAe,MAAM,CAAE,QAAO;AACnC,MAAI,KAAK,sBAAsB,MAAM,kBAAmB,QAAO;AAC/D,SAAO,KAAK,YAAY,MAAM"}