1use serde_json::{json, Value};
6use uuid::Uuid;
7
8use crate::types::CreateMemory;
9
10const PREVIEW_CHARS: usize = 280;
12
13pub fn node_id(id: &Uuid) -> String {
15 format!("memory:{id}")
16}
17
18pub fn edge_id(src: &str, dst: &str, rel_type: &str) -> String {
21 format!("{src}->{dst}:{rel_type}")
22}
23
24pub fn preview(content: &str) -> String {
25 let trimmed = content.trim();
26 if trimmed.chars().count() <= PREVIEW_CHARS {
27 return trimmed.to_string();
28 }
29 let cut: String = trimmed.chars().take(PREVIEW_CHARS).collect();
30 format!("{cut}…")
31}
32
33pub fn node_row(id: &Uuid, m: &CreateMemory, embedding: &[f32], now: &str) -> Value {
35 json!({
36 "id": node_id(id),
37 "labels": "Memory",
38 "realm": m.realm,
39 "memory_type": m.memory_type.as_str(),
40 "title": m.title,
41 "content": m.content,
42 "content_preview": preview(&m.content),
43 "tags": m.tags.join(","),
44 "importance": m.importance as f64,
45 "status": "active",
46 "source_session_id": m.source_session_id.map(|u| u.to_string()),
47 "source_run_id": m.source_run_id.map(|u| u.to_string()),
48 "embedding": embedding,
49 "created_at": now,
50 "updated_at": now,
51 "valid_at": m.valid_at.as_deref().unwrap_or(now),
52 "invalid_at": Value::Null,
53 "superseded_by": Value::Null,
54 "provenance": m.provenance.as_ref().map(|p| p.to_string()),
55 "topic_key": m.topic_key,
56 })
57}
58
59pub fn edge_row(
61 src: &str,
62 dst: &str,
63 rel_type: &str,
64 realm: &str,
65 target_namespace: Option<&str>,
66 props: Option<&Value>,
67 now: &str,
68) -> Value {
69 json!({
70 "id": edge_id(src, dst, rel_type),
71 "src": src,
72 "dst": dst,
73 "type": rel_type,
74 "realm": realm,
75 "target_namespace": target_namespace,
76 "props": props.map(|p| p.to_string()),
77 "created_at": now,
78 })
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::types::{CreateMemory, MemoryType};
85
86 #[test]
87 fn preview_truncates_long_content() {
88 let long = "x".repeat(1000);
89 let p = preview(&long);
90 assert!(p.chars().count() <= PREVIEW_CHARS + 1);
91 assert!(p.ends_with('…'));
92 }
93
94 #[test]
95 fn node_row_carries_embedding_array_and_ids() {
96 let id = Uuid::nil();
97 let mut m = CreateMemory::new("hello world");
98 m.memory_type = MemoryType::Decision;
99 m.tags = vec!["a".into(), "b".into()];
100 let row = node_row(&id, &m, &[0.5, 0.25, 0.125], "2026-05-31T00:00:00Z");
102 assert_eq!(row["id"], json!("memory:00000000-0000-0000-0000-000000000000"));
103 assert_eq!(row["memory_type"], json!("decision"));
104 assert_eq!(row["tags"], json!("a,b"));
105 assert_eq!(row["embedding"], json!([0.5, 0.25, 0.125]));
106 assert_eq!(row["status"], json!("active"));
107 }
108
109 #[test]
110 fn edge_id_is_stable() {
111 assert_eq!(
112 edge_id("memory:1", "default::repo:x", "REFERENCES"),
113 "memory:1->default::repo:x:REFERENCES"
114 );
115 }
116}