1
1
import { BaseCheckpointSaver , Checkpoint , CheckpointMetadata } from '@langchain/langgraph'
2
2
import { RunnableConfig } from '@langchain/core/runnables'
3
3
import { BaseMessage } from '@langchain/core/messages'
4
- import { DataSource , QueryRunner } from 'typeorm'
4
+ import { DataSource } from 'typeorm'
5
5
import { CheckpointTuple , SaverOptions , SerializerProtocol } from '../interface'
6
6
import { IMessage , MemoryMethods } from '../../../../src/Interface'
7
7
import { mapChatMessageToBaseMessage } from '../../../../src/utils'
8
8
9
9
export class SqliteSaver extends BaseCheckpointSaver implements MemoryMethods {
10
10
protected isSetup : boolean
11
-
12
- datasource : DataSource
13
-
14
- queryRunner : QueryRunner
15
-
16
11
config : SaverOptions
17
-
18
12
threadId : string
19
-
20
13
tableName = 'checkpoints'
21
14
22
15
constructor ( config : SaverOptions , serde ?: SerializerProtocol < Checkpoint > ) {
23
16
super ( serde )
24
17
this . config = config
25
- const { datasourceOptions , threadId } = config
18
+ const { threadId } = config
26
19
this . threadId = threadId
27
- this . datasource = new DataSource ( datasourceOptions )
28
20
}
29
21
30
- private async setup ( ) : Promise < void > {
22
+ private async getDataSource ( ) : Promise < DataSource > {
23
+ const { datasourceOptions } = this . config
24
+ const dataSource = new DataSource ( datasourceOptions )
25
+ await dataSource . initialize ( )
26
+ return dataSource
27
+ }
28
+
29
+ private async setup ( dataSource : DataSource ) : Promise < void > {
31
30
if ( this . isSetup ) {
32
31
return
33
32
}
34
33
35
34
try {
36
- const appDataSource = await this . datasource . initialize ( )
37
-
38
- this . queryRunner = appDataSource . createQueryRunner ( )
39
- await this . queryRunner . manager . query ( `
35
+ const queryRunner = dataSource . createQueryRunner ( )
36
+ await queryRunner . manager . query ( `
40
37
CREATE TABLE IF NOT EXISTS ${ this . tableName } (
41
38
thread_id TEXT NOT NULL,
42
39
checkpoint_id TEXT NOT NULL,
43
40
parent_id TEXT,
44
41
checkpoint BLOB,
45
42
metadata BLOB,
46
43
PRIMARY KEY (thread_id, checkpoint_id));` )
44
+ await queryRunner . release ( )
47
45
} catch ( error ) {
48
46
console . error ( `Error creating ${ this . tableName } table` , error )
49
47
throw new Error ( `Error creating ${ this . tableName } table` )
@@ -53,16 +51,20 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
53
51
}
54
52
55
53
async getTuple ( config : RunnableConfig ) : Promise < CheckpointTuple | undefined > {
56
- await this . setup ( )
54
+ const dataSource = await this . getDataSource ( )
55
+ await this . setup ( dataSource )
56
+
57
57
const thread_id = config . configurable ?. thread_id || this . threadId
58
58
const checkpoint_id = config . configurable ?. checkpoint_id
59
59
60
60
if ( checkpoint_id ) {
61
61
try {
62
+ const queryRunner = dataSource . createQueryRunner ( )
62
63
const keys = [ thread_id , checkpoint_id ]
63
64
const sql = `SELECT checkpoint, parent_id, metadata FROM ${ this . tableName } WHERE thread_id = ? AND checkpoint_id = ?`
64
65
65
- const rows = await this . queryRunner . manager . query ( sql , [ ...keys ] )
66
+ const rows = await queryRunner . manager . query ( sql , [ ...keys ] )
67
+ await queryRunner . release ( )
66
68
67
69
if ( rows && rows . length > 0 ) {
68
70
return {
@@ -82,39 +84,53 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
82
84
} catch ( error ) {
83
85
console . error ( `Error retrieving ${ this . tableName } ` , error )
84
86
throw new Error ( `Error retrieving ${ this . tableName } ` )
87
+ } finally {
88
+ await dataSource . destroy ( )
85
89
}
86
90
} else {
87
- const keys = [ thread_id ]
88
- const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${ this . tableName } WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
91
+ try {
92
+ const queryRunner = dataSource . createQueryRunner ( )
93
+ const keys = [ thread_id ]
94
+ const sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${ this . tableName } WHERE thread_id = ? ORDER BY checkpoint_id DESC LIMIT 1`
89
95
90
- const rows = await this . queryRunner . manager . query ( sql , [ ...keys ] )
96
+ const rows = await queryRunner . manager . query ( sql , [ ...keys ] )
97
+ await queryRunner . release ( )
91
98
92
- if ( rows && rows . length > 0 ) {
93
- return {
94
- config : {
95
- configurable : {
96
- thread_id : rows [ 0 ] . thread_id ,
97
- checkpoint_id : rows [ 0 ] . checkpoint_id
98
- }
99
- } ,
100
- checkpoint : ( await this . serde . parse ( rows [ 0 ] . checkpoint ) ) as Checkpoint ,
101
- metadata : ( await this . serde . parse ( rows [ 0 ] . metadata ) ) as CheckpointMetadata ,
102
- parentConfig : rows [ 0 ] . parent_id
103
- ? {
104
- configurable : {
105
- thread_id : rows [ 0 ] . thread_id ,
106
- checkpoint_id : rows [ 0 ] . parent_id
99
+ if ( rows && rows . length > 0 ) {
100
+ return {
101
+ config : {
102
+ configurable : {
103
+ thread_id : rows [ 0 ] . thread_id ,
104
+ checkpoint_id : rows [ 0 ] . checkpoint_id
105
+ }
106
+ } ,
107
+ checkpoint : ( await this . serde . parse ( rows [ 0 ] . checkpoint ) ) as Checkpoint ,
108
+ metadata : ( await this . serde . parse ( rows [ 0 ] . metadata ) ) as CheckpointMetadata ,
109
+ parentConfig : rows [ 0 ] . parent_id
110
+ ? {
111
+ configurable : {
112
+ thread_id : rows [ 0 ] . thread_id ,
113
+ checkpoint_id : rows [ 0 ] . parent_id
114
+ }
107
115
}
108
- }
109
- : undefined
116
+ : undefined
117
+ }
110
118
}
119
+ } catch ( error ) {
120
+ console . error ( `Error retrieving ${ this . tableName } ` , error )
121
+ throw new Error ( `Error retrieving ${ this . tableName } ` )
122
+ } finally {
123
+ await dataSource . destroy ( )
111
124
}
112
125
}
113
126
return undefined
114
127
}
115
128
116
129
async * list ( config : RunnableConfig , limit ?: number , before ?: RunnableConfig ) : AsyncGenerator < CheckpointTuple > {
117
- await this . setup ( )
130
+ const dataSource = await this . getDataSource ( )
131
+ await this . setup ( dataSource )
132
+
133
+ const queryRunner = dataSource . createQueryRunner ( )
118
134
const thread_id = config . configurable ?. thread_id || this . threadId
119
135
let sql = `SELECT thread_id, checkpoint_id, parent_id, checkpoint, metadata FROM ${ this . tableName } WHERE thread_id = ? ${
120
136
before ? 'AND checkpoint_id < ?' : ''
@@ -125,7 +141,8 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
125
141
const args = [ thread_id , before ?. configurable ?. checkpoint_id ] . filter ( Boolean )
126
142
127
143
try {
128
- const rows = await this . queryRunner . manager . query ( sql , [ ...args ] )
144
+ const rows = await queryRunner . manager . query ( sql , [ ...args ] )
145
+ await queryRunner . release ( )
129
146
130
147
if ( rows && rows . length > 0 ) {
131
148
for ( const row of rows ) {
@@ -152,13 +169,18 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
152
169
} catch ( error ) {
153
170
console . error ( `Error listing ${ this . tableName } ` , error )
154
171
throw new Error ( `Error listing ${ this . tableName } ` )
172
+ } finally {
173
+ await dataSource . destroy ( )
155
174
}
156
175
}
157
176
158
177
async put ( config : RunnableConfig , checkpoint : Checkpoint , metadata : CheckpointMetadata ) : Promise < RunnableConfig > {
159
- await this . setup ( )
178
+ const dataSource = await this . getDataSource ( )
179
+ await this . setup ( dataSource )
180
+
160
181
if ( ! config . configurable ?. checkpoint_id ) return { }
161
182
try {
183
+ const queryRunner = dataSource . createQueryRunner ( )
162
184
const row = [
163
185
config . configurable ?. thread_id || this . threadId ,
164
186
checkpoint . id ,
@@ -169,10 +191,13 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
169
191
170
192
const query = `INSERT OR REPLACE INTO ${ this . tableName } (thread_id, checkpoint_id, parent_id, checkpoint, metadata) VALUES (?, ?, ?, ?, ?)`
171
193
172
- await this . queryRunner . manager . query ( query , row )
194
+ await queryRunner . manager . query ( query , row )
195
+ await queryRunner . release ( )
173
196
} catch ( error ) {
174
197
console . error ( 'Error saving checkpoint' , error )
175
198
throw new Error ( 'Error saving checkpoint' )
199
+ } finally {
200
+ await dataSource . destroy ( )
176
201
}
177
202
178
203
return {
@@ -187,13 +212,20 @@ CREATE TABLE IF NOT EXISTS ${this.tableName} (
187
212
if ( ! threadId ) {
188
213
return
189
214
}
190
- await this . setup ( )
215
+
216
+ const dataSource = await this . getDataSource ( )
217
+ await this . setup ( dataSource )
218
+
191
219
const query = `DELETE FROM "${ this . tableName } " WHERE thread_id = ?;`
192
220
193
221
try {
194
- await this . queryRunner . manager . query ( query , [ threadId ] )
222
+ const queryRunner = dataSource . createQueryRunner ( )
223
+ await queryRunner . manager . query ( query , [ threadId ] )
224
+ await queryRunner . release ( )
195
225
} catch ( error ) {
196
226
console . error ( `Error deleting thread_id ${ threadId } ` , error )
227
+ } finally {
228
+ await dataSource . destroy ( )
197
229
}
198
230
}
199
231
0 commit comments