1- // src/pages/aiAssistant/db.js (or a more general lib folder)
2-
31const DB_NAME = "AIAssistant.db" ; // SQLite convention for .db extension
42const DB_LOCATION = "default" ; // Standard location
53
@@ -61,13 +59,27 @@ function initializeTables() {
6159 FOREIGN KEY (conversationId) REFERENCES conversations(id) ON DELETE CASCADE
6260 )
6361 ` ) ;
64- // Index for faster querying of messages by conversationId and sorting
62+ // LangGraph Checkpoints Table
63+ tx . executeSql ( `
64+ CREATE TABLE IF NOT EXISTS langgraph_checkpoints (
65+ thread_id TEXT NOT NULL,
66+ checkpoint_id TEXT NOT NULL,
67+ parent_checkpoint_id TEXT,
68+ checkpoint TEXT,
69+ updated_at INTEGER,
70+ PRIMARY KEY (thread_id, checkpoint_id)
71+ )
72+ ` ) ;
73+ // Indexes
6574 tx . executeSql (
6675 `CREATE INDEX IF NOT EXISTS idx_messages_conversationId_timestamp ON messages (conversationId, timestamp)` ,
6776 ) ;
6877 tx . executeSql (
6978 `CREATE INDEX IF NOT EXISTS idx_conversations_lastModifiedAt ON conversations (lastModifiedAt)` ,
7079 ) ;
80+ tx . executeSql (
81+ `CREATE INDEX IF NOT EXISTS idx_lg_checkpoints_thread_id_updated_at ON langgraph_checkpoints (thread_id, updated_at DESC)` ) ;
82+
7183 } ,
7284 ( error ) => {
7385 console . error (
@@ -264,4 +276,108 @@ export async function deleteConversation(conversationId) {
264276 } ) ;
265277}
266278
267- // Ensure DB is opened and tables initialized when module loads or on first call
279+ // --- LangGraph Checkpoint DB Functions ---
280+ export async function getLangGraphCheckpointFromDB ( threadId , checkpointId = null ) {
281+ await openDB ( ) ;
282+ return new Promise ( ( resolve , reject ) => {
283+ db . readTransaction ( async ( tx ) => {
284+ try {
285+ let sql = "SELECT checkpoint FROM langgraph_checkpoints WHERE thread_id = ?" ;
286+ const params = [ threadId ] ;
287+ if ( checkpointId ) {
288+ sql += " AND checkpoint_id = ?" ;
289+ params . push ( checkpointId ) ;
290+ } else {
291+ sql += " ORDER BY updated_at DESC LIMIT 1" ; // Get latest for the thread
292+ }
293+ const resultSet = await executeSqlAsync ( tx , sql , params ) ;
294+ if ( resultSet . rows . length > 0 ) {
295+ const checkpointStr = resultSet . rows . item ( 0 ) . checkpoint ;
296+ resolve ( checkpointStr ? JSON . parse ( checkpointStr ) : null ) ;
297+ } else {
298+ resolve ( null ) ;
299+ }
300+ } catch ( error ) {
301+ console . error ( `[DB] Error getting LangGraph checkpoint (thread: ${ threadId } , ckpt: ${ checkpointId } ):` , error ) ;
302+ reject ( error ) ;
303+ }
304+ } ) ;
305+ } ) ;
306+ }
307+
308+ export async function putLangGraphCheckpointInDB ( threadId , checkpointTuple ) {
309+ await openDB ( ) ;
310+ const checkpointId = checkpointTuple ?. config ?. configurable ?. checkpoint_id ;
311+ const parentCheckpointId = checkpointTuple ?. parent_config ?. configurable ?. checkpoint_id || null ;
312+
313+ if ( ! checkpointId ) {
314+ console . error ( "[DB] Cannot save LangGraph checkpoint: checkpoint_id missing from checkpointTuple.config" ) ;
315+ return Promise . reject ( new Error ( "Missing checkpoint_id in checkpoint config" ) ) ;
316+ }
317+ const checkpointStr = JSON . stringify ( checkpointTuple ) ;
318+ const updatedAt = Date . parse ( checkpointTuple . config ?. configurable ?. checkpoint_id ?. split ( "T" ) [ 0 ] || checkpointTuple . ts || new Date ( ) . toISOString ( ) ) ;
319+
320+
321+ return new Promise ( ( resolve , reject ) => {
322+ db . transaction ( async ( tx ) => {
323+ try {
324+ await executeSqlAsync ( tx ,
325+ "INSERT OR REPLACE INTO langgraph_checkpoints (thread_id, checkpoint_id, parent_checkpoint_id, checkpoint, updated_at) VALUES (?, ?, ?, ?, ?)" ,
326+ [ threadId , checkpointId , parentCheckpointId , checkpointStr , updatedAt ]
327+ ) ;
328+ resolve ( ) ;
329+ } catch ( error ) {
330+ console . error ( `[DB] Error putting LangGraph checkpoint (thread: ${ threadId } , ckpt: ${ checkpointId } ):` , error ) ;
331+ reject ( error ) ;
332+ }
333+ } ) ;
334+ } ) ;
335+ }
336+
337+ export async function listLangGraphCheckpointsFromDB ( threadId , limit , beforeConfig = null ) {
338+ await openDB ( ) ;
339+ return new Promise ( ( resolve , reject ) => {
340+ db . readTransaction ( async ( tx ) => {
341+ try {
342+ let sql = "SELECT checkpoint FROM langgraph_checkpoints WHERE thread_id = ?" ;
343+ const params = [ threadId ] ;
344+ let beforeTimestamp = null ;
345+
346+ if ( beforeConfig ?. configurable ?. checkpoint_id ) {
347+ // Attempt to get the timestamp of the 'before' checkpoint to filter accurately
348+ const beforeCheckpointTuple = await getLangGraphCheckpointFromDB ( threadId , beforeConfig . configurable . checkpoint_id ) ;
349+ if ( beforeCheckpointTuple ) {
350+ beforeTimestamp = Date . parse ( beforeCheckpointTuple . ts || beforeCheckpointTuple . config ?. configurable ?. checkpoint_id ?. split ( "T" ) [ 0 ] || new Date ( 0 ) . toISOString ( ) ) ;
351+ if ( beforeTimestamp ) {
352+ sql += " AND updated_at < ?" ;
353+ params . push ( beforeTimestamp ) ;
354+ } else {
355+ console . warn ( "[DB] list: Could not determine timestamp for 'before' checkpoint_id:" , beforeConfig . configurable . checkpoint_id ) ;
356+ }
357+ } else {
358+ console . warn ( "[DB] list: 'before' checkpoint_id not found:" , beforeConfig . configurable . checkpoint_id ) ;
359+ }
360+ }
361+
362+ sql += " ORDER BY updated_at DESC" ;
363+ if ( limit ) {
364+ sql += " LIMIT ?" ;
365+ params . push ( limit ) ;
366+ }
367+
368+ const resultSet = await executeSqlAsync ( tx , sql , params ) ;
369+ const checkpoints = [ ] ;
370+ for ( let i = 0 ; i < resultSet . rows . length ; i ++ ) {
371+ const checkpointStr = resultSet . rows . item ( i ) . checkpoint ;
372+ if ( checkpointStr ) {
373+ checkpoints . push ( JSON . parse ( checkpointStr ) ) ;
374+ }
375+ }
376+ resolve ( checkpoints ) ;
377+ } catch ( error ) {
378+ console . error ( `[DB] Error listing LangGraph checkpoints (thread: ${ threadId } ):` , error ) ;
379+ reject ( error ) ;
380+ }
381+ } ) ;
382+ } ) ;
383+ }
0 commit comments