@langgraph-js/pure-graph 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/fetch/index.js +2 -2
- package/dist/adapter/nextjs/index.js +1 -1
- package/dist/{checkpoint-1sAx_j1E.js → checkpoint-C5AFBYE-.js} +77 -34
- package/dist/checkpoint-C5AFBYE-.js.map +1 -0
- package/dist/{createEndpoint-D1nfguxg.js → createEndpoint-C2KsYHDx.js} +2 -2
- package/dist/{createEndpoint-D1nfguxg.js.map → createEndpoint-C2KsYHDx.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/{queue-Cu_nO_wt.js → queue-DySatFkr.js} +2 -2
- package/dist/{queue-Cu_nO_wt.js.map → queue-DySatFkr.js.map} +1 -1
- package/dist/remote/index.js +1 -1
- package/dist/shallow-checkpoint-BEhTdp7z.js +466 -0
- package/dist/shallow-checkpoint-BEhTdp7z.js.map +1 -0
- package/dist/{sqlite-adapter-BKOLSdoL.js → sqlite-adapter-oBA95xba.js} +19 -1
- package/dist/sqlite-adapter-oBA95xba.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/sqlite/shallow-checkpoint.d.ts +59 -0
- package/dist/{stream-CsqWsCjt.js → stream-pZfO6Y-p.js} +14 -7
- package/dist/{stream-CsqWsCjt.js.map → stream-pZfO6Y-p.js.map} +1 -1
- package/package.json +1 -1
- package/dist/checkpoint-1sAx_j1E.js.map +0 -1
- package/dist/sqlite-adapter-BKOLSdoL.js.map +0 -1
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { Kysely, sql } from 'kysely';
|
|
2
|
+
import { BaseCheckpointSaver, getCheckpointId, TASKS, uuid6, copyCheckpoint, WRITES_IDX_MAP, maxChannelVersion } from '@langchain/langgraph-checkpoint';
|
|
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
|
+
}
|
|
33
|
+
function deterministicStringify(obj) {
|
|
34
|
+
if (obj === null || typeof obj !== "object") {
|
|
35
|
+
return JSON.stringify(obj);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(obj)) {
|
|
38
|
+
return JSON.stringify(obj.map((item) => deterministicStringify(item)));
|
|
39
|
+
}
|
|
40
|
+
const sortedObj = {};
|
|
41
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
42
|
+
for (const key of sortedKeys) {
|
|
43
|
+
sortedObj[key] = obj[key];
|
|
44
|
+
}
|
|
45
|
+
return JSON.stringify(sortedObj, (_, value) => {
|
|
46
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
47
|
+
const sorted = {};
|
|
48
|
+
const keys = Object.keys(value).sort();
|
|
49
|
+
for (const k of keys) {
|
|
50
|
+
sorted[k] = value[k];
|
|
51
|
+
}
|
|
52
|
+
return sorted;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
class SqliteShallowSaver extends BaseCheckpointSaver {
|
|
58
|
+
db;
|
|
59
|
+
isSetup;
|
|
60
|
+
constructor(dialect, serde) {
|
|
61
|
+
super(serde);
|
|
62
|
+
this.db = new Kysely({
|
|
63
|
+
dialect
|
|
64
|
+
});
|
|
65
|
+
this.isSetup = false;
|
|
66
|
+
}
|
|
67
|
+
static async fromConnStringAsync(connStringOrLocalPath) {
|
|
68
|
+
let saver;
|
|
69
|
+
if (globalThis.Bun) {
|
|
70
|
+
console.log("LG | Using BunWorkerDialect " + connStringOrLocalPath);
|
|
71
|
+
const { BunWorkerDialect } = await import('kysely-bun-worker');
|
|
72
|
+
saver = new SqliteShallowSaver(new BunWorkerDialect({ url: connStringOrLocalPath }));
|
|
73
|
+
} else {
|
|
74
|
+
console.log("LG | Using NodeWasmDialect");
|
|
75
|
+
const { default: SqliteDatabase } = await import('node-sqlite3-wasm');
|
|
76
|
+
const { NodeWasmDialect } = await import('kysely-wasm');
|
|
77
|
+
console.log(connStringOrLocalPath);
|
|
78
|
+
const wasm = new NodeWasmDialect({
|
|
79
|
+
database: new SqliteDatabase.Database(connStringOrLocalPath)
|
|
80
|
+
});
|
|
81
|
+
saver = new SqliteShallowSaver(wasm);
|
|
82
|
+
}
|
|
83
|
+
await saver.setup();
|
|
84
|
+
return saver;
|
|
85
|
+
}
|
|
86
|
+
async setup() {
|
|
87
|
+
if (this.isSetup) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await sql`PRAGMA busy_timeout = 5000`.execute(this.db);
|
|
91
|
+
await sql`PRAGMA journal_mode = WAL`.execute(this.db);
|
|
92
|
+
await sql`PRAGMA synchronous = NORMAL`.execute(this.db);
|
|
93
|
+
await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db);
|
|
94
|
+
await sql`
|
|
95
|
+
CREATE TABLE IF NOT EXISTS shallow_checkpoints (
|
|
96
|
+
thread_id TEXT NOT NULL,
|
|
97
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
98
|
+
checkpoint_id TEXT NOT NULL,
|
|
99
|
+
parent_checkpoint_id TEXT,
|
|
100
|
+
type TEXT,
|
|
101
|
+
checkpoint BLOB,
|
|
102
|
+
metadata BLOB,
|
|
103
|
+
checkpoint_ts INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
PRIMARY KEY (thread_id, checkpoint_ns)
|
|
105
|
+
)`.execute(this.db);
|
|
106
|
+
await sql`
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_shallow_checkpoints_ts
|
|
108
|
+
ON shallow_checkpoints(checkpoint_ts DESC)`.execute(this.db);
|
|
109
|
+
await sql`
|
|
110
|
+
CREATE TABLE IF NOT EXISTS writes (
|
|
111
|
+
thread_id TEXT NOT NULL,
|
|
112
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
113
|
+
checkpoint_id TEXT NOT NULL,
|
|
114
|
+
task_id TEXT NOT NULL,
|
|
115
|
+
idx INTEGER NOT NULL,
|
|
116
|
+
channel TEXT NOT NULL,
|
|
117
|
+
type TEXT,
|
|
118
|
+
value BLOB,
|
|
119
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
120
|
+
)`.execute(this.db);
|
|
121
|
+
this.isSetup = true;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 获取 checkpoint(便捷方法)
|
|
125
|
+
*/
|
|
126
|
+
async get(config) {
|
|
127
|
+
const tuple = await this.getTuple(config);
|
|
128
|
+
return tuple?.checkpoint;
|
|
129
|
+
}
|
|
130
|
+
async getTuple(config) {
|
|
131
|
+
await this.setup();
|
|
132
|
+
const threadId = config.configurable?.thread_id;
|
|
133
|
+
const checkpointNs = config.configurable?.checkpoint_ns ?? "";
|
|
134
|
+
const requestedCheckpointId = getCheckpointId(config);
|
|
135
|
+
if (threadId === void 0) {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
let query = this.db.selectFrom("shallow_checkpoints").select([
|
|
139
|
+
"thread_id",
|
|
140
|
+
"checkpoint_ns",
|
|
141
|
+
"checkpoint_id",
|
|
142
|
+
"parent_checkpoint_id",
|
|
143
|
+
"type",
|
|
144
|
+
"checkpoint",
|
|
145
|
+
"metadata",
|
|
146
|
+
"checkpoint_ts",
|
|
147
|
+
sql`(
|
|
148
|
+
SELECT json_group_array(
|
|
149
|
+
json_object(
|
|
150
|
+
'task_id', pw.task_id,
|
|
151
|
+
'channel', pw.channel,
|
|
152
|
+
'type', pw.type,
|
|
153
|
+
'value', CAST(pw.value AS TEXT)
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
FROM writes as pw
|
|
157
|
+
WHERE pw.thread_id = shallow_checkpoints.thread_id
|
|
158
|
+
AND pw.checkpoint_ns = shallow_checkpoints.checkpoint_ns
|
|
159
|
+
AND pw.checkpoint_id = shallow_checkpoints.checkpoint_id
|
|
160
|
+
)`.as("pending_writes"),
|
|
161
|
+
sql`(
|
|
162
|
+
SELECT json_group_array(
|
|
163
|
+
json_object(
|
|
164
|
+
'type', ps.type,
|
|
165
|
+
'value', CAST(ps.value AS TEXT)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
FROM writes as ps
|
|
169
|
+
WHERE ps.thread_id = shallow_checkpoints.thread_id
|
|
170
|
+
AND ps.checkpoint_ns = shallow_checkpoints.checkpoint_ns
|
|
171
|
+
AND ps.checkpoint_id = shallow_checkpoints.parent_checkpoint_id
|
|
172
|
+
AND ps.channel = ${TASKS}
|
|
173
|
+
ORDER BY ps.idx
|
|
174
|
+
)`.as("pending_sends")
|
|
175
|
+
]).where("thread_id", "=", threadId).where("checkpoint_ns", "=", checkpointNs);
|
|
176
|
+
if (requestedCheckpointId) {
|
|
177
|
+
query = query.where("checkpoint_id", "=", requestedCheckpointId);
|
|
178
|
+
}
|
|
179
|
+
const row = await query.executeTakeFirst();
|
|
180
|
+
if (!row) return void 0;
|
|
181
|
+
if (requestedCheckpointId && row.checkpoint_id !== requestedCheckpointId) {
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
const pendingWrites = await Promise.all(
|
|
185
|
+
JSON.parse(row.pending_writes || "[]").map(async (write) => {
|
|
186
|
+
return [
|
|
187
|
+
write.task_id,
|
|
188
|
+
write.channel,
|
|
189
|
+
await this.serde.loadsTyped(write.type ?? "json", write.value ?? "")
|
|
190
|
+
];
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
const checkpoint = await this.serde.loadsTyped(
|
|
194
|
+
row.type ?? "json",
|
|
195
|
+
new TextDecoder().decode(row.checkpoint)
|
|
196
|
+
);
|
|
197
|
+
if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {
|
|
198
|
+
await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);
|
|
199
|
+
}
|
|
200
|
+
const finalConfig = {
|
|
201
|
+
configurable: {
|
|
202
|
+
thread_id: row.thread_id,
|
|
203
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
204
|
+
checkpoint_id: row.checkpoint_id
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
checkpoint,
|
|
209
|
+
config: finalConfig,
|
|
210
|
+
metadata: await this.serde.loadsTyped(
|
|
211
|
+
row.type ?? "json",
|
|
212
|
+
new TextDecoder().decode(row.metadata)
|
|
213
|
+
),
|
|
214
|
+
parentConfig: row.parent_checkpoint_id ? {
|
|
215
|
+
configurable: {
|
|
216
|
+
thread_id: row.thread_id,
|
|
217
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
218
|
+
checkpoint_id: row.parent_checkpoint_id
|
|
219
|
+
}
|
|
220
|
+
} : void 0,
|
|
221
|
+
pendingWrites
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async *list(config, options) {
|
|
225
|
+
await this.setup();
|
|
226
|
+
const { limit, before, filter } = options ?? {};
|
|
227
|
+
const threadId = config.configurable?.thread_id;
|
|
228
|
+
const checkpointNs = config.configurable?.checkpoint_ns;
|
|
229
|
+
let query = this.db.selectFrom("shallow_checkpoints").select([
|
|
230
|
+
"thread_id",
|
|
231
|
+
"checkpoint_ns",
|
|
232
|
+
"checkpoint_id",
|
|
233
|
+
"parent_checkpoint_id",
|
|
234
|
+
"type",
|
|
235
|
+
"checkpoint",
|
|
236
|
+
"metadata",
|
|
237
|
+
"checkpoint_ts",
|
|
238
|
+
sql`(
|
|
239
|
+
SELECT json_group_array(
|
|
240
|
+
json_object(
|
|
241
|
+
'task_id', pw.task_id,
|
|
242
|
+
'channel', pw.channel,
|
|
243
|
+
'type', pw.type,
|
|
244
|
+
'value', CAST(pw.value AS TEXT)
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
FROM writes as pw
|
|
248
|
+
WHERE pw.thread_id = shallow_checkpoints.thread_id
|
|
249
|
+
AND pw.checkpoint_ns = shallow_checkpoints.checkpoint_ns
|
|
250
|
+
AND pw.checkpoint_id = shallow_checkpoints.checkpoint_id
|
|
251
|
+
)`.as("pending_writes")
|
|
252
|
+
]);
|
|
253
|
+
if (threadId) {
|
|
254
|
+
query = query.where("thread_id", "=", threadId);
|
|
255
|
+
}
|
|
256
|
+
if (checkpointNs !== void 0 && checkpointNs !== null) {
|
|
257
|
+
query = query.where("checkpoint_ns", "=", checkpointNs);
|
|
258
|
+
}
|
|
259
|
+
if (before?.configurable?.checkpoint_id !== void 0) {
|
|
260
|
+
query = query.where("checkpoint_id", "<", before.configurable.checkpoint_id);
|
|
261
|
+
}
|
|
262
|
+
query = query.orderBy("checkpoint_ts", "desc");
|
|
263
|
+
const rows = await query.execute();
|
|
264
|
+
let count = 0;
|
|
265
|
+
for (const row of rows) {
|
|
266
|
+
const metadata = await this.serde.loadsTyped(
|
|
267
|
+
row.type ?? "json",
|
|
268
|
+
new TextDecoder().decode(row.metadata)
|
|
269
|
+
);
|
|
270
|
+
if (filter && !this._checkMetadataFilterMatch(metadata, filter)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (limit !== void 0 && count >= limit) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const pendingWrites = await Promise.all(
|
|
277
|
+
JSON.parse(row.pending_writes || "[]").map(async (write) => {
|
|
278
|
+
return [
|
|
279
|
+
write.task_id,
|
|
280
|
+
write.channel,
|
|
281
|
+
await this.serde.loadsTyped(write.type ?? "json", write.value ?? "")
|
|
282
|
+
];
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
const checkpoint = await this.serde.loadsTyped(
|
|
286
|
+
row.type ?? "json",
|
|
287
|
+
new TextDecoder().decode(row.checkpoint)
|
|
288
|
+
);
|
|
289
|
+
if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {
|
|
290
|
+
await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);
|
|
291
|
+
}
|
|
292
|
+
count++;
|
|
293
|
+
yield {
|
|
294
|
+
config: {
|
|
295
|
+
configurable: {
|
|
296
|
+
thread_id: row.thread_id,
|
|
297
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
298
|
+
checkpoint_id: row.checkpoint_id
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
checkpoint,
|
|
302
|
+
metadata,
|
|
303
|
+
parentConfig: row.parent_checkpoint_id ? {
|
|
304
|
+
configurable: {
|
|
305
|
+
thread_id: row.thread_id,
|
|
306
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
307
|
+
checkpoint_id: row.parent_checkpoint_id
|
|
308
|
+
}
|
|
309
|
+
} : void 0,
|
|
310
|
+
pendingWrites
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check metadata filter matches (with deep comparison support)
|
|
316
|
+
* Matches ShallowMemorySaver behavior
|
|
317
|
+
*/
|
|
318
|
+
_checkMetadataFilterMatch(metadata, filter) {
|
|
319
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
320
|
+
const metadataValue = metadata?.[key];
|
|
321
|
+
if (value === null) {
|
|
322
|
+
if (!(key in (metadata || {})) || metadataValue !== null) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
326
|
+
if (typeof metadataValue !== "object" || metadataValue === null) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (deterministicStringify(value) !== deterministicStringify(metadataValue)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
} else if (metadataValue !== value) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
async put(config, checkpoint, metadata, _newVersions) {
|
|
339
|
+
await this.setup();
|
|
340
|
+
const threadId = config.configurable?.thread_id;
|
|
341
|
+
const checkpointNs = config.configurable?.checkpoint_ns ?? "";
|
|
342
|
+
const parentCheckpointId = config.configurable?.checkpoint_id;
|
|
343
|
+
if (!threadId) {
|
|
344
|
+
throw new Error("thread_id is required");
|
|
345
|
+
}
|
|
346
|
+
const checkpointId = checkpoint.id || uuid6(0);
|
|
347
|
+
const preparedCheckpoint = copyCheckpoint(checkpoint);
|
|
348
|
+
const [[type1, serializedCheckpoint], [type2, serializedMetadata]] = await Promise.all([
|
|
349
|
+
this.serde.dumpsTyped(preparedCheckpoint),
|
|
350
|
+
this.serde.dumpsTyped(metadata)
|
|
351
|
+
]);
|
|
352
|
+
if (type1 !== type2) {
|
|
353
|
+
throw new Error("Failed to serialize checkpoint and metadata to the same type.");
|
|
354
|
+
}
|
|
355
|
+
const threadId_ = threadId;
|
|
356
|
+
const checkpointNs_ = checkpointNs;
|
|
357
|
+
const checkpointId_ = checkpointId;
|
|
358
|
+
await withRetry(
|
|
359
|
+
async () => {
|
|
360
|
+
await this.db.transaction().execute(async (trx) => {
|
|
361
|
+
const oldCheckpoint = await trx.selectFrom("shallow_checkpoints").select(["checkpoint_id"]).where("thread_id", "=", threadId_).where("checkpoint_ns", "=", checkpointNs_).executeTakeFirst();
|
|
362
|
+
if (oldCheckpoint && oldCheckpoint.checkpoint_id !== checkpointId_) {
|
|
363
|
+
await trx.deleteFrom("writes").where("thread_id", "=", threadId_).where("checkpoint_ns", "=", checkpointNs_).where("checkpoint_id", "=", oldCheckpoint.checkpoint_id).execute();
|
|
364
|
+
}
|
|
365
|
+
await trx.insertInto("shallow_checkpoints").values({
|
|
366
|
+
thread_id: threadId_,
|
|
367
|
+
checkpoint_ns: checkpointNs_,
|
|
368
|
+
checkpoint_id: checkpointId_,
|
|
369
|
+
parent_checkpoint_id: parentCheckpointId ?? null,
|
|
370
|
+
type: type1,
|
|
371
|
+
checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),
|
|
372
|
+
metadata: new Uint8Array(Buffer.from(serializedMetadata)),
|
|
373
|
+
checkpoint_ts: Date.now()
|
|
374
|
+
}).onConflict(
|
|
375
|
+
(oc) => oc.columns(["thread_id", "checkpoint_ns"]).doUpdateSet({
|
|
376
|
+
checkpoint_id: checkpointId_,
|
|
377
|
+
parent_checkpoint_id: parentCheckpointId ?? null,
|
|
378
|
+
type: type1,
|
|
379
|
+
checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),
|
|
380
|
+
metadata: new Uint8Array(Buffer.from(serializedMetadata)),
|
|
381
|
+
checkpoint_ts: Date.now()
|
|
382
|
+
})
|
|
383
|
+
).execute();
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
`put(${threadId}/${checkpointId})`
|
|
387
|
+
);
|
|
388
|
+
return {
|
|
389
|
+
configurable: {
|
|
390
|
+
thread_id: threadId,
|
|
391
|
+
checkpoint_ns: checkpointNs,
|
|
392
|
+
checkpoint_id: checkpointId
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
async putWrites(config, writes, taskId) {
|
|
397
|
+
await this.setup();
|
|
398
|
+
const threadId = config.configurable?.thread_id;
|
|
399
|
+
const checkpointNs = config.configurable?.checkpoint_ns ?? "";
|
|
400
|
+
const checkpointId = config.configurable?.checkpoint_id;
|
|
401
|
+
if (!threadId || !checkpointId) {
|
|
402
|
+
throw new Error("thread_id and checkpoint_id are required");
|
|
403
|
+
}
|
|
404
|
+
const values = await Promise.all(
|
|
405
|
+
writes.map(async (write, idx) => {
|
|
406
|
+
const [type, serializedWrite] = await this.serde.dumpsTyped(write[1]);
|
|
407
|
+
return {
|
|
408
|
+
thread_id: threadId,
|
|
409
|
+
checkpoint_ns: checkpointNs,
|
|
410
|
+
checkpoint_id: checkpointId,
|
|
411
|
+
task_id: taskId,
|
|
412
|
+
idx: WRITES_IDX_MAP[write[0]] ?? idx,
|
|
413
|
+
channel: write[0],
|
|
414
|
+
type,
|
|
415
|
+
value: new Uint8Array(Buffer.from(serializedWrite))
|
|
416
|
+
};
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
if (values.length === 0) return;
|
|
420
|
+
await withRetry(
|
|
421
|
+
async () => {
|
|
422
|
+
await this.db.transaction().execute(async (trx) => {
|
|
423
|
+
await trx.deleteFrom("writes").where("thread_id", "=", threadId).where("checkpoint_ns", "=", checkpointNs).where("checkpoint_id", "=", checkpointId).where("task_id", "=", taskId).execute();
|
|
424
|
+
for (const value of values) {
|
|
425
|
+
await trx.insertInto("writes").values(value).execute();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
`putWrites(${threadId}/${checkpointId}/${taskId})`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
async deleteThread(threadId) {
|
|
433
|
+
await withRetry(
|
|
434
|
+
async () => {
|
|
435
|
+
await this.db.transaction().execute(async (trx) => {
|
|
436
|
+
await trx.deleteFrom("shallow_checkpoints").where("thread_id", "=", threadId).execute();
|
|
437
|
+
await trx.deleteFrom("writes").where("thread_id", "=", threadId).execute();
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
`deleteThread(${threadId})`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
async migratePendingSends(checkpoint, threadId, parentCheckpointId) {
|
|
444
|
+
const result = await this.db.selectFrom("writes as ps").select([
|
|
445
|
+
"ps.checkpoint_id",
|
|
446
|
+
sql`json_group_array(
|
|
447
|
+
json_object(
|
|
448
|
+
'type', ps.type,
|
|
449
|
+
'value', CAST(ps.value AS TEXT)
|
|
450
|
+
)
|
|
451
|
+
)`.as("pending_sends")
|
|
452
|
+
]).where("ps.thread_id", "=", threadId).where("ps.checkpoint_id", "=", parentCheckpointId).where("ps.channel", "=", TASKS).orderBy("ps.idx").executeTakeFirst();
|
|
453
|
+
if (!result) return;
|
|
454
|
+
const mutableCheckpoint = checkpoint;
|
|
455
|
+
mutableCheckpoint.channel_values ??= {};
|
|
456
|
+
mutableCheckpoint.channel_values[TASKS] = await Promise.all(
|
|
457
|
+
JSON.parse(result.pending_sends || "[]").map(
|
|
458
|
+
({ type, value }) => this.serde.loadsTyped(type, value)
|
|
459
|
+
)
|
|
460
|
+
);
|
|
461
|
+
mutableCheckpoint.channel_versions[TASKS] = Object.keys(checkpoint.channel_versions).length > 0 ? maxChannelVersion(...Object.values(checkpoint.channel_versions)) : this.getNextVersion(void 0);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export { SqliteShallowSaver };
|
|
466
|
+
//# sourceMappingURL=shallow-checkpoint-BEhTdp7z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shallow-checkpoint-BEhTdp7z.js","sources":["../src/storage/sqlite/shallow-checkpoint.ts"],"sourcesContent":["import { Dialect, Kysely, 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 getCheckpointId,\n WRITES_IDX_MAP,\n uuid6,\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 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/**\n * Helper function for deterministic object comparison\n * Used for deep metadata filtering\n */\nfunction deterministicStringify(obj: any): string {\n if (obj === null || typeof obj !== 'object') {\n return JSON.stringify(obj);\n }\n if (Array.isArray(obj)) {\n return JSON.stringify(obj.map((item) => deterministicStringify(item)));\n }\n const sortedObj: Record<string, any> = {};\n const sortedKeys = Object.keys(obj).sort();\n for (const key of sortedKeys) {\n sortedObj[key] = obj[key];\n }\n return JSON.stringify(sortedObj, (_, value) => {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, any> = {};\n const keys = Object.keys(value).sort();\n for (const k of keys) {\n sorted[k] = value[k];\n }\n return sorted;\n }\n return value;\n });\n}\n\n// Kysely 数据库表类型定义 - 浅层模式\ninterface ShallowCheckpointsTable {\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 checkpoint_ts: number; // 时间戳用于排序\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 ShallowCheckpointDatabase {\n shallow_checkpoints: ShallowCheckpointsTable;\n writes: WritesTable;\n}\n\n// Valid metadata keys for filtering\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\nconst validCheckpointMetadataKeys = validateKeys<CheckpointMetadata, typeof checkpointMetadataKeys>(\n checkpointMetadataKeys,\n);\n\n/**\n * SqliteShallowSaver - SQLite 浅层检查点存储器\n *\n * 特性:\n * - 每个 thread_id + checkpoint_ns 组合只保留最新的 checkpoint\n * - 新 checkpoint 写入时自动清理旧 checkpoint 的 writes\n * - 使用 checkpoint_ts 时间戳排序\n * - 大幅减少存储数据量\n */\nexport class SqliteShallowSaver extends BaseCheckpointSaver {\n db: Kysely<ShallowCheckpointDatabase>;\n protected isSetup: boolean;\n\n constructor(dialect: Dialect, serde?: SerializerProtocol) {\n super(serde);\n this.db = new Kysely<ShallowCheckpointDatabase>({\n dialect,\n });\n this.isSetup = false;\n }\n\n static async fromConnStringAsync(connStringOrLocalPath: string): Promise<SqliteShallowSaver> {\n let saver: SqliteShallowSaver;\n /** @ts-ignore */\n if (globalThis.Bun) {\n console.log('LG | Using BunWorkerDialect ' + connStringOrLocalPath);\n const { BunWorkerDialect } = await import('kysely-bun-worker');\n saver = new SqliteShallowSaver(new BunWorkerDialect({ 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 SqliteShallowSaver(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 秒\n await sql`PRAGMA busy_timeout = 5000`.execute(this.db as any);\n\n // WAL 模式 - 允许读写并发\n await sql`PRAGMA journal_mode = WAL`.execute(this.db as any);\n\n // NORMAL 模式 - 平衡数据安全与性能\n await sql`PRAGMA synchronous = NORMAL`.execute(this.db as any);\n\n // WAL 自动检查点\n await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db as any);\n\n // 创建浅层 checkpoints 表 - 使用复合主键 (thread_id, checkpoint_ns)\n await sql`\nCREATE TABLE IF NOT EXISTS shallow_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 checkpoint_ts INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (thread_id, checkpoint_ns)\n)`.execute(this.db as any);\n\n // 创建 checkpoint_ts 索引用于排序\n await sql`\nCREATE INDEX IF NOT EXISTS idx_shallow_checkpoints_ts \nON shallow_checkpoints(checkpoint_ts DESC)`.execute(this.db as any);\n\n // 创建 writes 表\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 as any);\n\n this.isSetup = true;\n }\n\n /**\n * 获取 checkpoint(便捷方法)\n */\n async get(config: RunnableConfig): Promise<Checkpoint | undefined> {\n const tuple = await this.getTuple(config);\n return tuple?.checkpoint;\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n await this.setup();\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? '';\n const requestedCheckpointId = getCheckpointId(config);\n\n if (threadId === undefined) {\n return undefined;\n }\n\n let query = this.db\n .selectFrom('shallow_checkpoints')\n .select([\n 'thread_id',\n 'checkpoint_ns',\n 'checkpoint_id',\n 'parent_checkpoint_id',\n 'type',\n 'checkpoint',\n 'metadata',\n 'checkpoint_ts',\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 = shallow_checkpoints.thread_id\n AND pw.checkpoint_ns = shallow_checkpoints.checkpoint_ns\n AND pw.checkpoint_id = shallow_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 = shallow_checkpoints.thread_id\n AND ps.checkpoint_ns = shallow_checkpoints.checkpoint_ns\n AND ps.checkpoint_id = shallow_checkpoints.parent_checkpoint_id\n AND ps.channel = ${TASKS}\n ORDER BY ps.idx\n )`.as('pending_sends'),\n ])\n .where('thread_id', '=', threadId)\n .where('checkpoint_ns', '=', checkpointNs);\n\n // 如果指定了 checkpoint_id,需要验证是否匹配\n if (requestedCheckpointId) {\n query = query.where('checkpoint_id', '=', requestedCheckpointId);\n }\n\n const row = await query.executeTakeFirst();\n if (!row) return undefined;\n\n // 如果请求了特定的 checkpoint_id 但不匹配(在 shallow 模式下只有一条记录)\n if (requestedCheckpointId && row.checkpoint_id !== requestedCheckpointId) {\n return undefined;\n }\n\n // 反序列化 pending writes\n const pendingWrites = await Promise.all(\n (JSON.parse(row.pending_writes || '[]') as Array<{\n task_id: string;\n channel: string;\n type: string;\n value: string;\n }>).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 // 反序列化 checkpoint\n const checkpoint = (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.checkpoint),\n )) as Checkpoint;\n\n // 处理 v < 4 迁移\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 const finalConfig: RunnableConfig = {\n configurable: {\n thread_id: row.thread_id,\n checkpoint_ns: row.checkpoint_ns,\n checkpoint_id: row.checkpoint_id,\n },\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: row.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 await this.setup();\n const { limit, before, filter } = options ?? {};\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns;\n\n let query = this.db.selectFrom('shallow_checkpoints').select([\n 'thread_id',\n 'checkpoint_ns',\n 'checkpoint_id',\n 'parent_checkpoint_id',\n 'type',\n 'checkpoint',\n 'metadata',\n 'checkpoint_ts',\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 = shallow_checkpoints.thread_id\n AND pw.checkpoint_ns = shallow_checkpoints.checkpoint_ns\n AND pw.checkpoint_id = shallow_checkpoints.checkpoint_id\n )`.as('pending_writes'),\n ]);\n\n if (threadId) {\n query = query.where('thread_id', '=', threadId);\n }\n\n if (checkpointNs !== undefined && checkpointNs !== null) {\n query = query.where('checkpoint_ns', '=', checkpointNs);\n }\n\n if (before?.configurable?.checkpoint_id !== undefined) {\n query = query.where('checkpoint_id', '<', before.configurable.checkpoint_id);\n }\n\n // 按时间戳降序(最新的在前)\n query = query.orderBy('checkpoint_ts', 'desc');\n\n const rows = await query.execute();\n let count = 0;\n\n for (const row of rows) {\n // 先反序列化 metadata 以便在应用层过滤\n const metadata = (await this.serde.loadsTyped(\n row.type ?? 'json',\n new TextDecoder().decode(row.metadata),\n )) as CheckpointMetadata;\n\n // 应用层 metadata 过滤(与 ShallowMemorySaver 保持一致)\n if (filter && !this._checkMetadataFilterMatch(metadata, filter as CheckpointMetadata)) {\n continue;\n }\n\n // 应用 limit\n if (limit !== undefined && count >= limit) {\n return;\n }\n\n const pendingWrites = await Promise.all(\n (JSON.parse(row.pending_writes || '[]') as Array<{\n task_id: string;\n channel: string;\n type: string;\n value: string;\n }>).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 count++;\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,\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 /**\n * Check metadata filter matches (with deep comparison support)\n * Matches ShallowMemorySaver behavior\n */\n private _checkMetadataFilterMatch(metadata: any, filter: CheckpointMetadata): boolean {\n for (const [key, value] of Object.entries(filter)) {\n const metadataValue = metadata?.[key];\n\n if (value === null) {\n // For null filter value, check if key doesn't exist or value is null\n if (!(key in (metadata || {})) || metadataValue !== null) {\n return false;\n }\n } else if (typeof value === 'object' && !Array.isArray(value)) {\n // Deep comparison for objects with deterministic key ordering\n if (typeof metadataValue !== 'object' || metadataValue === null) {\n return false;\n }\n if (deterministicStringify(value) !== deterministicStringify(metadataValue)) {\n return false;\n }\n } else if (metadataValue !== value) {\n return false;\n }\n }\n return true;\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n _newVersions?: Record<string, string | number>,\n ): Promise<RunnableConfig> {\n await this.setup();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? '';\n const parentCheckpointId = config.configurable?.checkpoint_id;\n\n if (!threadId) {\n throw new Error('thread_id is required');\n }\n\n // 使用 checkpoint.id 或生成新的\n const checkpointId = checkpoint.id || uuid6(0);\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 serialize checkpoint and metadata to the same type.');\n }\n\n const threadId_ = threadId;\n const checkpointNs_ = checkpointNs;\n const checkpointId_ = checkpointId;\n\n // 带重试的数据库操作\n await withRetry(\n async () => {\n await this.db.transaction().execute(async (trx) => {\n // 获取旧的 checkpoint_id 用于清理 writes\n const oldCheckpoint = await trx\n .selectFrom('shallow_checkpoints')\n .select(['checkpoint_id'])\n .where('thread_id', '=', threadId_)\n .where('checkpoint_ns', '=', checkpointNs_)\n .executeTakeFirst();\n\n // 如果存在旧的 checkpoint 且 id 不同,清理旧的 writes\n if (oldCheckpoint && oldCheckpoint.checkpoint_id !== checkpointId_) {\n await trx\n .deleteFrom('writes')\n .where('thread_id', '=', threadId_)\n .where('checkpoint_ns', '=', checkpointNs_)\n .where('checkpoint_id', '=', oldCheckpoint.checkpoint_id)\n .execute();\n }\n\n // 使用 INSERT OR REPLACE (UPSERT) 覆盖写入\n await trx\n .insertInto('shallow_checkpoints')\n .values({\n thread_id: threadId_,\n checkpoint_ns: checkpointNs_,\n checkpoint_id: checkpointId_,\n parent_checkpoint_id: parentCheckpointId ?? null,\n type: type1,\n checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),\n metadata: new Uint8Array(Buffer.from(serializedMetadata)),\n checkpoint_ts: Date.now(),\n })\n .onConflict((oc) =>\n oc.columns(['thread_id', 'checkpoint_ns']).doUpdateSet({\n checkpoint_id: checkpointId_,\n parent_checkpoint_id: parentCheckpointId ?? null,\n type: type1,\n checkpoint: new Uint8Array(Buffer.from(serializedCheckpoint)),\n metadata: new Uint8Array(Buffer.from(serializedMetadata)),\n checkpoint_ts: Date.now(),\n }),\n )\n .execute();\n });\n },\n `put(${threadId}/${checkpointId})`,\n );\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n },\n };\n }\n\n async putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void> {\n await this.setup();\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? '';\n const checkpointId = config.configurable?.checkpoint_id;\n\n if (!threadId || !checkpointId) {\n throw new Error('thread_id and checkpoint_id are required');\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: threadId,\n checkpoint_ns: checkpointNs,\n checkpoint_id: checkpointId,\n task_id: taskId,\n idx: WRITES_IDX_MAP[write[0]] ?? 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 // 带重试的批量插入\n await withRetry(\n async () => {\n await this.db.transaction().execute(async (trx) => {\n // 先删除已存在的记录\n await trx\n .deleteFrom('writes')\n .where('thread_id', '=', threadId)\n .where('checkpoint_ns', '=', checkpointNs)\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): Promise<void> {\n await withRetry(\n async () => {\n await this.db.transaction().execute(async (trx) => {\n await trx.deleteFrom('shallow_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 mutableCheckpoint.channel_values ??= {};\n mutableCheckpoint.channel_values[TASKS] = await Promise.all(\n JSON.parse(result.pending_sends || '[]').map(({ type, value }: { type: string; value: string }) =>\n this.serde.loadsTyped(type, value),\n ),\n );\n\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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"}
|
|
@@ -2,9 +2,26 @@ import { sql } from 'kysely';
|
|
|
2
2
|
|
|
3
3
|
class SQLiteAdapter {
|
|
4
4
|
db;
|
|
5
|
+
pragmaPromise = null;
|
|
5
6
|
constructor(database) {
|
|
6
7
|
this.db = database;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* 设置 SQLite PRAGMA 配置,解决锁问题
|
|
11
|
+
* 使用 Promise 缓存确保并发安全且只执行一次
|
|
12
|
+
*/
|
|
13
|
+
ensurePragma() {
|
|
14
|
+
if (!this.pragmaPromise) {
|
|
15
|
+
this.pragmaPromise = this.doEnsurePragma();
|
|
16
|
+
}
|
|
17
|
+
return this.pragmaPromise;
|
|
18
|
+
}
|
|
19
|
+
async doEnsurePragma() {
|
|
20
|
+
await sql`PRAGMA busy_timeout = 5000`.execute(this.db);
|
|
21
|
+
await sql`PRAGMA journal_mode = WAL`.execute(this.db);
|
|
22
|
+
await sql`PRAGMA synchronous = NORMAL`.execute(this.db);
|
|
23
|
+
await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db);
|
|
24
|
+
}
|
|
8
25
|
dateToDb(date) {
|
|
9
26
|
return date.toISOString();
|
|
10
27
|
}
|
|
@@ -52,6 +69,7 @@ class SQLiteAdapter {
|
|
|
52
69
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
53
70
|
}
|
|
54
71
|
async createTables(db) {
|
|
72
|
+
await this.ensurePragma();
|
|
55
73
|
await sql`
|
|
56
74
|
CREATE TABLE IF NOT EXISTS threads (
|
|
57
75
|
thread_id TEXT PRIMARY KEY,
|
|
@@ -87,4 +105,4 @@ class SQLiteAdapter {
|
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
export { SQLiteAdapter };
|
|
90
|
-
//# sourceMappingURL=sqlite-adapter-
|
|
108
|
+
//# sourceMappingURL=sqlite-adapter-oBA95xba.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-adapter-oBA95xba.js","sources":["../src/storage/kysely/sqlite-adapter.ts"],"sourcesContent":["import { Kysely, sql, SqlBool, Expression, SqliteDialect } from 'kysely';\nimport { DatabaseAdapter } from './adapter';\nimport { Database } from './types';\n\n/**\n * SQLite 适配器\n * - 时间类型:TEXT (ISO 8601 字符串)\n * - JSON 类型:TEXT\n * - JSON 查询:使用 json_extract 函数\n */\nexport class SQLiteAdapter implements DatabaseAdapter {\n db: Kysely<Database>;\n private pragmaPromise: Promise<void> | null = null;\n\n constructor(database: Kysely<any>) {\n this.db = database;\n }\n\n /**\n * 设置 SQLite PRAGMA 配置,解决锁问题\n * 使用 Promise 缓存确保并发安全且只执行一次\n */\n private ensurePragma(): Promise<void> {\n if (!this.pragmaPromise) {\n this.pragmaPromise = this.doEnsurePragma();\n }\n return this.pragmaPromise;\n }\n\n private async doEnsurePragma(): Promise<void> {\n // 锁等待超时 5 秒\n await sql`PRAGMA busy_timeout = 5000`.execute(this.db);\n // WAL 模式 - 读写并发\n await sql`PRAGMA journal_mode = WAL`.execute(this.db);\n // 平衡安全与性能\n await sql`PRAGMA synchronous = NORMAL`.execute(this.db);\n // 自动清理 WAL\n await sql`PRAGMA wal_autocheckpoint = 1000`.execute(this.db);\n }\n dateToDb(date: Date): string {\n // SQLite 存储为 ISO 8601 字符串\n return date.toISOString();\n }\n\n dbToDate(dbValue: any): Date {\n // SQLite 返回字符串,需要转换为 Date\n return new Date(dbValue);\n }\n\n jsonToDb(obj: any): string {\n // SQLite 存储为 JSON 字符串\n return JSON.stringify(obj);\n }\n\n dbToJson(dbValue: any): any {\n // SQLite 返回字符串,需要解析\n if (typeof dbValue === 'string') {\n try {\n return JSON.parse(dbValue);\n } catch {\n return dbValue;\n }\n }\n return dbValue;\n }\n\n buildJsonQuery(\n db: Kysely<Database>,\n field: 'metadata' | 'interrupts',\n key: string,\n value: any,\n ): Expression<SqlBool> {\n // SQLite json_extract 的行为:\n // - 字符串: 返回不带引号的文本\n // - 数字: 返回数字\n // - 布尔值: 返回 1/0\n // - null: 返回 NULL\n // 所以比较时需要根据类型处理\n\n // 构建 JSON 路径,包含特殊字符的键需要用双引号括起来\n // 例如: user.name -> \"$.\"user.name\"\"\n const escapedKey = key.includes('-') || key.includes('.') || key.includes(' ')\n ? `\"${key}\"`\n : key;\n const jsonPath = `\\$.${escapedKey}`;\n\n let compareValue: any;\n if (typeof value === 'string') {\n // 字符串: json_extract 返回不带引号的字符串,直接比较\n compareValue = value;\n } else if (typeof value === 'number') {\n // 数字: json_extract 返回数字\n compareValue = value;\n } else if (typeof value === 'boolean') {\n // 布尔值: json_extract 返回 1/0\n compareValue = value ? 1 : 0;\n } else if (value === null) {\n // null: json_extract 返回 NULL\n compareValue = null;\n } else {\n // 其他类型(对象、数组): 使用 JSON 字符串\n compareValue = JSON.stringify(value);\n }\n\n // 构建 NULL 处理条件\n // 注意:SQLite 中 json_extract 对键不存在和值为 null 都返回 NULL\n // 我们需要检查 JSON 字符串中是否包含该键来区分这两种情况\n if (value === null) {\n // 检查键存在且值为 null\n // 使用 json_extract IS NULL 并结合 JSON 字符串包含该键\n const keyPattern = `\"${key}\":null`;\n return sql<boolean>`\n json_extract(${sql.ref(field)}, ${sql.lit(jsonPath)}) IS NULL\n AND ${sql.ref(field)} LIKE ${sql.lit(`%${keyPattern}%`)}\n `;\n }\n\n // 构建普通比较条件\n return sql<boolean>`json_extract(${sql.ref(field)}, ${sql.lit(jsonPath)}) = ${sql.lit(compareValue)}`;\n }\n\n now(): string {\n return new Date().toISOString();\n }\n\n async createTables(db: Kysely<Database>): Promise<void> {\n // 先设置 PRAGMA\n await this.ensurePragma();\n\n // 创建 threads 表\n await sql`\n CREATE TABLE IF NOT EXISTS threads (\n thread_id TEXT PRIMARY KEY,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n metadata TEXT NOT NULL DEFAULT '{}',\n status TEXT NOT NULL DEFAULT 'idle',\n \"values\" TEXT,\n interrupts TEXT NOT NULL DEFAULT '{}'\n )\n `.execute(db);\n\n // 创建 runs 表\n await sql`\n CREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n assistant_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n metadata TEXT NOT NULL DEFAULT '{}',\n multitask_strategy TEXT NOT NULL DEFAULT 'reject',\n FOREIGN KEY (thread_id) REFERENCES threads(thread_id) ON DELETE CASCADE\n )\n `.execute(db);\n }\n\n async createIndexes(db: Kysely<Database>): Promise<void> {\n await sql`CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status)`.execute(db);\n await sql`CREATE INDEX IF NOT EXISTS idx_threads_created_at ON threads(created_at)`.execute(db);\n await sql`CREATE INDEX IF NOT EXISTS idx_threads_updated_at ON threads(updated_at)`.execute(db);\n await sql`CREATE INDEX IF NOT EXISTS idx_runs_thread_id ON runs(thread_id)`.execute(db);\n await sql`CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status)`.execute(db);\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -3,11 +3,13 @@ import { KyselyThreadsManager } from './kysely/threads';
|
|
|
3
3
|
import { ShallowMemorySaver } from './memory/shallow-memory';
|
|
4
4
|
import { MemoryThreadsManager } from './memory/threads';
|
|
5
5
|
import type { SqliteSaver as SqliteSaverType } from './sqlite/checkpoint';
|
|
6
|
+
import type { SqliteShallowSaver as SqliteShallowSaverType } from './sqlite/shallow-checkpoint';
|
|
6
7
|
import type { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
|
|
7
8
|
export { ShallowMemorySaver } from './memory/shallow-memory';
|
|
8
9
|
export { MemorySaver } from './memory/checkpoint';
|
|
9
|
-
export
|
|
10
|
+
export { SqliteShallowSaver } from './sqlite/shallow-checkpoint';
|
|
11
|
+
export declare const createCheckPointer: () => Promise<ShallowMemorySaver | SqliteSaverType | SqliteShallowSaverType | PostgresSaver | import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver>;
|
|
10
12
|
export declare const createMessageQueue: () => Promise<StreamQueueManager<BaseStreamQueueInterface>>;
|
|
11
13
|
export declare const createThreadManager: (config: {
|
|
12
|
-
checkpointer?: SqliteSaverType | PostgresSaver;
|
|
14
|
+
checkpointer?: SqliteSaverType | SqliteShallowSaverType | PostgresSaver;
|
|
13
15
|
}) => Promise<import("./kysely").RemoteKyselyThreadsManager<unknown> | KyselyThreadsManager<unknown> | MemoryThreadsManager<unknown>>;
|
|
@@ -9,7 +9,14 @@ import { Database } from './types';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare class SQLiteAdapter implements DatabaseAdapter {
|
|
11
11
|
db: Kysely<Database>;
|
|
12
|
+
private pragmaPromise;
|
|
12
13
|
constructor(database: Kysely<any>);
|
|
14
|
+
/**
|
|
15
|
+
* 设置 SQLite PRAGMA 配置,解决锁问题
|
|
16
|
+
* 使用 Promise 缓存确保并发安全且只执行一次
|
|
17
|
+
*/
|
|
18
|
+
private ensurePragma;
|
|
19
|
+
private doEnsurePragma;
|
|
13
20
|
dateToDb(date: Date): string;
|
|
14
21
|
dbToDate(dbValue: any): Date;
|
|
15
22
|
jsonToDb(obj: any): string;
|