@dawn-ai/sqlite-storage 0.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/LICENSE +21 -0
- package/dist/checkpointer/index.d.ts +7 -0
- package/dist/checkpointer/index.d.ts.map +1 -0
- package/dist/checkpointer/index.js +10 -0
- package/dist/checkpointer/saver.d.ts +14 -0
- package/dist/checkpointer/saver.d.ts.map +1 -0
- package/dist/checkpointer/saver.js +155 -0
- package/dist/checkpointer/schema.d.ts +3 -0
- package/dist/checkpointer/schema.d.ts.map +1 -0
- package/dist/checkpointer/schema.js +29 -0
- package/dist/checkpointer/serde.d.ts +3 -0
- package/dist/checkpointer/serde.d.ts.map +1 -0
- package/dist/checkpointer/serde.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/internal/db.d.ts +4 -0
- package/dist/internal/db.d.ts.map +1 -0
- package/dist/internal/db.js +16 -0
- package/dist/internal/migrate.d.ts +7 -0
- package/dist/internal/migrate.d.ts.map +1 -0
- package/dist/internal/migrate.js +20 -0
- package/dist/threads/index.d.ts +6 -0
- package/dist/threads/index.d.ts.map +1 -0
- package/dist/threads/index.js +9 -0
- package/dist/threads/schema.d.ts +3 -0
- package/dist/threads/schema.d.ts.map +1 -0
- package/dist/threads/schema.js +15 -0
- package/dist/threads/store.d.ts +28 -0
- package/dist/threads/store.d.ts.map +1 -0
- package/dist/threads/store.js +60 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Brian Love
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DawnSqliteSaver } from "./saver.js";
|
|
2
|
+
export interface SqliteCheckpointerOptions {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function sqliteCheckpointer(options: SqliteCheckpointerOptions): DawnSqliteSaver;
|
|
6
|
+
export { DawnSqliteSaver } from "./saver.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkpointer/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAG5C,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,eAAe,CAItF;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { openDb } from "../internal/db.js";
|
|
2
|
+
import { runMigrations } from "../internal/migrate.js";
|
|
3
|
+
import { DawnSqliteSaver } from "./saver.js";
|
|
4
|
+
import { CHECKPOINTER_MIGRATIONS } from "./schema.js";
|
|
5
|
+
export function sqliteCheckpointer(options) {
|
|
6
|
+
const db = openDb(options.path);
|
|
7
|
+
runMigrations(db, CHECKPOINTER_MIGRATIONS);
|
|
8
|
+
return new DawnSqliteSaver(db);
|
|
9
|
+
}
|
|
10
|
+
export { DawnSqliteSaver } from "./saver.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RunnableConfig } from "@langchain/core/runnables";
|
|
2
|
+
import type { Checkpoint, CheckpointListOptions, CheckpointMetadata, CheckpointTuple } from "@langchain/langgraph-checkpoint";
|
|
3
|
+
import { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
4
|
+
import type { Db } from "../internal/db.js";
|
|
5
|
+
export declare class DawnSqliteSaver extends BaseCheckpointSaver {
|
|
6
|
+
private readonly db;
|
|
7
|
+
constructor(db: Db);
|
|
8
|
+
getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
|
|
9
|
+
list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
|
|
10
|
+
put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata, _newVersions: Record<string, string | number>): Promise<RunnableConfig>;
|
|
11
|
+
putWrites(config: RunnableConfig, writes: [string, unknown][], taskId: string): Promise<void>;
|
|
12
|
+
deleteThread(threadId: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=saver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saver.d.ts","sourceRoot":"","sources":["../../src/checkpointer/saver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,KAAK,EACV,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EAChB,MAAM,iCAAiC,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAA;AAuE3C,qBAAa,eAAgB,SAAQ,mBAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAI7B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAoCrE,IAAI,CACT,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAAC,eAAe,CAAC;IA4B5B,GAAG,CACP,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,kBAAkB,EAC5B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC5C,OAAO,CAAC,cAAc,CAAC;IA2BpB,SAAS,CACb,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAqCV,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYpD"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
2
|
+
async function buildTuple(row, writes, serde) {
|
|
3
|
+
const checkpoint = (await serde.loadsTyped(row.type ?? "json", row.checkpoint));
|
|
4
|
+
const metadata = (await serde.loadsTyped("json", row.metadata));
|
|
5
|
+
const pendingWrites = await Promise.all(writes.map(async (w) => [
|
|
6
|
+
w.task_id,
|
|
7
|
+
w.channel,
|
|
8
|
+
w.value != null ? await serde.loadsTyped(w.type ?? "json", w.value) : null,
|
|
9
|
+
]));
|
|
10
|
+
const config = {
|
|
11
|
+
configurable: {
|
|
12
|
+
thread_id: row.thread_id,
|
|
13
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
14
|
+
checkpoint_id: row.checkpoint_id,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const base = { config, checkpoint, metadata, pendingWrites };
|
|
18
|
+
if (row.parent_checkpoint_id != null) {
|
|
19
|
+
return {
|
|
20
|
+
...base,
|
|
21
|
+
parentConfig: {
|
|
22
|
+
configurable: {
|
|
23
|
+
thread_id: row.thread_id,
|
|
24
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
25
|
+
checkpoint_id: row.parent_checkpoint_id,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return base;
|
|
31
|
+
}
|
|
32
|
+
export class DawnSqliteSaver extends BaseCheckpointSaver {
|
|
33
|
+
db;
|
|
34
|
+
constructor(db) {
|
|
35
|
+
super();
|
|
36
|
+
this.db = db;
|
|
37
|
+
}
|
|
38
|
+
async getTuple(config) {
|
|
39
|
+
const threadId = config.configurable?.thread_id;
|
|
40
|
+
if (!threadId)
|
|
41
|
+
return undefined;
|
|
42
|
+
const ns = config.configurable?.checkpoint_ns ?? "";
|
|
43
|
+
const ckptId = config.configurable?.checkpoint_id;
|
|
44
|
+
let row;
|
|
45
|
+
if (ckptId) {
|
|
46
|
+
row = this.db
|
|
47
|
+
.prepare("SELECT thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata FROM checkpoints WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?")
|
|
48
|
+
.get(threadId, ns, ckptId);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
row = this.db
|
|
52
|
+
.prepare("SELECT thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata FROM checkpoints WHERE thread_id = ? AND checkpoint_ns = ? ORDER BY checkpoint_id DESC LIMIT 1")
|
|
53
|
+
.get(threadId, ns);
|
|
54
|
+
}
|
|
55
|
+
if (!row)
|
|
56
|
+
return undefined;
|
|
57
|
+
const typedRow = row;
|
|
58
|
+
const writeRows = this.db
|
|
59
|
+
.prepare("SELECT task_id, channel, type, value FROM writes WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ? ORDER BY task_id, idx")
|
|
60
|
+
.all(typedRow.thread_id, typedRow.checkpoint_ns, typedRow.checkpoint_id);
|
|
61
|
+
return buildTuple(typedRow, writeRows, this.serde);
|
|
62
|
+
}
|
|
63
|
+
async *list(config, options) {
|
|
64
|
+
const threadId = config.configurable?.thread_id;
|
|
65
|
+
if (!threadId)
|
|
66
|
+
return;
|
|
67
|
+
const ns = config.configurable?.checkpoint_ns ?? "";
|
|
68
|
+
const before = options?.before?.configurable?.checkpoint_id;
|
|
69
|
+
const limit = options?.limit ?? -1;
|
|
70
|
+
const params = [threadId, ns];
|
|
71
|
+
let sql = "SELECT thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata FROM checkpoints WHERE thread_id = ? AND checkpoint_ns = ?";
|
|
72
|
+
if (before) {
|
|
73
|
+
sql += " AND checkpoint_id < ?";
|
|
74
|
+
params.push(before);
|
|
75
|
+
}
|
|
76
|
+
sql += " ORDER BY checkpoint_id DESC";
|
|
77
|
+
if (limit > 0) {
|
|
78
|
+
sql += " LIMIT ?";
|
|
79
|
+
params.push(limit);
|
|
80
|
+
}
|
|
81
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
// Note: list returns lightweight tuples without pendingWrites. Callers that
|
|
84
|
+
// need writes should call getTuple(specificCheckpointId) for full hydration.
|
|
85
|
+
// This matches the @langchain/langgraph-checkpoint-sqlite reference behavior.
|
|
86
|
+
yield await buildTuple(row, [], this.serde);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async put(config, checkpoint, metadata, _newVersions) {
|
|
90
|
+
const threadId = config.configurable?.thread_id;
|
|
91
|
+
if (!threadId) {
|
|
92
|
+
throw new Error("[DawnSqliteSaver] config.configurable.thread_id is required");
|
|
93
|
+
}
|
|
94
|
+
const ns = config.configurable?.checkpoint_ns ?? "";
|
|
95
|
+
const parentId = config.configurable?.checkpoint_id ?? null;
|
|
96
|
+
// _newVersions is provided by LangGraph for version-tracking purposes but is
|
|
97
|
+
// not persisted separately — versions live inside the serialized checkpoint payload.
|
|
98
|
+
// Use the inherited serde (JsonPlusSerializer) so that LangChain objects such
|
|
99
|
+
// as BaseMessage instances survive the round-trip through SQLite.
|
|
100
|
+
const [checkpointType, checkpointBytes] = await this.serde.dumpsTyped(checkpoint);
|
|
101
|
+
const [, metadataBytes] = await this.serde.dumpsTyped(metadata);
|
|
102
|
+
this.db
|
|
103
|
+
.prepare(`INSERT OR REPLACE INTO checkpoints
|
|
104
|
+
(thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata)
|
|
105
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
106
|
+
.run(threadId, ns, checkpoint.id, parentId, checkpointType, checkpointBytes, metadataBytes);
|
|
107
|
+
return {
|
|
108
|
+
configurable: { thread_id: threadId, checkpoint_ns: ns, checkpoint_id: checkpoint.id },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async putWrites(config, writes, taskId) {
|
|
112
|
+
const threadId = config.configurable?.thread_id;
|
|
113
|
+
if (!threadId) {
|
|
114
|
+
throw new Error("[DawnSqliteSaver] config.configurable.thread_id is required");
|
|
115
|
+
}
|
|
116
|
+
const ns = config.configurable?.checkpoint_ns ?? "";
|
|
117
|
+
const ckptId = config.configurable?.checkpoint_id;
|
|
118
|
+
if (!ckptId) {
|
|
119
|
+
throw new Error("[DawnSqliteSaver] config.configurable.checkpoint_id is required");
|
|
120
|
+
}
|
|
121
|
+
// Serialize all values before opening the transaction (serde is async).
|
|
122
|
+
const serialized = await Promise.all(writes.map(async ([channel, value]) => {
|
|
123
|
+
const [type, bytes] = await this.serde.dumpsTyped(value);
|
|
124
|
+
return { channel, type, bytes };
|
|
125
|
+
}));
|
|
126
|
+
const stmt = this.db.prepare(`INSERT OR REPLACE INTO writes
|
|
127
|
+
(thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
129
|
+
this.db.exec("BEGIN");
|
|
130
|
+
try {
|
|
131
|
+
serialized.forEach(({ channel, type, bytes }, idx) => {
|
|
132
|
+
stmt.run(threadId, ns, ckptId, taskId, idx, channel, type, bytes);
|
|
133
|
+
});
|
|
134
|
+
this.db.exec("COMMIT");
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
this.db.exec("ROLLBACK");
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async deleteThread(threadId) {
|
|
142
|
+
if (!threadId)
|
|
143
|
+
throw new Error("[DawnSqliteSaver] deleteThread requires a thread_id");
|
|
144
|
+
this.db.exec("BEGIN");
|
|
145
|
+
try {
|
|
146
|
+
this.db.prepare("DELETE FROM writes WHERE thread_id = ?").run(threadId);
|
|
147
|
+
this.db.prepare("DELETE FROM checkpoints WHERE thread_id = ?").run(threadId);
|
|
148
|
+
this.db.exec("COMMIT");
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
this.db.exec("ROLLBACK");
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/checkpointer/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD,eAAO,MAAM,uBAAuB,EAAE,SAAS,SAAS,EA4BvD,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const CHECKPOINTER_MIGRATIONS = [
|
|
2
|
+
{
|
|
3
|
+
version: 1,
|
|
4
|
+
up: `
|
|
5
|
+
CREATE TABLE checkpoints (
|
|
6
|
+
thread_id TEXT NOT NULL,
|
|
7
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
8
|
+
checkpoint_id TEXT NOT NULL,
|
|
9
|
+
parent_checkpoint_id TEXT,
|
|
10
|
+
type TEXT,
|
|
11
|
+
checkpoint BLOB NOT NULL,
|
|
12
|
+
metadata BLOB NOT NULL,
|
|
13
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
|
|
14
|
+
);
|
|
15
|
+
CREATE INDEX idx_checkpoints_thread ON checkpoints(thread_id, checkpoint_ns);
|
|
16
|
+
CREATE TABLE writes (
|
|
17
|
+
thread_id TEXT NOT NULL,
|
|
18
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
19
|
+
checkpoint_id TEXT NOT NULL,
|
|
20
|
+
task_id TEXT NOT NULL,
|
|
21
|
+
idx INTEGER NOT NULL,
|
|
22
|
+
channel TEXT NOT NULL,
|
|
23
|
+
type TEXT,
|
|
24
|
+
value BLOB,
|
|
25
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
26
|
+
);
|
|
27
|
+
`,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serde.d.ts","sourceRoot":"","sources":["../../src/checkpointer/serde.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAErD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAEnD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { SqliteCheckpointerOptions } from "./checkpointer/index.js";
|
|
2
|
+
export { DawnSqliteSaver, sqliteCheckpointer } from "./checkpointer/index.js";
|
|
3
|
+
export type { CreateThreadInput, Thread, ThreadStatus, ThreadsStore, ThreadsStoreOptions, } from "./threads/index.js";
|
|
4
|
+
export { createThreadsStore } from "./threads/index.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,YAAY,EACV,iBAAiB,EACjB,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/internal/db.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,MAAM,EAAE,GAAG,YAAY,CAAA;AAE7B,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAYvC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
export function openDb(path) {
|
|
5
|
+
const isMemory = path === ":memory:";
|
|
6
|
+
if (!isMemory) {
|
|
7
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
const db = new DatabaseSync(path);
|
|
10
|
+
if (!isMemory) {
|
|
11
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
12
|
+
}
|
|
13
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
14
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
15
|
+
return db;
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
export interface Migration {
|
|
3
|
+
readonly version: number;
|
|
4
|
+
readonly up: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runMigrations(db: DatabaseSync, migrations: readonly Migration[]): void;
|
|
7
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/internal/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CAmBtF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function runMigrations(db, migrations) {
|
|
2
|
+
db.exec("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY)");
|
|
3
|
+
const row = db.prepare("SELECT max(version) AS v FROM schema_version").get();
|
|
4
|
+
const current = row?.v ?? 0;
|
|
5
|
+
const sorted = [...migrations].sort((a, b) => a.version - b.version);
|
|
6
|
+
for (const m of sorted) {
|
|
7
|
+
if (m.version <= current)
|
|
8
|
+
continue;
|
|
9
|
+
db.exec("BEGIN");
|
|
10
|
+
try {
|
|
11
|
+
db.exec(m.up);
|
|
12
|
+
db.prepare("INSERT INTO schema_version(version) VALUES (?)").run(m.version);
|
|
13
|
+
db.exec("COMMIT");
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
db.exec("ROLLBACK");
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface ThreadsStoreOptions {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
}
|
|
4
|
+
export declare function createThreadsStore(options: ThreadsStoreOptions): import("./store.js").ThreadsStore;
|
|
5
|
+
export type { CreateThreadInput, Thread, ThreadStatus, ThreadsStore } from "./store.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/threads/index.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,qCAI9D;AAED,YAAY,EAAE,iBAAiB,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { openDb } from "../internal/db.js";
|
|
2
|
+
import { runMigrations } from "../internal/migrate.js";
|
|
3
|
+
import { THREADS_MIGRATIONS } from "./schema.js";
|
|
4
|
+
import { makeThreadsStore } from "./store.js";
|
|
5
|
+
export function createThreadsStore(options) {
|
|
6
|
+
const db = openDb(options.path);
|
|
7
|
+
runMigrations(db, THREADS_MIGRATIONS);
|
|
8
|
+
return makeThreadsStore(db);
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/threads/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD,eAAO,MAAM,kBAAkB,EAAE,SAAS,SAAS,EAclD,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const THREADS_MIGRATIONS = [
|
|
2
|
+
{
|
|
3
|
+
version: 1,
|
|
4
|
+
up: `
|
|
5
|
+
CREATE TABLE threads (
|
|
6
|
+
thread_id TEXT PRIMARY KEY,
|
|
7
|
+
created_at TEXT NOT NULL,
|
|
8
|
+
updated_at TEXT NOT NULL,
|
|
9
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
10
|
+
status TEXT NOT NULL DEFAULT 'idle'
|
|
11
|
+
);
|
|
12
|
+
CREATE INDEX idx_threads_updated ON threads(updated_at DESC);
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Db } from "../internal/db.js";
|
|
2
|
+
export type ThreadStatus = "idle" | "busy" | "interrupted";
|
|
3
|
+
export interface Thread {
|
|
4
|
+
readonly thread_id: string;
|
|
5
|
+
readonly created_at: string;
|
|
6
|
+
readonly updated_at: string;
|
|
7
|
+
readonly metadata: Record<string, unknown>;
|
|
8
|
+
readonly status: ThreadStatus;
|
|
9
|
+
}
|
|
10
|
+
export interface CreateThreadInput {
|
|
11
|
+
readonly thread_id?: string;
|
|
12
|
+
readonly metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface ThreadsStore {
|
|
15
|
+
createThread(input: CreateThreadInput): Promise<Thread>;
|
|
16
|
+
getThread(threadId: string): Promise<Thread | undefined>;
|
|
17
|
+
deleteThread(threadId: string): Promise<void>;
|
|
18
|
+
listThreads(): Promise<Thread[]>;
|
|
19
|
+
updateStatus(threadId: string, status: ThreadStatus): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Shallow-merge `patch` into the thread's existing metadata. No-op if the
|
|
22
|
+
* thread does not exist. Used to persist durable per-thread runtime facts
|
|
23
|
+
* (e.g. the last route key) so they survive a server restart.
|
|
24
|
+
*/
|
|
25
|
+
updateMetadata(threadId: string, patch: Record<string, unknown>): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function makeThreadsStore(db: Db): ThreadsStore;
|
|
28
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/threads/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAA;AAE3C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAA;AAE1D,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC1C,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACxD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAChC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChF;AAwBD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,GAAG,YAAY,CA2DrD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
function rowToThread(row) {
|
|
3
|
+
return {
|
|
4
|
+
thread_id: row.thread_id,
|
|
5
|
+
created_at: row.created_at,
|
|
6
|
+
updated_at: row.updated_at,
|
|
7
|
+
metadata: JSON.parse(row.metadata),
|
|
8
|
+
status: row.status,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function newThreadId() {
|
|
12
|
+
return `t-${randomBytes(4).toString("hex")}`;
|
|
13
|
+
}
|
|
14
|
+
export function makeThreadsStore(db) {
|
|
15
|
+
return {
|
|
16
|
+
async createThread(input) {
|
|
17
|
+
const now = new Date().toISOString();
|
|
18
|
+
const threadId = input.thread_id ?? newThreadId();
|
|
19
|
+
const metadata = JSON.stringify(input.metadata ?? {});
|
|
20
|
+
db.prepare("INSERT INTO threads(thread_id, created_at, updated_at, metadata, status) VALUES (?, ?, ?, ?, 'idle')").run(threadId, now, now, metadata);
|
|
21
|
+
return {
|
|
22
|
+
thread_id: threadId,
|
|
23
|
+
created_at: now,
|
|
24
|
+
updated_at: now,
|
|
25
|
+
metadata: input.metadata ?? {},
|
|
26
|
+
status: "idle",
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
async getThread(threadId) {
|
|
30
|
+
const row = db
|
|
31
|
+
.prepare("SELECT thread_id, created_at, updated_at, metadata, status FROM threads WHERE thread_id = ?")
|
|
32
|
+
.get(threadId);
|
|
33
|
+
return row ? rowToThread(row) : undefined;
|
|
34
|
+
},
|
|
35
|
+
async deleteThread(threadId) {
|
|
36
|
+
db.prepare("DELETE FROM threads WHERE thread_id = ?").run(threadId);
|
|
37
|
+
},
|
|
38
|
+
async listThreads() {
|
|
39
|
+
const rows = db
|
|
40
|
+
.prepare("SELECT thread_id, created_at, updated_at, metadata, status FROM threads ORDER BY updated_at DESC")
|
|
41
|
+
.all();
|
|
42
|
+
return rows.map(rowToThread);
|
|
43
|
+
},
|
|
44
|
+
async updateStatus(threadId, status) {
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
db.prepare("UPDATE threads SET status = ?, updated_at = ? WHERE thread_id = ?").run(status, now, threadId);
|
|
47
|
+
},
|
|
48
|
+
async updateMetadata(threadId, patch) {
|
|
49
|
+
const row = db
|
|
50
|
+
.prepare("SELECT metadata FROM threads WHERE thread_id = ?")
|
|
51
|
+
.get(threadId);
|
|
52
|
+
if (!row)
|
|
53
|
+
return;
|
|
54
|
+
const current = JSON.parse(row.metadata);
|
|
55
|
+
const merged = JSON.stringify({ ...current, ...patch });
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
db.prepare("UPDATE threads SET metadata = ?, updated_at = ? WHERE thread_id = ?").run(merged, now, threadId);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dawn-ai/sqlite-storage",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/cacheplane/dawnai/tree/main/packages/sqlite-storage#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/cacheplane/dawnai.git",
|
|
11
|
+
"directory": "packages/sqlite-storage"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/cacheplane/dawnai/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.13.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@langchain/core": "^1.1.44",
|
|
34
|
+
"@langchain/langgraph-checkpoint": "^1.0.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@langchain/core": "^1.1.47",
|
|
38
|
+
"@langchain/langgraph-checkpoint": "^1.0.2",
|
|
39
|
+
"@types/node": "25.6.0",
|
|
40
|
+
"@dawn-ai/config-typescript": "0.2.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -b tsconfig.json",
|
|
44
|
+
"lint": "biome check --config-path ../config-biome/biome.json package.json src tsconfig.json vitest.config.ts",
|
|
45
|
+
"test": "vitest --run --config vitest.config.ts --passWithNoTests",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
}
|
|
48
|
+
}
|