@langgraph-js/pure-graph 3.2.0 → 3.2.3

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.
@@ -16,7 +16,16 @@ export declare class RedisStreamQueue extends BaseStreamQueue implements BaseStr
16
16
  cancelSignal: AbortController;
17
17
  private lastStreamId;
18
18
  private pollInterval;
19
+ private connectionReady;
19
20
  constructor(id: string, compressMessages?: boolean, ttl?: number);
21
+ /**
22
+ * 初始化 Redis 连接(使用共享连接池)
23
+ */
24
+ private initConnection;
25
+ /**
26
+ * 确保连接已建立
27
+ */
28
+ private ensureConnected;
20
29
  /**
21
30
  * 推送消息到 Redis Stream 和 List
22
31
  * - Stream: 用于实时推送(集群友好)
@@ -34,7 +43,11 @@ export declare class RedisStreamQueue extends BaseStreamQueue implements BaseStr
34
43
  /**
35
44
  * 清空队列
36
45
  */
37
- clear(): void;
46
+ clear(): Promise<void>;
47
+ /**
48
+ * 销毁队列实例,释放 Redis 连接引用
49
+ */
50
+ destroy(): Promise<void>;
38
51
  /**
39
52
  * 取消操作
40
53
  */
@@ -321,11 +321,7 @@ class StreamQueueManager {
321
321
  * @param id 队列 ID / Queue ID
322
322
  */
323
323
  async cancelQueue(id) {
324
- const queue = this.queues.get(id);
325
- if (queue) {
326
- await queue.cancel();
327
- this.removeQueue(id);
328
- }
324
+ await this.removeQueue(id);
329
325
  }
330
326
  /**
331
327
  * 向指定 id 的队列推送数据
@@ -370,10 +366,32 @@ class StreamQueueManager {
370
366
  * @param id 队列 ID / Queue ID
371
367
  * @returns 是否成功删除 / Whether successfully deleted
372
368
  */
373
- removeQueue(id) {
374
- setTimeout(() => {
375
- return this.queues.delete(id);
376
- }, 500);
369
+ async removeQueue(id) {
370
+ const queue = this.queues.get(id);
371
+ if (!queue) {
372
+ return false;
373
+ }
374
+ try {
375
+ await queue.cancel();
376
+ } catch (e) {
377
+ console.error("Error cancelling queue:", e);
378
+ }
379
+ try {
380
+ const clearResult = queue.clear();
381
+ if (clearResult instanceof Promise) {
382
+ await clearResult;
383
+ }
384
+ } catch (e) {
385
+ console.error("Error clearing queue:", e);
386
+ }
387
+ if (typeof queue.destroy === "function") {
388
+ try {
389
+ await queue.destroy();
390
+ } catch (e) {
391
+ console.error("Error destroying queue:", e);
392
+ }
393
+ }
394
+ return this.queues.delete(id);
377
395
  }
378
396
  /**
379
397
  * 获取所有队列的 ID
@@ -448,15 +466,29 @@ class KyselyThreadsManager {
448
466
  }
449
467
  async create(payload) {
450
468
  const threadId = payload?.threadId || v7();
469
+ const now = /* @__PURE__ */ new Date();
470
+ const metadata = payload?.metadata || {};
471
+ const interrupts = {};
451
472
  if (payload?.ifExists === "raise") {
452
473
  const existing = await this.db.selectFrom("threads").select("thread_id").where("thread_id", "=", threadId).executeTakeFirst();
453
474
  if (existing) {
454
475
  throw new Error(`Thread with ID ${threadId} already exists.`);
455
476
  }
456
477
  }
457
- const now = /* @__PURE__ */ new Date();
458
- const metadata = payload?.metadata || {};
459
- const interrupts = {};
478
+ if (payload?.ifExists === "do_nothing" && payload?.threadId) {
479
+ const existing = await this.db.selectFrom("threads").selectAll().where("thread_id", "=", threadId).executeTakeFirst();
480
+ if (existing) {
481
+ return {
482
+ thread_id: existing.thread_id,
483
+ created_at: this.adapter.dbToDate(existing.created_at).toISOString(),
484
+ updated_at: this.adapter.dbToDate(existing.updated_at).toISOString(),
485
+ metadata: this.adapter.dbToJson(existing.metadata),
486
+ status: existing.status,
487
+ values: existing.values ? this.adapter.dbToJson(existing.values) : null,
488
+ interrupts: this.adapter.dbToJson(existing.interrupts)
489
+ };
490
+ }
491
+ }
460
492
  await this.db.insertInto("threads").values({
461
493
  thread_id: threadId,
462
494
  created_at: this.adapter.dateToDb(now),
@@ -596,10 +628,17 @@ class KyselyThreadsManager {
596
628
  if (targetThread.status === "busy") {
597
629
  throw new Error(`Thread with ID ${threadId} is busy, can't update state.`);
598
630
  }
599
- if (!targetThread.metadata?.graph_id) {
600
- throw new Error(`Thread with ID ${threadId} has no graph_id.`);
601
- }
602
631
  const graphId = targetThread.metadata?.graph_id;
632
+ if (!graphId) {
633
+ await this.set(threadId, {
634
+ values: thread.values ?? null
635
+ });
636
+ return {
637
+ configurable: {
638
+ thread_id: threadId
639
+ }
640
+ };
641
+ }
603
642
  const config = {
604
643
  configurable: {
605
644
  thread_id: threadId,
@@ -1146,13 +1185,19 @@ class StreamErrorEventMessage extends EventMessage {
1146
1185
 
1147
1186
  class MemoryStreamQueue extends BaseStreamQueue {
1148
1187
  data = [];
1188
+ activeGenerators = /* @__PURE__ */ new Set();
1189
+ isDestroyed = false;
1149
1190
  async push(item) {
1191
+ if (this.isDestroyed) return;
1150
1192
  const data = this.compressMessages ? await this.encodeData(item) : item;
1151
1193
  this.data.push(data);
1152
1194
  this.emit("dataChange", data);
1153
1195
  }
1154
1196
  onDataChange(listener) {
1197
+ if (this.isDestroyed) return () => {
1198
+ };
1155
1199
  this.on("dataChange", async (item) => {
1200
+ if (this.isDestroyed) return;
1156
1201
  listener(this.compressMessages ? await this.decodeData(item) : item);
1157
1202
  });
1158
1203
  return () => this.off("dataChange", listener);
@@ -1161,16 +1206,27 @@ class MemoryStreamQueue extends BaseStreamQueue {
1161
1206
  * 异步生成器:支持 for await...of 方式消费队列数据
1162
1207
  */
1163
1208
  async *onDataReceive() {
1164
- let queue = [];
1209
+ if (this.isDestroyed) {
1210
+ return;
1211
+ }
1212
+ const localAbortController = new AbortController();
1213
+ this.activeGenerators.add(localAbortController);
1214
+ let localQueue = [];
1165
1215
  let pendingResolve = null;
1166
1216
  let isStreamEnded = false;
1167
1217
  let isCleanupDone = false;
1218
+ let endTimeoutId = null;
1168
1219
  const handleData = async (item) => {
1220
+ if (isCleanupDone || localAbortController.signal.aborted) return;
1169
1221
  try {
1170
1222
  const data = this.compressMessages ? await this.decodeData(item) : item;
1171
- queue.push(data);
1223
+ localQueue.push(data);
1172
1224
  if (data.event === "__stream_end__" || data.event === "__stream_error__" || data.event === "__stream_cancel__") {
1173
- setTimeout(() => {
1225
+ if (endTimeoutId) {
1226
+ clearTimeout(endTimeoutId);
1227
+ endTimeoutId = null;
1228
+ }
1229
+ endTimeoutId = setTimeout(() => {
1174
1230
  isStreamEnded = true;
1175
1231
  if (pendingResolve) {
1176
1232
  pendingResolve();
@@ -1178,7 +1234,7 @@ class MemoryStreamQueue extends BaseStreamQueue {
1178
1234
  }
1179
1235
  }, 300);
1180
1236
  if (data.event === "__stream_cancel__") {
1181
- await this.cancel();
1237
+ localAbortController.abort("stream cancelled");
1182
1238
  }
1183
1239
  }
1184
1240
  if (pendingResolve) {
@@ -1201,35 +1257,39 @@ class MemoryStreamQueue extends BaseStreamQueue {
1201
1257
  pendingResolve = null;
1202
1258
  }
1203
1259
  };
1204
- this.cancelSignal.signal.addEventListener("abort", abortHandler);
1260
+ localAbortController.signal.addEventListener("abort", abortHandler);
1205
1261
  const cleanup = () => {
1206
1262
  if (isCleanupDone) return;
1207
1263
  isCleanupDone = true;
1264
+ if (endTimeoutId) {
1265
+ clearTimeout(endTimeoutId);
1266
+ endTimeoutId = null;
1267
+ }
1208
1268
  try {
1209
1269
  this.off("dataChange", handleData);
1210
1270
  } catch (e) {
1211
- console.error("Error removing dataChange listener:", e);
1212
1271
  }
1213
1272
  try {
1214
- this.cancelSignal.signal.removeEventListener("abort", abortHandler);
1273
+ localAbortController.signal.removeEventListener("abort", abortHandler);
1215
1274
  } catch (e) {
1216
- console.error("Error removing abort listener:", e);
1217
1275
  }
1218
1276
  if (pendingResolve) {
1219
1277
  pendingResolve();
1220
1278
  pendingResolve = null;
1221
1279
  }
1280
+ localQueue.length = 0;
1281
+ this.activeGenerators.delete(localAbortController);
1222
1282
  };
1223
1283
  try {
1224
- if (this.cancelSignal.signal.aborted) {
1284
+ if (localAbortController.signal.aborted || this.isDestroyed) {
1225
1285
  return;
1226
1286
  }
1227
- while (!isStreamEnded && !this.cancelSignal.signal.aborted) {
1228
- if (queue.length > 0) {
1229
- for (const item of queue) {
1287
+ while (!isStreamEnded && !localAbortController.signal.aborted && !this.isDestroyed) {
1288
+ if (localQueue.length > 0) {
1289
+ for (const item of localQueue) {
1230
1290
  yield item;
1231
1291
  }
1232
- queue = [];
1292
+ localQueue.length = 0;
1233
1293
  } else {
1234
1294
  await new Promise((resolve) => {
1235
1295
  pendingResolve = resolve;
@@ -1241,17 +1301,29 @@ class MemoryStreamQueue extends BaseStreamQueue {
1241
1301
  }
1242
1302
  }
1243
1303
  async getAll() {
1304
+ if (this.isDestroyed) return [];
1244
1305
  return this.compressMessages ? await Promise.all(
1245
1306
  this.data.map((i) => this.decodeData(i))
1246
- ) : this.data;
1307
+ ) : [...this.data];
1247
1308
  }
1248
1309
  clear() {
1249
- this.data = [];
1310
+ this.data.length = 0;
1250
1311
  }
1251
1312
  cancelSignal = new AbortController();
1252
1313
  async cancel() {
1253
- this.cancelSignal.abort("user cancel this run");
1254
- await this.push(new CancelEventMessage());
1314
+ for (const controller of this.activeGenerators) {
1315
+ try {
1316
+ controller.abort("user cancel this run");
1317
+ } catch (e) {
1318
+ }
1319
+ }
1320
+ this.activeGenerators.clear();
1321
+ if (!this.cancelSignal.signal.aborted) {
1322
+ this.cancelSignal.abort("user cancel this run");
1323
+ }
1324
+ if (!this.isDestroyed) {
1325
+ await this.push(new CancelEventMessage());
1326
+ }
1255
1327
  }
1256
1328
  async copyToQueue(toId, ttl) {
1257
1329
  const data = this.data.slice();
@@ -1259,6 +1331,17 @@ class MemoryStreamQueue extends BaseStreamQueue {
1259
1331
  queue.data = data;
1260
1332
  return queue;
1261
1333
  }
1334
+ /**
1335
+ * 销毁队列,释放所有资源
1336
+ */
1337
+ async destroy() {
1338
+ if (this.isDestroyed) return;
1339
+ this.isDestroyed = true;
1340
+ await this.cancel();
1341
+ this.clear();
1342
+ this.removeAllListeners();
1343
+ this.activeGenerators.clear();
1344
+ }
1262
1345
  }
1263
1346
 
1264
1347
  class MemoryThreadsManager {
@@ -1624,12 +1707,12 @@ const createCheckPointer = async () => {
1624
1707
  if (process.env.SQLITE_DATABASE_URI) {
1625
1708
  if (process.env.CHECKPOINT_TYPE === "sqlite") {
1626
1709
  console.debug("LG | Using sqlite (full) as checkpoint");
1627
- const { SqliteSaver } = await import('./checkpoint-C5AFBYE-.js');
1710
+ const { SqliteSaver } = await import('./checkpoint-Due32543.js');
1628
1711
  const db2 = await SqliteSaver.fromConnStringAsync(process.env.SQLITE_DATABASE_URI);
1629
1712
  return db2;
1630
1713
  }
1631
1714
  console.debug("LG | Using shallow sqlite as checkpoint (default)");
1632
- const { SqliteShallowSaver: SqliteShallowSaver2 } = await import('./shallow-checkpoint-BEhTdp7z.js');
1715
+ const { SqliteShallowSaver: SqliteShallowSaver2 } = await import('./shallow-checkpoint-CHYRdSct.js');
1633
1716
  const db = await SqliteShallowSaver2.fromConnStringAsync(process.env.SQLITE_DATABASE_URI);
1634
1717
  return db;
1635
1718
  }
@@ -1648,7 +1731,7 @@ const createMessageQueue = async () => {
1648
1731
  let q;
1649
1732
  if (process.env.REDIS_URL) {
1650
1733
  console.debug("LG | Using redis as stream queue");
1651
- const { RedisStreamQueue } = await import('./queue-DySatFkr.js');
1734
+ const { RedisStreamQueue } = await import('./queue-DPHwOl26.js');
1652
1735
  q = RedisStreamQueue;
1653
1736
  } else {
1654
1737
  q = MemoryStreamQueue;
@@ -1688,7 +1771,7 @@ const createThreadManager = async (config) => {
1688
1771
  }
1689
1772
  if (process.env.SQLITE_DATABASE_URI && config.checkpointer) {
1690
1773
  console.debug("LG | Using SQLite ThreadsManager");
1691
- const { SQLiteAdapter } = await import('./sqlite-adapter-oBA95xba.js');
1774
+ const { SQLiteAdapter } = await import('./sqlite-adapter-CJXgit1j.js');
1692
1775
  const database = config.checkpointer.db;
1693
1776
  const threadsManager = new KyselyThreadsManager(new SQLiteAdapter(database));
1694
1777
  await threadsManager.setup();
@@ -1734,7 +1817,7 @@ async function streamStateWithQueue(threads, run, queue, payload, options) {
1734
1817
  const graph = await options.getGraph(graphId, payload.config, {
1735
1818
  checkpointer: payload.temporary ? null : void 0
1736
1819
  });
1737
- const userStreamMode = payload.streamMode ?? [];
1820
+ const userStreamMode = Array.isArray(payload.streamMode) ? payload.streamMode : payload.streamMode ? [payload.streamMode] : [];
1738
1821
  const libStreamMode = /* @__PURE__ */ new Set([
1739
1822
  "values",
1740
1823
  ...userStreamMode.filter((mode) => mode !== "events" && mode !== "messages-tuple")
@@ -1756,27 +1839,28 @@ async function streamStateWithQueue(threads, run, queue, payload, options) {
1756
1839
  ...payload.config?.metadata,
1757
1840
  run_attempt: options.attempt
1758
1841
  };
1759
- const events = graph.stream(
1760
- payload.command != null ? getLangGraphCommand(payload.command) : payload.input ?? null,
1761
- {
1762
- interruptAfter: payload.interruptAfter,
1763
- interruptBefore: payload.interruptBefore,
1764
- tags: payload.config?.tags,
1765
- configurable: payload.config?.configurable,
1766
- recursionLimit: payload.config?.recursionLimit,
1767
- subgraphs: payload.streamSubgraphs,
1768
- metadata,
1769
- runId: run.run_id,
1770
- streamMode: [...libStreamMode],
1771
- signal: queue.cancelSignal.signal
1772
- }
1773
- );
1774
- let sendedMetadataMessage;
1775
- let messageChunks;
1842
+ let sendedMetadataMessage = null;
1843
+ let messageChunks = null;
1844
+ let eventsIterator = null;
1776
1845
  try {
1777
1846
  sendedMetadataMessage = /* @__PURE__ */ new Set();
1778
1847
  messageChunks = /* @__PURE__ */ new Map();
1779
- for await (const event of await events) {
1848
+ eventsIterator = await graph.stream(
1849
+ payload.command != null ? getLangGraphCommand(payload.command) : payload.input ?? null,
1850
+ {
1851
+ interruptAfter: payload.interruptAfter,
1852
+ interruptBefore: payload.interruptBefore,
1853
+ tags: payload.config?.tags,
1854
+ configurable: payload.config?.configurable,
1855
+ recursionLimit: payload.config?.recursionLimit,
1856
+ subgraphs: payload.streamSubgraphs,
1857
+ metadata,
1858
+ runId: run.run_id,
1859
+ streamMode: [...libStreamMode],
1860
+ signal: queue.cancelSignal.signal
1861
+ }
1862
+ );
1863
+ for await (const event of eventsIterator) {
1780
1864
  let ns = [];
1781
1865
  if (event.length === 3) {
1782
1866
  ns = event.splice(0, 1);
@@ -1788,8 +1872,8 @@ async function streamStateWithQueue(threads, run, queue, payload, options) {
1788
1872
  };
1789
1873
  if (event[0] === "values") {
1790
1874
  const value = event[1];
1791
- await queue.push(new EventMessage(getNameWithNs("values"), value));
1792
1875
  if (getNameWithNs("values") === "values") {
1876
+ await queue.push(new EventMessage(getNameWithNs("values"), value));
1793
1877
  if (value?.__interrupt__) {
1794
1878
  await threads.set(run.thread_id, {
1795
1879
  status: "interrupted",
@@ -1828,14 +1912,29 @@ async function streamStateWithQueue(threads, run, queue, payload, options) {
1828
1912
  await queue.push(new EventMessage(getNameWithNs("updates"), updates));
1829
1913
  }
1830
1914
  }
1915
+ } catch (error) {
1916
+ if (!(error instanceof Error && error.message?.includes("cancel"))) {
1917
+ console.error("streamStateWithQueue error:", error);
1918
+ try {
1919
+ await queue.push(new StreamErrorEventMessage(error));
1920
+ } catch (e) {
1921
+ }
1922
+ }
1923
+ throw error;
1831
1924
  } finally {
1832
- await queue.push(new StreamEndEventMessage());
1925
+ try {
1926
+ await queue.push(new StreamEndEventMessage());
1927
+ } catch (e) {
1928
+ }
1833
1929
  if (sendedMetadataMessage) {
1834
1930
  sendedMetadataMessage.clear();
1931
+ sendedMetadataMessage = null;
1835
1932
  }
1836
1933
  if (messageChunks) {
1837
1934
  messageChunks.clear();
1935
+ messageChunks = null;
1838
1936
  }
1937
+ eventsIterator = null;
1839
1938
  }
1840
1939
  }
1841
1940
  const serialiseAsDict = (obj, indent = 0) => {
@@ -1856,12 +1955,17 @@ async function* streamState(threads, run, payload, options) {
1856
1955
  run = await run;
1857
1956
  const queueId = run.run_id;
1858
1957
  const threadId = run.thread_id;
1958
+ let state = null;
1959
+ let queue = null;
1960
+ let backgroundTask = null;
1961
+ let isCleaningUp = false;
1859
1962
  try {
1860
1963
  await threads.set(threadId, { status: "busy" });
1861
1964
  await threads.updateRun(run.run_id, { status: "running" });
1862
- const queue = LangGraphGlobal.globalMessageQueue.createQueue(queueId);
1863
- const state = queue.onDataReceive();
1864
- streamStateWithQueue(threads, run, queue, payload, options).catch((error) => {
1965
+ queue = LangGraphGlobal.globalMessageQueue.createQueue(queueId);
1966
+ state = queue.onDataReceive();
1967
+ backgroundTask = streamStateWithQueue(threads, run, queue, payload, options).catch((error) => {
1968
+ if (isCleaningUp) return;
1865
1969
  if (error.message !== "user cancel this run") console.error("Queue task error:", error);
1866
1970
  LangGraphGlobal.globalMessageQueue.pushToQueue(queueId, new StreamErrorEventMessage(error));
1867
1971
  });
@@ -1874,16 +1978,37 @@ async function* streamState(threads, run, payload, options) {
1874
1978
  await threads.updateRun(run.run_id, { status: "error" });
1875
1979
  await threads.set(threadId, { status: "error" });
1876
1980
  } finally {
1981
+ isCleaningUp = true;
1982
+ if (state) {
1983
+ try {
1984
+ await state.return(void 0);
1985
+ } catch (e) {
1986
+ }
1987
+ state = null;
1988
+ }
1989
+ if (queue && !queue.cancelSignal.signal.aborted) {
1990
+ try {
1991
+ queue.cancelSignal.abort("Stream consumer disconnected");
1992
+ } catch (e) {
1993
+ }
1994
+ }
1995
+ if (backgroundTask) {
1996
+ try {
1997
+ await Promise.race([backgroundTask, new Promise((resolve) => setTimeout(resolve, 1e3))]);
1998
+ } catch (e) {
1999
+ }
2000
+ backgroundTask = null;
2001
+ }
1877
2002
  const nowState = await threads.get(threadId);
1878
2003
  if (nowState.status === "interrupted") {
1879
2004
  await LangGraphGlobal.globalMessageQueue.copyQueue(queueId, threadId, 3e4);
1880
2005
  } else {
1881
2006
  await threads.set(threadId, { status: "idle", interrupts: {} });
1882
2007
  }
1883
- await LangGraphGlobal.globalMessageQueue.clearQueue(queueId);
1884
- LangGraphGlobal.globalMessageQueue.removeQueue(queueId);
2008
+ await LangGraphGlobal.globalMessageQueue.removeQueue(queueId);
2009
+ queue = null;
1885
2010
  }
1886
2011
  }
1887
2012
 
1888
2013
  export { BaseStreamQueue as B, CancelEventMessage as C, GRAPHS as G, KyselyThreadsManager as K, LangGraphGlobal as L, streamState as a, getGraph as g, registerGraph as r, serialiseAsDict as s };
1889
- //# sourceMappingURL=stream-pZfO6Y-p.js.map
2014
+ //# sourceMappingURL=stream-Zt8tbgEj.js.map