Skip to main content

hippmem_engine/
list_api.rs

1//! Engine::list — paginated memory listing API.
2
3use crate::{Engine, EngineResult, ListInput, ListItem, ListOutput};
4
5impl Engine {
6    /// Lists memories with pagination, fixed to NewestFirst (MemoryId descending) order.
7    ///
8    /// Reads all memories from the MEMORY_KV table, filters by content_type,
9    /// then returns a page.
10    pub fn list(&self, input: ListInput) -> EngineResult<ListOutput> {
11        let limit = input.limit.clamp(1, 100);
12
13        // Load all MemoryUnit entries
14        let units = crate::retrieve_api::load_all_units(self.store.db_arc());
15
16        // Build ListItem and filter
17        let mut items: Vec<ListItem> = units
18            .iter()
19            .filter(|u| {
20                if let Some(ref ct) = input.content_type {
21                    u.content.content_type == *ct
22                } else {
23                    true
24                }
25            })
26            .map(|u| ListItem {
27                id: u.id,
28                content_preview: u.content.raw.chars().take(100).collect(),
29                content_type: u.content.content_type,
30                created_at: u.created_at,
31                importance: u.understanding.importance.value(),
32                stage: u.stage,
33                lifecycle: u.lifecycle.clone(),
34                edge_count: u.links.len(),
35            })
36            .collect();
37
38        // Sort by MemoryId descending (NewestFirst)
39        items.sort_by_key(|item| std::cmp::Reverse(item.id));
40
41        let total = items.len() as u64;
42
43        // Cursor pagination
44        let start_idx = if let Some(cursor) = input.cursor {
45            // Find the position of the cursor ID in the sorted list, start after it
46            items
47                .iter()
48                .position(|item| item.id.0 == cursor)
49                .map(|p| p + 1) // Start from the item after the cursor
50                .unwrap_or_else(|| {
51                    // Cursor does not exist (may have been deleted); use binary search for the insertion point
52                    items
53                        .binary_search_by(|item| {
54                            // Descending order, so reverse comparison direction
55                            cursor.cmp(&item.id.0)
56                        })
57                        .unwrap_or_else(|insert_point| insert_point)
58                })
59        } else {
60            0
61        };
62
63        let end_idx = (start_idx + limit).min(items.len());
64        let has_more = end_idx < items.len();
65        let page: Vec<ListItem> = if start_idx < items.len() {
66            items.drain(start_idx..end_idx).collect()
67        } else {
68            Vec::new()
69        };
70
71        let next_cursor = if has_more {
72            page.last().map(|item| item.id.0)
73        } else {
74            None
75        };
76
77        Ok(ListOutput {
78            items: page,
79            next_cursor,
80            total,
81        })
82    }
83}