@@ -36,9 +36,7 @@ import {
3636 loadSourceTrustMap ,
3737 getBrainPolicy ,
3838 setBrainPolicy ,
39- loadExplicitMemoryLinks ,
4039 logChangelog ,
41- parseJsonObject ,
4240} from './db.js' ;
4341
4442import {
@@ -450,7 +448,11 @@ export async function handleApiExport(env: Env, brainId: string): Promise<Respon
450448 FROM memories WHERE brain_id = ? ORDER BY created_at DESC LIMIT 50000`
451449 ) . bind ( brainId ) . all < Record < string , unknown > > ( ) ;
452450
453- const links = await loadExplicitMemoryLinks ( env , brainId , 50000 ) ;
451+ const linksRows = await env . DB . prepare (
452+ `SELECT id, from_id, to_id, relation_type, label, created_at
453+ FROM memory_links WHERE brain_id = ? ORDER BY created_at DESC LIMIT 50000`
454+ ) . bind ( brainId ) . all < Record < string , unknown > > ( ) ;
455+ const links = linksRows . results ;
454456
455457 const changelog = await env . DB . prepare (
456458 `SELECT id, event_type, entity_type, entity_id, summary, payload, created_at
@@ -514,6 +516,8 @@ export async function handleApiExport(env: Env, brainId: string): Promise<Respon
514516 ...CORS_HEADERS ,
515517 'Content-Type' : 'application/json' ,
516518 'Content-Disposition' : `attachment; filename="${ filename } "` ,
519+ 'Cache-Control' : 'no-store' ,
520+ 'Pragma' : 'no-cache' ,
517521 } ,
518522 } ) ;
519523}
@@ -569,6 +573,13 @@ export async function handleApiImport(request: Request, env: Env, brainId: strin
569573 const aliasesPayload = Array . isArray ( data . memory_entity_aliases ) ? data . memory_entity_aliases as Array < Record < string , unknown > > : [ ] ;
570574 const watchesPayload = Array . isArray ( data . memory_watches ) ? data . memory_watches as Array < Record < string , unknown > > : [ ] ;
571575
576+ // Validate payload has restorable content before destructive overwrite
577+ if ( strategy === 'overwrite' && memoriesPayload . length === 0 ) {
578+ return new Response ( JSON . stringify ( { error : 'Overwrite import requires at least one memory in the payload. Aborting to prevent data loss.' } ) , {
579+ status : 400 , headers : { ...CORS_HEADERS , 'Content-Type' : 'application/json' } ,
580+ } ) ;
581+ }
582+
572583 const ts = now ( ) ;
573584 const counts = { memories : 0 , memory_links : 0 , memory_changelog : 0 , brain_source_trust : 0 , memory_conflict_resolutions : 0 , memory_entity_aliases : 0 , memory_watches : 0 , skipped : 0 } ;
574585 const restoredMemoryRows : Array < Record < string , unknown > > = [ ] ;
@@ -608,10 +619,11 @@ export async function handleApiImport(request: Request, env: Env, brainId: strin
608619 `INSERT INTO memories (id, brain_id, type, title, key, content, tags, source, confidence, importance, archived_at, created_at, updated_at)
609620 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
610621 ON CONFLICT(id) DO UPDATE SET
611- brain_id = excluded.brain_id, type = excluded.type, title = excluded.title, key = excluded.key,
622+ type = excluded.type, title = excluded.title, key = excluded.key,
612623 content = excluded.content, tags = excluded.tags, source = excluded.source,
613624 confidence = excluded.confidence, importance = excluded.importance,
614- archived_at = excluded.archived_at, created_at = excluded.created_at, updated_at = excluded.updated_at`
625+ archived_at = excluded.archived_at, created_at = excluded.created_at, updated_at = excluded.updated_at
626+ WHERE memories.brain_id = excluded.brain_id`
615627 ) . bind (
616628 memoryId , brainId , type ,
617629 typeof m . title === 'string' ? m . title : null ,
@@ -624,7 +636,13 @@ export async function handleApiImport(request: Request, env: Env, brainId: strin
624636 archivedAt , createdAt , updatedAt
625637 ) . run ( ) ;
626638
627- restoredMemoryRows . push ( { id : memoryId , type, content, tags : typeof m . tags === 'string' ? m . tags : null } ) ;
639+ restoredMemoryRows . push ( {
640+ id : memoryId , type, content,
641+ title : typeof m . title === 'string' ? m . title : null ,
642+ key : typeof m . key === 'string' ? m . key : null ,
643+ tags : typeof m . tags === 'string' ? m . tags : null ,
644+ source : typeof m . source === 'string' ? m . source : null ,
645+ } ) ;
628646 counts . memories ++ ;
629647 }
630648
@@ -653,8 +671,9 @@ export async function handleApiImport(request: Request, env: Env, brainId: strin
653671 `INSERT INTO memory_links (id, brain_id, from_id, to_id, relation_type, label, created_at)
654672 VALUES (?, ?, ?, ?, ?, ?, ?)
655673 ON CONFLICT(id) DO UPDATE SET
656- brain_id = excluded.brain_id, from_id = excluded.from_id, to_id = excluded.to_id,
657- relation_type = excluded.relation_type, label = excluded.label`
674+ from_id = excluded.from_id, to_id = excluded.to_id,
675+ relation_type = excluded.relation_type, label = excluded.label
676+ WHERE memory_links.brain_id = excluded.brain_id`
658677 ) . bind (
659678 linkId , brainId , fromId , toId ,
660679 normalizeRelation ( link . relation_type ) ,
@@ -673,15 +692,15 @@ export async function handleApiImport(request: Request, env: Env, brainId: strin
673692 const entityId = typeof entry . entity_id === 'string' ? entry . entity_id : '' ;
674693 const summary = typeof entry . summary === 'string' ? entry . summary : '' ;
675694 if ( ! eventType || ! entityType || ! entityId || ! summary ) continue ;
676- await env . DB . prepare (
695+ const result = await env . DB . prepare (
677696 `INSERT OR IGNORE INTO memory_changelog (id, brain_id, event_type, entity_type, entity_id, summary, payload, created_at)
678697 VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
679698 ) . bind (
680699 entryId , brainId , eventType , entityType , entityId , summary ,
681700 typeof entry . payload === 'string' ? entry . payload : ( entry . payload ? stableJson ( entry . payload ) : null ) ,
682701 Math . floor ( toFiniteNumber ( entry . created_at , ts ) )
683702 ) . run ( ) ;
684- counts . memory_changelog ++ ;
703+ if ( result . meta . changes > 0 ) counts . memory_changelog ++ ;
685704 }
686705
687706 for ( const raw of sourceTrustPayload ) {
@@ -810,6 +829,7 @@ export async function handleApiPurge(request: Request, env: Env, brainId: string
810829 await env . DB . prepare ( 'DELETE FROM memory_watches WHERE brain_id = ?' ) . bind ( brainId ) . run ( ) ;
811830 await env . DB . prepare ( 'DELETE FROM brain_source_trust WHERE brain_id = ?' ) . bind ( brainId ) . run ( ) ;
812831 await env . DB . prepare ( 'DELETE FROM brain_snapshots WHERE brain_id = ?' ) . bind ( brainId ) . run ( ) ;
832+ await env . DB . prepare ( 'DELETE FROM brain_policies WHERE brain_id = ?' ) . bind ( brainId ) . run ( ) ;
813833 await env . DB . prepare ( 'DELETE FROM memories WHERE brain_id = ?' ) . bind ( brainId ) . run ( ) ;
814834
815835 return new Response ( JSON . stringify ( { ok : true , purged : { memories : memoryCount , links : linkCount } } ) , {
0 commit comments