@langgraph-js/pure-graph 3.1.2 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/fetch/index.js +128 -30
- package/dist/adapter/fetch/index.js.map +1 -1
- package/dist/adapter/fetch/utils.d.ts +13 -3
- package/dist/adapter/nextjs/index.js +1 -1
- package/dist/{checkpoint-1sAx_j1E.js → checkpoint-CY59Lr2q.js} +79 -36
- package/dist/checkpoint-CY59Lr2q.js.map +1 -0
- package/dist/{createEndpoint-D1nfguxg.js → createEndpoint-vMmFiMSz.js} +20 -5
- package/dist/createEndpoint-vMmFiMSz.js.map +1 -0
- package/dist/createEndpoint.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/queue/stream_queue.d.ts +3 -1
- package/dist/{queue-Cu_nO_wt.js → queue-CUe5TDP1.js} +120 -16
- package/dist/queue-CUe5TDP1.js.map +1 -0
- package/dist/remote/index.js +1 -1
- package/dist/shallow-checkpoint-ImbLxYNR.js +466 -0
- package/dist/shallow-checkpoint-ImbLxYNR.js.map +1 -0
- package/dist/{sqlite-adapter-BKOLSdoL.js → sqlite-adapter-CJXgit1j.js} +25 -12
- package/dist/sqlite-adapter-CJXgit1j.js.map +1 -0
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/kysely/sqlite-adapter.d.ts +7 -0
- package/dist/storage/memory/queue.d.ts +6 -0
- package/dist/storage/redis/queue.d.ts +14 -1
- package/dist/storage/sqlite/shallow-checkpoint.d.ts +59 -0
- package/dist/{stream-CsqWsCjt.js → stream-jYlUzTZO.js} +199 -64
- package/dist/stream-jYlUzTZO.js.map +1 -0
- package/package.json +2 -1
- package/dist/checkpoint-1sAx_j1E.js.map +0 -1
- package/dist/createEndpoint-D1nfguxg.js.map +0 -1
- package/dist/queue-Cu_nO_wt.js.map +0 -1
- package/dist/sqlite-adapter-BKOLSdoL.js.map +0 -1
- package/dist/stream-CsqWsCjt.js.map +0 -1
|
@@ -27,9 +27,8 @@ export declare function jsonResponse(data: any, status?: number, headers?: Recor
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function errorResponse(error: unknown, status?: number): Response;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* SSE Writer 基础接口
|
|
31
31
|
*/
|
|
32
|
-
export declare function createSSEStream(streamFn: (writer: SSEWriter) => Promise<void>): Response;
|
|
33
32
|
export interface SSEWriter {
|
|
34
33
|
writeSSE: (data: {
|
|
35
34
|
data: string;
|
|
@@ -38,7 +37,18 @@ export interface SSEWriter {
|
|
|
38
37
|
}) => Promise<void>;
|
|
39
38
|
close: () => void;
|
|
40
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* 扩展的 SSE Writer 接口,包含中止信号
|
|
42
|
+
*/
|
|
43
|
+
export interface SSEWriterWithAbort extends SSEWriter {
|
|
44
|
+
/** 当客户端断开连接时触发的中止信号 */
|
|
45
|
+
signal: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 创建 SSE 流响应
|
|
49
|
+
*/
|
|
50
|
+
export declare function createSSEStream(streamFn: (writer: SSEWriterWithAbort) => Promise<void>): Response;
|
|
41
51
|
/**
|
|
42
52
|
* 为 SSE 流添加心跳功能
|
|
43
53
|
*/
|
|
44
|
-
export declare function withHeartbeat(streamFn: (writer:
|
|
54
|
+
export declare function withHeartbeat(streamFn: (writer: SSEWriterWithAbort) => Promise<void>, heartbeatInterval?: number): (writer: SSEWriterWithAbort) => Promise<void>;
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { Kysely, sql } from 'kysely';
|
|
2
2
|
import { BaseCheckpointSaver, TASKS, copyCheckpoint, maxChannelVersion } from '@langchain/langgraph-checkpoint';
|
|
3
3
|
|
|
4
|
+
const SQLITE_RETRY_CONFIG = {
|
|
5
|
+
maxRetries: 3,
|
|
6
|
+
baseDelayMs: 100,
|
|
7
|
+
isRetryableError: (error) => {
|
|
8
|
+
const msg = error?.message?.toLowerCase() || "";
|
|
9
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked") || msg.includes("database disk image is malformed") || msg === "sqlite_busy" || msg === "database is locked";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
async function withRetry(operation, context) {
|
|
13
|
+
let lastError = null;
|
|
14
|
+
for (let attempt = 0; attempt < SQLITE_RETRY_CONFIG.maxRetries; attempt++) {
|
|
15
|
+
try {
|
|
16
|
+
return await operation();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
lastError = error;
|
|
19
|
+
if (!SQLITE_RETRY_CONFIG.isRetryableError(error)) {
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
if (attempt < SQLITE_RETRY_CONFIG.maxRetries - 1) {
|
|
23
|
+
const delay = SQLITE_RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
|
|
24
|
+
console.warn(
|
|
25
|
+
`SQLite lock detected${context ? ` (${context})` : ""}, retrying in ${delay}ms (attempt ${attempt + 1}/${SQLITE_RETRY_CONFIG.maxRetries})`
|
|
26
|
+
);
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw lastError;
|
|
32
|
+
}
|
|
4
33
|
const checkpointMetadataKeys = ["source", "step", "parents"];
|
|
5
34
|
function validateKeys(keys) {
|
|
6
35
|
return keys;
|
|
@@ -22,8 +51,8 @@ class SqliteSaver extends BaseCheckpointSaver {
|
|
|
22
51
|
let saver;
|
|
23
52
|
if (globalThis.Bun) {
|
|
24
53
|
console.log("LG | Using BunWorkerDialect " + connStringOrLocalPath);
|
|
25
|
-
const {
|
|
26
|
-
saver = new SqliteSaver(new
|
|
54
|
+
const { BunSqliteDialect } = await import('kysely-bun-worker/normal');
|
|
55
|
+
saver = new SqliteSaver(new BunSqliteDialect({ url: connStringOrLocalPath }));
|
|
27
56
|
} else {
|
|
28
57
|
console.log("LG | Using NodeWasmDialect");
|
|
29
58
|
const { default: SqliteDatabase } = await import('node-sqlite3-wasm');
|
|
@@ -41,7 +70,10 @@ class SqliteSaver extends BaseCheckpointSaver {
|
|
|
41
70
|
if (this.isSetup) {
|
|
42
71
|
return;
|
|
43
72
|
}
|
|
73
|
+
await sql`PRAGMA busy_timeout = 5000`.execute(this.db);
|
|
44
74
|
await sql`PRAGMA journal_mode = WAL`.execute(this.db);
|
|
75
|
+
await sql`PRAGMA synchronous = NORMAL`.execute(this.db);
|
|
76
|
+
await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db);
|
|
45
77
|
await sql`
|
|
46
78
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
47
79
|
thread_id TEXT NOT NULL,
|
|
@@ -288,22 +320,27 @@ CREATE TABLE IF NOT EXISTS writes (
|
|
|
288
320
|
if (type1 !== type2) {
|
|
289
321
|
throw new Error("Failed to serialized checkpoint and metadata to the same type.");
|
|
290
322
|
}
|
|
291
|
-
await
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
await withRetry(
|
|
324
|
+
async () => {
|
|
325
|
+
await this.db.insertInto("checkpoints").values({
|
|
326
|
+
thread_id,
|
|
327
|
+
checkpoint_ns,
|
|
328
|
+
checkpoint_id: checkpoint.id,
|
|
329
|
+
parent_checkpoint_id: parent_checkpoint_id ?? null,
|
|
330
|
+
type: type1,
|
|
331
|
+
checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),
|
|
332
|
+
metadata: new Uint8Array(Buffer.from(serializedMetadata))
|
|
333
|
+
}).onConflict(
|
|
334
|
+
(oc) => oc.columns(["thread_id", "checkpoint_ns", "checkpoint_id"]).doUpdateSet({
|
|
335
|
+
parent_checkpoint_id: parent_checkpoint_id ?? null,
|
|
336
|
+
type: type1,
|
|
337
|
+
checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),
|
|
338
|
+
metadata: new Uint8Array(Buffer.from(serializedMetadata))
|
|
339
|
+
})
|
|
340
|
+
).execute();
|
|
341
|
+
},
|
|
342
|
+
`put(${thread_id}/${checkpoint.id})`
|
|
343
|
+
);
|
|
307
344
|
return {
|
|
308
345
|
configurable: {
|
|
309
346
|
thread_id,
|
|
@@ -338,25 +375,31 @@ CREATE TABLE IF NOT EXISTS writes (
|
|
|
338
375
|
};
|
|
339
376
|
})
|
|
340
377
|
);
|
|
341
|
-
if (values.length
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
378
|
+
if (values.length === 0) return;
|
|
379
|
+
const threadId = config.configurable.thread_id;
|
|
380
|
+
const checkpointId = config.configurable.checkpoint_id;
|
|
381
|
+
await withRetry(
|
|
382
|
+
async () => {
|
|
383
|
+
await this.db.transaction().execute(async (trx) => {
|
|
384
|
+
await trx.deleteFrom("writes").where("thread_id", "=", threadId).where("checkpoint_ns", "=", values[0].checkpoint_ns).where("checkpoint_id", "=", checkpointId).where("task_id", "=", taskId).execute();
|
|
385
|
+
for (const value of values) {
|
|
386
|
+
await trx.insertInto("writes").values(value).execute();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
`putWrites(${threadId}/${checkpointId}/${taskId})`
|
|
391
|
+
);
|
|
354
392
|
}
|
|
355
393
|
async deleteThread(threadId) {
|
|
356
|
-
await
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
394
|
+
await withRetry(
|
|
395
|
+
async () => {
|
|
396
|
+
await this.db.transaction().execute(async (trx) => {
|
|
397
|
+
await trx.deleteFrom("checkpoints").where("thread_id", "=", threadId).execute();
|
|
398
|
+
await trx.deleteFrom("writes").where("thread_id", "=", threadId).execute();
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
`deleteThread(${threadId})`
|
|
402
|
+
);
|
|
360
403
|
}
|
|
361
404
|
async migratePendingSends(checkpoint, threadId, parentCheckpointId) {
|
|
362
405
|
const result = await this.db.selectFrom("writes as ps").select([
|
|
@@ -381,4 +424,4 @@ CREATE TABLE IF NOT EXISTS writes (
|
|
|
381
424
|
}
|
|
382
425
|
|
|
383
426
|
export { SqliteSaver };
|
|
384
|
-
//# sourceMappingURL=checkpoint-
|
|
427
|
+
//# sourceMappingURL=checkpoint-CY59Lr2q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint-CY59Lr2q.js","sources":["../src/storage/sqlite/checkpoint.ts"],"sourcesContent":["import { Dialect, Kysely, SqliteDialect, sql } from 'kysely';\nimport type { RunnableConfig } from '@langchain/core/runnables';\n\nimport {\n BaseCheckpointSaver,\n type Checkpoint,\n type CheckpointListOptions,\n type CheckpointTuple,\n type SerializerProtocol,\n type PendingWrite,\n type CheckpointMetadata,\n TASKS,\n copyCheckpoint,\n maxChannelVersion,\n} from '@langchain/langgraph-checkpoint';\n\n/**\n * SQLite 重试配置\n */\nconst SQLITE_RETRY_CONFIG = {\n maxRetries: 3,\n baseDelayMs: 100,\n isRetryableError: (error: any): boolean => {\n const msg = error?.message?.toLowerCase() || '';\n // 精确匹配 SQLITE_BUSY 和 database is locked\n // 注意:不重试事务状态错误(如 cannot rollback),这些可能是结构性问题\n return (\n msg.includes('sqlite_busy') ||\n msg.includes('database is locked') ||\n msg.includes('database disk image is malformed') ||\n msg === 'sqlite_busy' ||\n msg === 'database is locked'\n );\n },\n};\n\n/**\n * 带重试的数据库操作包装器\n */\nasync function withRetry<T>(operation: () => Promise<T>, context?: string): Promise<T> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < SQLITE_RETRY_CONFIG.maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error: any) {\n lastError = error;\n\n if (!SQLITE_RETRY_CONFIG.isRetryableError(error)) {\n throw error;\n }\n\n if (attempt < SQLITE_RETRY_CONFIG.maxRetries - 1) {\n const delay = SQLITE_RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);\n console.warn(\n `SQLite lock detected${context ? ` (${context})` : ''}, retrying in ${delay}ms (attempt ${attempt + 1}/${SQLITE_RETRY_CONFIG.maxRetries})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw lastError;\n}\n\n// Kysely 数据库表类型定义\ninterface CheckpointsTable {\n thread_id: string;\n checkpoint_ns: string;\n checkpoint_id: string;\n parent_checkpoint_id: string | null;\n type: string | null;\n checkpoint: Uint8Array;\n metadata: Uint8Array;\n}\n\ninterface WritesTable {\n thread_id: string;\n checkpoint_ns: string;\n checkpoint_id: string;\n task_id: string;\n idx: number;\n channel: string;\n type: string | null;\n value: Uint8Array | null;\n}\n\ninterface CheckpointDatabase {\n checkpoints: CheckpointsTable;\n writes: WritesTable;\n}\n\ninterface CheckpointRow {\n checkpoint: string;\n metadata: string;\n parent_checkpoint_id?: string;\n thread_id: string;\n checkpoint_id: string;\n checkpoint_ns?: string;\n type?: string;\n pending_writes: string;\n}\n\ninterface PendingWriteColumn {\n task_id: string;\n channel: string;\n type: string;\n value: string;\n}\n\ninterface PendingSendColumn {\n type: string;\n value: string;\n}\n\n// In the `SqliteSaver.list` method, we need to sanitize the `options.filter` argument to ensure it only contains keys\n// that are part of the `CheckpointMetadata` type. The lines below ensure that we get compile-time errors if the list\n// of keys that we use is out of sync with the `CheckpointMetadata` type.\nconst checkpointMetadataKeys = ['source', 'step', 'parents'] as const;\n\ntype CheckKeys<T, K extends readonly (keyof T)[]> = [K[number]] extends [keyof T]\n ? [keyof T] extends [K[number]]\n ? K\n : never\n : never;\n\nfunction validateKeys<T, K extends readonly (keyof T)[]>(keys: CheckKeys<T, K>): K {\n return keys;\n}\n\n// If this line fails to compile, the list of keys that we use in the `SqliteSaver.list` method is out of sync with the\n// `CheckpointMetadata` type. In that case, just update `checkpointMetadataKeys` to contain all the keys in\n// `CheckpointMetadata`\nconst validCheckpointMetadataKeys = validateKeys<CheckpointMetadata, typeof checkpointMetadataKeys>(\n checkpointMetadataKeys,\n);\n\nexport class SqliteSaver extends BaseCheckpointSaver {\n db: Kysely<CheckpointDatabase>;\n\n protected isSetup: boolean;\n\n constructor(dialect: Dialect, serde?: SerializerProtocol) {\n super(serde);\n this.db = new Kysely<CheckpointDatabase>({\n dialect,\n });\n this.isSetup = false;\n }\n\n static async fromConnStringAsync(connStringOrLocalPath: string): Promise<SqliteSaver> {\n let saver: SqliteSaver;\n /** @ts-ignore */\n if (globalThis.Bun) {\n console.log('LG | Using BunWorkerDialect ' + connStringOrLocalPath);\n const { BunSqliteDialect } = await import('kysely-bun-worker/normal');\n // 使用 BunSqliteDialect(非 Worker 模式)避免 Worker 事务状态同步问题\n // BunWorkerDialect 在高并发下可能出现 \"cannot rollback - no transaction is active\"\n saver = new SqliteSaver(new BunSqliteDialect({ url: connStringOrLocalPath }));\n } else {\n /** @ts-ignore */\n console.log('LG | Using NodeWasmDialect');\n const { default: SqliteDatabase } = await import('node-sqlite3-wasm');\n const { NodeWasmDialect } = await import('kysely-wasm');\n console.log(connStringOrLocalPath);\n const wasm = new NodeWasmDialect({\n database: new SqliteDatabase.Database(connStringOrLocalPath),\n });\n saver = new SqliteSaver(wasm);\n }\n await saver.setup();\n return saver;\n }\n\n protected async setup(): Promise<void> {\n if (this.isSetup) {\n return;\n }\n\n // 锁等待超时 5 秒,避免立即返回 SQLITE_BUSY\n await sql`PRAGMA busy_timeout = 5000`.execute(this.db);\n\n // WAL 模式 - 允许读写并发\n await sql`PRAGMA journal_mode = WAL`.execute(this.db);\n\n // NORMAL 模式 - 平衡数据安全与性能\n await sql`PRAGMA synchronous = NORMAL`.execute(this.db);\n\n // WAL 自动检查点 - 每 1000 页执行一次,避免 WAL 文件无限增长\n await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db);\n\n await sql`\nCREATE TABLE IF NOT EXISTS checkpoints (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n parent_checkpoint_id TEXT,\n type TEXT,\n checkpoint BLOB,\n metadata BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)\n)`.execute(this.db);\n\n await sql`\nCREATE TABLE IF NOT EXISTS writes (\n thread_id TEXT NOT NULL,\n checkpoint_ns TEXT NOT NULL DEFAULT '',\n checkpoint_id TEXT NOT NULL,\n task_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n channel TEXT NOT NULL,\n type TEXT,\n value BLOB,\n PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)\n)`.execute(this.db);\n this.isSetup = true;\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n await this.setup();\n const { thread_id, checkpoint_ns = '', checkpoint_id } = config.configurable ?? {};\n\n let query = this.db\n .selectFrom('checkpoints')\n .select([\n 'thread_id',\n 'checkpoint_ns',\n 'checkpoint_id',\n 'parent_checkpoint_id',\n 'type',\n 'checkpoint',\n 'metadata',\n sql<string>`(\n SELECT json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n )`.as('pending_writes'),\n sql<string>`(\n SELECT json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = ${TASKS}\n ORDER BY ps.idx\n )`.as('pending_sends'),\n ])\n .where('thread_id', '=', thread_id)\n .where('checkpoint_ns', '=', checkpoint_ns);\n\n if (checkpoint_id) {\n query = query.where('checkpoint_id', '=', checkpoint_id);\n } else {\n query = query.orderBy('checkpoint_id', 'desc').limit(1);\n }\n\n const row = await query.executeTakeFirst();\n if (!row) return undefined;\n\n let finalConfig = config;\n\n if (!checkpoint_id) {\n finalConfig = {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n };\n }\n\n if (\n finalConfig.configurable?.thread_id === undefined ||\n finalConfig.configurable?.checkpoint_id === undefined\n ) {\n throw new Error('Missing thread_id or checkpoint_id');\n }\n\n const pendingWrites = await Promise.all(\n (JSON.parse(row.pending_writes) as PendingWriteColumn[]).map(async (write) => {\n return [\n write.task_id,\n write.channel,\n await this.serde.loadsTyped(write.type ?? 'json', write.value ?? ''),\n ] as [string, string, unknown];\n }),\n );\n\n const checkpoint = (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.checkpoint),\n )) as Checkpoint;\n\n if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {\n await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);\n }\n\n return {\n checkpoint,\n config: finalConfig,\n metadata: (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.metadata),\n )) as CheckpointMetadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n\n async *list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple> {\n const { limit, before, filter } = options ?? {};\n await this.setup();\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns;\n\n let query = this.db.selectFrom('checkpoints').select([\n 'thread_id',\n 'checkpoint_ns',\n 'checkpoint_id',\n 'parent_checkpoint_id',\n 'type',\n 'checkpoint',\n 'metadata',\n sql<string>`(\n SELECT json_group_array(\n json_object(\n 'task_id', pw.task_id,\n 'channel', pw.channel,\n 'type', pw.type,\n 'value', CAST(pw.value AS TEXT)\n )\n )\n FROM writes as pw\n WHERE pw.thread_id = checkpoints.thread_id\n AND pw.checkpoint_ns = checkpoints.checkpoint_ns\n AND pw.checkpoint_id = checkpoints.checkpoint_id\n )`.as('pending_writes'),\n sql<string>`(\n SELECT json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )\n FROM writes as ps\n WHERE ps.thread_id = checkpoints.thread_id\n AND ps.checkpoint_ns = checkpoints.checkpoint_ns\n AND ps.checkpoint_id = checkpoints.parent_checkpoint_id\n AND ps.channel = ${TASKS}\n ORDER BY ps.idx\n )`.as('pending_sends'),\n ]);\n\n if (thread_id) {\n query = query.where('thread_id', '=', thread_id);\n }\n\n if (checkpoint_ns !== undefined && checkpoint_ns !== null) {\n query = query.where('checkpoint_ns', '=', checkpoint_ns);\n }\n\n if (before?.configurable?.checkpoint_id !== undefined) {\n query = query.where('checkpoint_id', '<', before.configurable.checkpoint_id);\n }\n\n const sanitizedFilter = Object.fromEntries(\n Object.entries(filter ?? {}).filter(\n ([key, value]) =>\n value !== undefined && validCheckpointMetadataKeys.includes(key as keyof CheckpointMetadata),\n ),\n );\n\n for (const [key, value] of Object.entries(sanitizedFilter)) {\n query = query.where(\n sql`json_extract(CAST(metadata AS TEXT), ${sql.lit('$.' + key)})`,\n '=',\n sql.lit(JSON.stringify(value)),\n );\n }\n\n query = query.orderBy('checkpoint_id', 'desc');\n\n if (limit) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n query = query.limit(parseInt(limit as any, 10));\n }\n\n const rows = await query.execute();\n\n for (const row of rows) {\n const pendingWrites = await Promise.all(\n (JSON.parse(row.pending_writes) as PendingWriteColumn[]).map(async (write) => {\n return [\n write.task_id,\n write.channel,\n await this.serde.loadsTyped(write.type ?? 'json', write.value ?? ''),\n ] as [string, string, unknown];\n }),\n );\n\n const checkpoint = (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.checkpoint),\n )) as Checkpoint;\n\n if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {\n await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);\n }\n\n yield {\n config: {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\n },\n checkpoint,\n metadata: (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.metadata),\n )) as CheckpointMetadata,\n parentConfig: row.parent_checkpoint_id\n ? {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.parent_checkpoint_id,\n },\n }\n : undefined,\n pendingWrites,\n };\n }\n }\n\n async put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig> {\n await this.setup();\n\n if (!config.configurable) {\n throw new Error('Empty configuration supplied.');\n }\n\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns ?? '';\n const parent_checkpoint_id = config.configurable?.checkpoint_id;\n\n if (!thread_id) {\n throw new Error(`Missing \"thread_id\" field in passed \"config.configurable\".`);\n }\n\n const preparedCheckpoint: Partial<Checkpoint> = copyCheckpoint(checkpoint);\n\n // 序列化在事务外完成\n const [[type1, serializedCheckpoint], [type2, serializedMetadata]] = await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n\n if (type1 !== type2) {\n throw new Error('Failed to serialized checkpoint and metadata to the same type.');\n }\n\n // 带重试的数据库操作\n await withRetry(\n async () => {\n await this.db\n .insertInto('checkpoints')\n .values({\n thread_id,\n checkpoint_ns,\n checkpoint_id: checkpoint.id,\n parent_checkpoint_id: parent_checkpoint_id ?? null,\n type: type1,\n checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),\n metadata: new Uint8Array(Buffer.from(serializedMetadata)),\n })\n .onConflict((oc) =>\n oc.columns(['thread_id', 'checkpoint_ns', 'checkpoint_id']).doUpdateSet({\n parent_checkpoint_id: parent_checkpoint_id ?? null,\n type: type1,\n checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),\n metadata: new Uint8Array(Buffer.from(serializedMetadata)),\n }),\n )\n .execute();\n },\n `put(${thread_id}/${checkpoint.id})`,\n );\n\n return {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void> {\n await this.setup();\n\n if (!config.configurable) {\n throw new Error('Empty configuration supplied.');\n }\n\n if (!config.configurable?.thread_id) {\n throw new Error('Missing thread_id field in config.configurable.');\n }\n\n if (!config.configurable?.checkpoint_id) {\n throw new Error('Missing checkpoint_id field in config.configurable.');\n }\n\n // 预先序列化所有数据(在事务外完成,减少锁持有时间)\n const values = await Promise.all(\n writes.map(async (write, idx) => {\n const [type, serializedWrite] = await this.serde.dumpsTyped(write[1]);\n return {\n thread_id: config.configurable!.thread_id,\n checkpoint_ns: config.configurable!.checkpoint_ns ?? '',\n checkpoint_id: config.configurable!.checkpoint_id,\n task_id: taskId,\n idx,\n channel: write[0],\n type,\n value: new Uint8Array(Buffer.from(serializedWrite)),\n };\n }),\n );\n\n if (values.length === 0) return;\n\n const threadId = config.configurable.thread_id;\n const checkpointId = config.configurable.checkpoint_id;\n\n // 带重试的批量插入\n await withRetry(\n async () => {\n await this.db.transaction().execute(async (trx) => {\n // 先删除已存在的记录(比逐条 ON CONFLICT 更快)\n await trx\n .deleteFrom('writes')\n .where('thread_id', '=', threadId)\n .where('checkpoint_ns', '=', values[0].checkpoint_ns)\n .where('checkpoint_id', '=', checkpointId)\n .where('task_id', '=', taskId)\n .execute();\n\n // 批量插入\n for (const value of values) {\n await trx.insertInto('writes').values(value).execute();\n }\n });\n },\n `putWrites(${threadId}/${checkpointId}/${taskId})`,\n );\n }\n\n async deleteThread(threadId: string) {\n // 带重试的删除操作\n await withRetry(\n async () => {\n await this.db.transaction().execute(async (trx) => {\n await trx.deleteFrom('checkpoints').where('thread_id', '=', threadId).execute();\n await trx.deleteFrom('writes').where('thread_id', '=', threadId).execute();\n });\n },\n `deleteThread(${threadId})`,\n );\n }\n\n protected async migratePendingSends(checkpoint: Checkpoint, threadId: string, parentCheckpointId: string) {\n const result = await this.db\n .selectFrom('writes as ps')\n .select([\n 'ps.checkpoint_id',\n sql<string>`json_group_array(\n json_object(\n 'type', ps.type,\n 'value', CAST(ps.value AS TEXT)\n )\n )`.as('pending_sends'),\n ])\n .where('ps.thread_id', '=', threadId)\n .where('ps.checkpoint_id', '=', parentCheckpointId)\n .where('ps.channel', '=', TASKS)\n .orderBy('ps.idx')\n .executeTakeFirst();\n\n if (!result) return;\n\n const mutableCheckpoint = checkpoint;\n\n // add pending sends to checkpoint\n mutableCheckpoint.channel_values ??= {};\n mutableCheckpoint.channel_values[TASKS] = await Promise.all(\n JSON.parse(result.pending_sends).map(({ type, value }: PendingSendColumn) =>\n this.serde.loadsTyped(type, value),\n ),\n );\n\n // add to versions\n mutableCheckpoint.channel_versions[TASKS] =\n Object.keys(checkpoint.channel_versions).length > 0\n ? maxChannelVersion(...Object.values(checkpoint.channel_versions))\n : this.getNextVersion(undefined);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LangGraphGlobal, a as streamState, G as GRAPHS, g as getGraph } from './stream-
|
|
1
|
+
import { L as LangGraphGlobal, a as streamState, G as GRAPHS, g as getGraph } from './stream-jYlUzTZO.js';
|
|
2
2
|
|
|
3
3
|
const AssistantEndpoint = {
|
|
4
4
|
async search(query) {
|
|
@@ -168,10 +168,15 @@ const createEndpoint = () => {
|
|
|
168
168
|
async *joinStream(threadId, runId, options) {
|
|
169
169
|
const config = options && typeof options === "object" && "signal" in options ? options : {};
|
|
170
170
|
const signal = (options instanceof AbortSignal ? options : config.signal) || new AbortController().signal;
|
|
171
|
+
let queue = null;
|
|
172
|
+
let generator = null;
|
|
171
173
|
try {
|
|
172
|
-
|
|
174
|
+
queue = await LangGraphGlobal.globalMessageQueue.getQueue(runId);
|
|
173
175
|
const allData = await queue.getAll();
|
|
174
176
|
for (const eventMessage of allData) {
|
|
177
|
+
if (signal.aborted) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
175
180
|
yield {
|
|
176
181
|
id: eventMessage.id,
|
|
177
182
|
event: eventMessage.event,
|
|
@@ -181,7 +186,8 @@ const createEndpoint = () => {
|
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
|
-
|
|
189
|
+
generator = queue.onDataReceive();
|
|
190
|
+
for await (const eventMessage of generator) {
|
|
185
191
|
if (signal.aborted) {
|
|
186
192
|
break;
|
|
187
193
|
}
|
|
@@ -197,7 +203,16 @@ const createEndpoint = () => {
|
|
|
197
203
|
}
|
|
198
204
|
}
|
|
199
205
|
} catch (error) {
|
|
200
|
-
|
|
206
|
+
if (!(error instanceof Error) || !error.message.includes("does not exist")) {
|
|
207
|
+
console.warn("Join stream failed:", error);
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
if (generator) {
|
|
211
|
+
try {
|
|
212
|
+
await generator.return(void 0);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
201
216
|
}
|
|
202
217
|
}
|
|
203
218
|
}
|
|
@@ -205,4 +220,4 @@ const createEndpoint = () => {
|
|
|
205
220
|
};
|
|
206
221
|
|
|
207
222
|
export { AssistantEndpoint as A, createEndpoint as c };
|
|
208
|
-
//# sourceMappingURL=createEndpoint-
|
|
223
|
+
//# sourceMappingURL=createEndpoint-vMmFiMSz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createEndpoint-vMmFiMSz.js","sources":["../src/createEndpoint.ts"],"sourcesContent":["import { StreamEvent } from '@langchain/core/tracers/log_stream';\nimport { streamState } from './graph/stream.js';\nimport { Assistant, Run, StreamMode, Metadata, AssistantGraph } from '@langchain/langgraph-sdk';\nimport { getGraph, GRAPHS } from './utils/getGraph.js';\nimport { LangGraphGlobal } from './global.js';\nimport { AssistantSortBy, CancelAction, ILangGraphClient, RunStatus, SortOrder, StreamInputData } from './types.js';\nimport type { BaseStreamQueueInterface } from './queue/stream_queue.js';\nimport type { EventMessage } from './queue/event_message.js';\nexport { registerGraph } from './utils/getGraph.js';\n\nexport const AssistantEndpoint: ILangGraphClient['assistants'] = {\n async search(query?: {\n graphId?: string;\n metadata?: Metadata;\n limit?: number;\n offset?: number;\n sortBy?: AssistantSortBy;\n sortOrder?: SortOrder;\n }): Promise<Assistant[]> {\n let results = Object.entries(GRAPHS).map(\n ([graphId, _]) =>\n ({\n assistant_id: graphId,\n graph_id: graphId,\n config: {},\n metadata: {},\n version: 1,\n name: graphId,\n description: '',\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n } as Assistant),\n );\n\n // Filter by graphId\n if (query?.graphId) {\n results = results.filter((a) => a.graph_id === query.graphId);\n }\n\n // Filter by metadata (simple implementation - check if all metadata keys/values match)\n if (query?.metadata && Object.keys(query.metadata).length > 0) {\n results = results.filter((assistant) => {\n return Object.entries(query.metadata!).every(([key, value]) => {\n return assistant.metadata && assistant.metadata[key] === value;\n });\n });\n }\n\n // Sort results\n if (query?.sortBy) {\n results.sort((a, b) => {\n const aValue = a[query.sortBy!];\n const bValue = b[query.sortBy!];\n const comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0;\n return query.sortOrder === 'desc' ? -comparison : comparison;\n });\n }\n\n // Pagination\n const offset = query?.offset ?? 0;\n const limit = query?.limit;\n const paginatedResults = limit ? results.slice(offset, offset + limit) : results.slice(offset);\n\n return paginatedResults;\n },\n\n async count(query?: { graphId?: string; metadata?: Metadata }): Promise<number> {\n const results = await this.search(query);\n return results.length;\n },\n\n async get(assistantId: string): Promise<Assistant> {\n const assistant = Object.entries(GRAPHS).find(([graphId, _]) => graphId === assistantId);\n if (!assistant) {\n throw new Error(`Assistant not found: ${assistantId}`);\n }\n return {\n assistant_id: assistantId,\n graph_id: assistantId,\n config: {},\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n metadata: {},\n version: 1,\n name: assistantId,\n description: '',\n } as Assistant;\n },\n\n async delete(assistantId: string): Promise<void> {\n // ⚠️ 删除 assistant 不可用 - assistants 是从注册的图中生成的,不能删除\n throw new Error('Deleting assistants is not supported. Assistants are generated from registered graphs.');\n },\n\n async update(\n assistantId: string,\n updates: Partial<Pick<Assistant, 'name' | 'description' | 'metadata' | 'config'>>,\n ): Promise<Assistant> {\n // ⚠️ 更新 assistant 不可用 - assistants 是从注册的图中生成的,不能更新\n throw new Error('Updating assistants is not supported. Assistants are generated from registered graphs.');\n },\n\n async getGraph(assistantId: string, options?: { xray?: boolean | number }): Promise<AssistantGraph> {\n const config = {};\n const graph = await getGraph(assistantId, config);\n const drawable = await graph.getGraphAsync({\n ...config,\n xray: options?.xray ?? undefined,\n });\n return drawable.toJSON() as AssistantGraph;\n },\n\n async getSchemas(assistantId: string): Promise<{ graph_id: string; state_schema: any }> {\n const compiledGraph = await getGraph(assistantId, {});\n const builder = compiledGraph.builder;\n console.log(builder);\n return {\n graph_id: assistantId,\n /** @ts-ignore */\n state_schema: builder._inputDefinition,\n /** @ts-ignore */\n input_schema: builder._inputDefinition,\n /** @ts-ignore */\n output_schema: builder._outputDefinition,\n /** @ts-ignore */\n config_schema: builder._configSchema,\n /** @ts-ignoreß */\n context_schema: builder._configSchema,\n };\n },\n\n async getVersions(assistantId: string, options?: { limit?: number; offset?: number }): Promise<Assistant[]> {\n // ⚠️ 版本管理不可用 - 当前实现不支持多版本\n const assistant = await this.get(assistantId);\n const offset = options?.offset ?? 0;\n const limit = options?.limit;\n const results = limit ? [assistant].slice(offset, offset + limit) : [assistant].slice(offset);\n return results;\n },\n\n async setLatest(assistantId: string, version: number): Promise<Assistant> {\n // Fake\n const item = await this.get(assistantId);\n item.version = version;\n return item;\n },\n\n async create(params: {\n assistantId?: string;\n graphId: string;\n name?: string;\n description?: string;\n metadata?: Metadata;\n config?: any;\n ifExists?: 'raise' | 'do_nothing';\n }): Promise<Assistant> {\n // ⚠️ 创建 assistant 不可用 - assistants 是从注册的图中自动生成的,不能动态创建\n // 返回假数据以通过测试\n console.warn(\n '⚠️ Creating assistants is not supported. Assistants are generated from registered graphs. Returning mock data.',\n );\n const graphExists = Object.keys(GRAPHS).includes(params.graphId);\n\n if (!graphExists) {\n if (params.ifExists === 'raise') {\n throw new Error(`Graph not found: ${params.graphId}`);\n }\n // 如果 graph 不存在,我们仍然返回假数据\n }\n\n return {\n assistant_id: params.assistantId || params.graphId,\n graph_id: params.graphId,\n name: params.name || params.graphId,\n description: params.description || '',\n metadata: params.metadata || {},\n config: params.config || {},\n version: 1,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n } as Assistant;\n },\n};\n\nexport const createEndpoint = () => {\n const getThreads = () => {\n return LangGraphGlobal.globalThreadsManager;\n };\n return {\n assistants: AssistantEndpoint,\n get threads() {\n return LangGraphGlobal.globalThreadsManager;\n },\n runs: {\n list(\n threadId: string,\n options?: {\n limit?: number;\n offset?: number;\n status?: RunStatus;\n },\n ): Promise<Run[]> {\n return getThreads().listRuns(threadId, options);\n },\n async cancel(threadId: string, runId: string, wait?: boolean, action?: CancelAction): Promise<void> {\n return await LangGraphGlobal.globalMessageQueue.cancelQueue(runId);\n },\n async *stream(threadId: string, assistantId: string, payload: StreamInputData) {\n payload.config = {\n ...(payload.config ?? {}),\n configurable: {\n ...(payload.config?.configurable ?? {}),\n graph_id: assistantId,\n thread_id: threadId,\n },\n };\n const threads = getThreads();\n for await (const data of streamState(\n threads,\n threads.createRun(threadId, assistantId, payload),\n payload,\n {\n attempt: 0,\n getGraph,\n },\n )) {\n yield data;\n }\n },\n async *joinStream(\n threadId: string,\n runId: string,\n options?:\n | {\n signal?: AbortSignal;\n cancelOnDisconnect?: boolean;\n lastEventId?: string;\n streamMode?: StreamMode | StreamMode[];\n }\n | AbortSignal,\n ): AsyncGenerator<{ id?: string; event: StreamEvent; data: any }> {\n // 处理参数兼容性\n const config = options && typeof options === 'object' && 'signal' in options ? options : {};\n const signal =\n (options instanceof AbortSignal ? options : config.signal) || new AbortController().signal;\n\n let queue: BaseStreamQueueInterface | null = null;\n let generator: AsyncGenerator<EventMessage, void, unknown> | null = null;\n\n try {\n // 获取队列实例\n queue = await LangGraphGlobal.globalMessageQueue.getQueue(runId);\n\n // 获取历史数据\n const allData = await queue.getAll();\n for (const eventMessage of allData) {\n // 检查是否被取消\n if (signal.aborted) {\n return;\n }\n\n yield {\n id: eventMessage.id,\n event: eventMessage.event as unknown as StreamEvent,\n data: eventMessage.data,\n };\n // 如果是流结束信号,停止监听\n if (\n eventMessage.event === '__stream_end__' ||\n eventMessage.event === '__stream_error__' ||\n eventMessage.event === '__stream_cancel__'\n ) {\n return;\n }\n }\n\n // 监听队列数据并转换格式\n generator = queue.onDataReceive();\n for await (const eventMessage of generator) {\n // 检查是否被取消\n if (signal.aborted) {\n break;\n }\n\n // 转换 EventMessage 为期望的格式\n const event = eventMessage.event as unknown as StreamEvent;\n const data = eventMessage.data;\n\n yield {\n id: eventMessage.id,\n event,\n data,\n };\n\n // 如果是流结束信号,停止监听\n if (\n eventMessage.event === '__stream_end__' ||\n eventMessage.event === '__stream_error__' ||\n eventMessage.event === '__stream_cancel__'\n ) {\n break;\n }\n }\n } catch (error) {\n // 如果队列不存在或其他错误,记录警告但不抛出错误\n if (!(error instanceof Error) || !error.message.includes('does not exist')) {\n console.warn('Join stream failed:', error);\n }\n } finally {\n // 清理生成器,释放资源\n if (generator) {\n try {\n await generator.return(undefined);\n } catch (e) {\n // 忽略生成器清理错误\n }\n }\n // 注意:不在这里清理队列,队列由 streamState 在运行完成时统一清理\n }\n },\n },\n };\n};\n"],"names":[],"mappings":"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"}
|
package/dist/createEndpoint.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StreamEvent } from '@langchain/core/tracers/log_stream';
|
|
2
2
|
import { Assistant, Run, StreamMode, Metadata, AssistantGraph } from '@langchain/langgraph-sdk';
|
|
3
3
|
import { AssistantSortBy, CancelAction, ILangGraphClient, RunStatus, SortOrder, StreamInputData } from './types.js';
|
|
4
|
+
import type { EventMessage } from './queue/event_message.js';
|
|
4
5
|
export { registerGraph } from './utils/getGraph.js';
|
|
5
6
|
export declare const AssistantEndpoint: ILangGraphClient['assistants'];
|
|
6
7
|
export declare const createEndpoint: () => {
|
|
@@ -50,7 +51,7 @@ export declare const createEndpoint: () => {
|
|
|
50
51
|
status?: RunStatus;
|
|
51
52
|
}): Promise<Run[]>;
|
|
52
53
|
cancel(threadId: string, runId: string, wait?: boolean, action?: CancelAction): Promise<void>;
|
|
53
|
-
stream(threadId: string, assistantId: string, payload: StreamInputData): AsyncGenerator<
|
|
54
|
+
stream(threadId: string, assistantId: string, payload: StreamInputData): AsyncGenerator<EventMessage, void, unknown>;
|
|
54
55
|
joinStream(threadId: string, runId: string, options?: {
|
|
55
56
|
signal?: AbortSignal;
|
|
56
57
|
cancelOnDisconnect?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AssistantEndpoint, c as createEndpoint } from './createEndpoint-
|
|
2
|
-
export { L as LangGraphGlobal, r as registerGraph } from './stream-
|
|
1
|
+
export { A as AssistantEndpoint, c as createEndpoint } from './createEndpoint-vMmFiMSz.js';
|
|
2
|
+
export { L as LangGraphGlobal, r as registerGraph } from './stream-jYlUzTZO.js';
|
|
3
3
|
import { entrypoint, getPreviousState, getConfig, Command } from '@langchain/langgraph';
|
|
4
4
|
import { schemaMetaRegistry } from '@langchain/langgraph/zod';
|
|
5
5
|
import { getDefaultsForSchema } from 'zod-defaults';
|
|
@@ -73,6 +73,8 @@ export interface BaseStreamQueueInterface {
|
|
|
73
73
|
cancel(): Promise<void>;
|
|
74
74
|
/** 复制队列数据 / Copy queue data */
|
|
75
75
|
copyToQueue(toId: string, ttl?: number): Promise<BaseStreamQueueInterface>;
|
|
76
|
+
/** 销毁队列,释放资源 / Destroy queue and release resources */
|
|
77
|
+
destroy?: () => Promise<void>;
|
|
76
78
|
}
|
|
77
79
|
export type QueueConstructor<Q extends BaseStreamQueueInterface> = (new (queueId: string, compressMessages: boolean, ttl?: number) => Q) & {
|
|
78
80
|
isQueueExist?: (id: string) => Promise<boolean>;
|
|
@@ -146,7 +148,7 @@ export declare class StreamQueueManager<Q extends BaseStreamQueueInterface> {
|
|
|
146
148
|
* @param id 队列 ID / Queue ID
|
|
147
149
|
* @returns 是否成功删除 / Whether successfully deleted
|
|
148
150
|
*/
|
|
149
|
-
removeQueue(id: string):
|
|
151
|
+
removeQueue(id: string): Promise<boolean>;
|
|
150
152
|
/**
|
|
151
153
|
* 获取所有队列的 ID
|
|
152
154
|
* Get all queue IDs
|
|
@@ -1,8 +1,63 @@
|
|
|
1
|
-
import { B as BaseStreamQueue, C as CancelEventMessage } from './stream-
|
|
1
|
+
import { B as BaseStreamQueue, C as CancelEventMessage } from './stream-jYlUzTZO.js';
|
|
2
2
|
import { createClient } from 'redis';
|
|
3
3
|
|
|
4
|
+
let sharedRedisClient = null;
|
|
5
|
+
let connectionRefCount = 0;
|
|
6
|
+
let connectionPromise = null;
|
|
7
|
+
let releaseTimeoutId = null;
|
|
8
|
+
async function getSharedRedisClient() {
|
|
9
|
+
if (sharedRedisClient && sharedRedisClient.isOpen) {
|
|
10
|
+
connectionRefCount++;
|
|
11
|
+
return sharedRedisClient;
|
|
12
|
+
}
|
|
13
|
+
if (connectionPromise) {
|
|
14
|
+
await connectionPromise;
|
|
15
|
+
if (sharedRedisClient) {
|
|
16
|
+
connectionRefCount++;
|
|
17
|
+
return sharedRedisClient;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
connectionPromise = (async () => {
|
|
21
|
+
const client = createClient({
|
|
22
|
+
url: process.env.REDIS_URL
|
|
23
|
+
});
|
|
24
|
+
await client.connect();
|
|
25
|
+
sharedRedisClient = client;
|
|
26
|
+
})();
|
|
27
|
+
try {
|
|
28
|
+
await connectionPromise;
|
|
29
|
+
connectionRefCount++;
|
|
30
|
+
return sharedRedisClient;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
connectionPromise = null;
|
|
33
|
+
throw error;
|
|
34
|
+
} finally {
|
|
35
|
+
connectionPromise = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function releaseRedisClient() {
|
|
39
|
+
if (connectionRefCount > 0) {
|
|
40
|
+
connectionRefCount--;
|
|
41
|
+
}
|
|
42
|
+
if (connectionRefCount <= 0 && sharedRedisClient) {
|
|
43
|
+
if (releaseTimeoutId) {
|
|
44
|
+
clearTimeout(releaseTimeoutId);
|
|
45
|
+
releaseTimeoutId = null;
|
|
46
|
+
}
|
|
47
|
+
releaseTimeoutId = setTimeout(async () => {
|
|
48
|
+
if (connectionRefCount <= 0 && sharedRedisClient) {
|
|
49
|
+
try {
|
|
50
|
+
await sharedRedisClient.quit();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
}
|
|
53
|
+
sharedRedisClient = null;
|
|
54
|
+
connectionRefCount = 0;
|
|
55
|
+
releaseTimeoutId = null;
|
|
56
|
+
}
|
|
57
|
+
}, 5e3);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
4
60
|
class RedisStreamQueue extends BaseStreamQueue {
|
|
5
|
-
// 轮询间隔(毫秒)
|
|
6
61
|
constructor(id, compressMessages = true, ttl = 300) {
|
|
7
62
|
super(id, true, ttl);
|
|
8
63
|
this.id = id;
|
|
@@ -10,16 +65,10 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
10
65
|
this.ttl = ttl;
|
|
11
66
|
this.streamKey = `stream:${this.id}`;
|
|
12
67
|
this.listKey = `queue:${this.id}`;
|
|
13
|
-
this.redis = createClient({
|
|
14
|
-
url: process.env.REDIS_URL
|
|
15
|
-
});
|
|
16
68
|
this.cancelSignal = new AbortController();
|
|
17
|
-
|
|
18
|
-
this.redis.connect();
|
|
19
|
-
}
|
|
20
|
-
this.isConnected = true;
|
|
69
|
+
this.connectionReady = this.initConnection();
|
|
21
70
|
}
|
|
22
|
-
redis;
|
|
71
|
+
redis = null;
|
|
23
72
|
streamKey;
|
|
24
73
|
listKey;
|
|
25
74
|
isConnected = false;
|
|
@@ -27,12 +76,36 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
27
76
|
lastStreamId = "0";
|
|
28
77
|
// 最后读取的 Stream ID
|
|
29
78
|
pollInterval = 100;
|
|
79
|
+
// 轮询间隔(毫秒)
|
|
80
|
+
connectionReady;
|
|
81
|
+
/**
|
|
82
|
+
* 初始化 Redis 连接(使用共享连接池)
|
|
83
|
+
*/
|
|
84
|
+
async initConnection() {
|
|
85
|
+
try {
|
|
86
|
+
this.redis = await getSharedRedisClient();
|
|
87
|
+
this.isConnected = true;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("Failed to connect to Redis:", error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 确保连接已建立
|
|
95
|
+
*/
|
|
96
|
+
async ensureConnected() {
|
|
97
|
+
if (!this.isConnected || !this.redis) {
|
|
98
|
+
await this.connectionReady;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
30
101
|
/**
|
|
31
102
|
* 推送消息到 Redis Stream 和 List
|
|
32
103
|
* - Stream: 用于实时推送(集群友好)
|
|
33
104
|
* - List: 用于 getAll() 批量获取历史数据
|
|
34
105
|
*/
|
|
35
106
|
async push(item) {
|
|
107
|
+
await this.ensureConnected();
|
|
108
|
+
if (!this.redis) throw new Error("Redis connection not available");
|
|
36
109
|
const encodedData = await this.encodeData(item);
|
|
37
110
|
const dataString = Buffer.from(encodedData).toString("base64");
|
|
38
111
|
const serializedData = Buffer.from(encodedData);
|
|
@@ -47,6 +120,11 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
47
120
|
*/
|
|
48
121
|
async *onDataReceive() {
|
|
49
122
|
let isStreamEnded = false;
|
|
123
|
+
let isCleanupDone = false;
|
|
124
|
+
await this.ensureConnected();
|
|
125
|
+
if (!this.redis) {
|
|
126
|
+
throw new Error("Redis connection not available");
|
|
127
|
+
}
|
|
50
128
|
if (this.cancelSignal.signal.aborted) {
|
|
51
129
|
return;
|
|
52
130
|
}
|
|
@@ -54,8 +132,20 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
54
132
|
isStreamEnded = true;
|
|
55
133
|
};
|
|
56
134
|
this.cancelSignal.signal.addEventListener("abort", abortHandler);
|
|
135
|
+
const cleanup = () => {
|
|
136
|
+
if (isCleanupDone) return;
|
|
137
|
+
isCleanupDone = true;
|
|
138
|
+
try {
|
|
139
|
+
this.cancelSignal.signal.removeEventListener("abort", abortHandler);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error("Error removing abort listener:", e);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
57
144
|
try {
|
|
58
145
|
while (!isStreamEnded && !this.cancelSignal.signal.aborted) {
|
|
146
|
+
if (!this.redis || !this.isConnected) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
59
149
|
const streams = await this.redis.xRead([{ key: this.streamKey, id: this.lastStreamId }], {
|
|
60
150
|
BLOCK: this.pollInterval,
|
|
61
151
|
COUNT: 10
|
|
@@ -89,13 +179,15 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
89
179
|
}
|
|
90
180
|
}
|
|
91
181
|
} finally {
|
|
92
|
-
|
|
182
|
+
cleanup();
|
|
93
183
|
}
|
|
94
184
|
}
|
|
95
185
|
/**
|
|
96
186
|
* 获取队列中的所有数据(从 List 获取历史数据)
|
|
97
187
|
*/
|
|
98
188
|
async getAll() {
|
|
189
|
+
await this.ensureConnected();
|
|
190
|
+
if (!this.redis) return [];
|
|
99
191
|
const data = await this.redis.lRange(this.listKey, 0, -1);
|
|
100
192
|
if (!data || data.length === 0) {
|
|
101
193
|
return [];
|
|
@@ -114,12 +206,20 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
114
206
|
/**
|
|
115
207
|
* 清空队列
|
|
116
208
|
*/
|
|
117
|
-
clear() {
|
|
118
|
-
if (this.isConnected) {
|
|
119
|
-
this.redis.del(this.streamKey);
|
|
120
|
-
this.redis.del(this.listKey);
|
|
209
|
+
async clear() {
|
|
210
|
+
if (this.isConnected && this.redis) {
|
|
211
|
+
await Promise.all([this.redis.del(this.streamKey), this.redis.del(this.listKey)]);
|
|
121
212
|
}
|
|
122
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* 销毁队列实例,释放 Redis 连接引用
|
|
216
|
+
*/
|
|
217
|
+
async destroy() {
|
|
218
|
+
await this.clear();
|
|
219
|
+
this.isConnected = false;
|
|
220
|
+
this.redis = null;
|
|
221
|
+
await releaseRedisClient();
|
|
222
|
+
}
|
|
123
223
|
/**
|
|
124
224
|
* 取消操作
|
|
125
225
|
*/
|
|
@@ -131,7 +231,11 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
131
231
|
* 复制队列到另一个队列
|
|
132
232
|
*/
|
|
133
233
|
async copyToQueue(toId, ttl) {
|
|
234
|
+
await this.ensureConnected();
|
|
235
|
+
if (!this.redis) throw new Error("Redis connection not available");
|
|
134
236
|
const queue = new RedisStreamQueue(toId, this.compressMessages, ttl ?? this.ttl);
|
|
237
|
+
await queue.ensureConnected();
|
|
238
|
+
if (!queue.redis) throw new Error("Target Redis connection not available");
|
|
135
239
|
await this.redis.copy(this.listKey, queue.listKey);
|
|
136
240
|
await this.redis.expire(queue.listKey, ttl ?? this.ttl);
|
|
137
241
|
const allStreamData = await this.redis.xRange(this.streamKey, "-", "+");
|
|
@@ -150,4 +254,4 @@ class RedisStreamQueue extends BaseStreamQueue {
|
|
|
150
254
|
}
|
|
151
255
|
|
|
152
256
|
export { RedisStreamQueue };
|
|
153
|
-
//# sourceMappingURL=queue-
|
|
257
|
+
//# sourceMappingURL=queue-CUe5TDP1.js.map
|