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