1use async_graphql::{Context, EmptySubscription, Object, Result, Schema, SimpleObject};
6use ipfrs_core::Cid;
7use ipfrs_semantic::{QueryFilter, SemanticRouter};
8use ipfrs_storage::{BlockStoreTrait, SledBlockStore};
9use ipfrs_tensorlogic::{Predicate, TensorLogicStore, Term};
10use std::sync::Arc;
11
12pub type IpfrsSchema = Schema<QueryRoot, MutationRoot, EmptySubscription>;
14
15pub struct QueryRoot;
17
18pub struct MutationRoot;
20
21#[derive(SimpleObject, Clone)]
23pub struct BlockInfo {
24 pub cid: String,
26 pub size: u64,
28 pub data: Option<String>,
30}
31
32#[derive(SimpleObject, Clone)]
34pub struct SemanticSearchResult {
35 pub cid: String,
37 pub score: f32,
39}
40
41#[derive(SimpleObject, Clone)]
43pub struct InferenceResult {
44 pub solution_count: usize,
46 pub solutions: String,
48}
49
50#[derive(SimpleObject, Clone)]
52pub struct ProofInfo {
53 pub exists: bool,
55 pub cid: Option<String>,
57 pub goal: String,
59}
60
61#[derive(SimpleObject, Clone)]
63pub struct RouterStats {
64 pub num_vectors: usize,
66 pub dimension: usize,
68 pub metric: String,
70}
71
72#[derive(SimpleObject, Clone)]
74pub struct KbStats {
75 pub num_facts: usize,
77 pub num_rules: usize,
79}
80
81#[Object]
82impl QueryRoot {
83 async fn block(&self, ctx: &Context<'_>, cid: String) -> Result<Option<BlockInfo>> {
85 let store = ctx.data::<Arc<SledBlockStore>>()?;
86
87 let cid_parsed = cid
88 .parse::<Cid>()
89 .map_err(|e| format!("Invalid CID: {}", e))?;
90
91 match store.get(&cid_parsed).await? {
92 Some(block) => {
93 let data_base64 = base64::Engine::encode(
94 &base64::engine::general_purpose::STANDARD,
95 block.data(),
96 );
97
98 Ok(Some(BlockInfo {
99 cid: cid.clone(),
100 size: block.size(),
101 data: Some(data_base64),
102 }))
103 }
104 None => Ok(None),
105 }
106 }
107
108 async fn has_block(&self, ctx: &Context<'_>, cid: String) -> Result<bool> {
110 let store = ctx.data::<Arc<SledBlockStore>>()?;
111
112 let cid_parsed = cid
113 .parse::<Cid>()
114 .map_err(|e| format!("Invalid CID: {}", e))?;
115
116 Ok(store.has(&cid_parsed).await?)
117 }
118
119 async fn block_stats(&self, ctx: &Context<'_>, cid: String) -> Result<Option<BlockInfo>> {
121 let store = ctx.data::<Arc<SledBlockStore>>()?;
122
123 let cid_parsed = cid
124 .parse::<Cid>()
125 .map_err(|e| format!("Invalid CID: {}", e))?;
126
127 match store.get(&cid_parsed).await? {
128 Some(block) => {
129 Ok(Some(BlockInfo {
130 cid: cid.clone(),
131 size: block.size(),
132 data: None, }))
134 }
135 None => Ok(None),
136 }
137 }
138
139 async fn semantic_search(
141 &self,
142 ctx: &Context<'_>,
143 query: Vec<f32>,
144 k: Option<usize>,
145 min_score: Option<f32>,
146 ) -> Result<Vec<SemanticSearchResult>> {
147 let router = ctx.data::<Arc<SemanticRouter>>()?;
148
149 let k = k.unwrap_or(10);
150
151 let mut filter = QueryFilter::default();
152 if let Some(min) = min_score {
153 filter.min_score = Some(min);
154 }
155 filter.max_results = Some(k);
156
157 let results = router.query_with_filter(&query, k, filter).await?;
158
159 Ok(results
160 .into_iter()
161 .map(|r| SemanticSearchResult {
162 cid: r.cid.to_string(),
163 score: r.score,
164 })
165 .collect())
166 }
167
168 async fn semantic_stats(&self, ctx: &Context<'_>) -> Result<RouterStats> {
170 let router = ctx.data::<Arc<SemanticRouter>>()?;
171 let stats = router.stats();
172
173 let metric = match stats.metric {
174 ipfrs_semantic::DistanceMetric::Cosine => "cosine",
175 ipfrs_semantic::DistanceMetric::L2 => "l2",
176 ipfrs_semantic::DistanceMetric::DotProduct => "dotproduct",
177 };
178
179 Ok(RouterStats {
180 num_vectors: stats.num_vectors,
181 dimension: stats.dimension,
182 metric: metric.to_string(),
183 })
184 }
185
186 async fn infer(
188 &self,
189 ctx: &Context<'_>,
190 predicate: String,
191 terms: Vec<String>,
192 ) -> Result<InferenceResult> {
193 let tensorlogic = ctx.data::<Arc<TensorLogicStore<SledBlockStore>>>()?;
194
195 let parsed_terms: Vec<Term> = terms
197 .iter()
198 .map(|t| {
199 if t.starts_with('?') || t.chars().next().unwrap().is_uppercase() {
200 Term::Var(t.to_string())
201 } else {
202 Term::Const(ipfrs_tensorlogic::Constant::String(t.to_string()))
203 }
204 })
205 .collect();
206
207 let goal = Predicate::new(predicate, parsed_terms);
208 let solutions = tensorlogic.infer(&goal)?;
209
210 let solutions_json = serde_json::to_string(&solutions)
211 .map_err(|e| format!("Failed to serialize solutions: {}", e))?;
212
213 Ok(InferenceResult {
214 solution_count: solutions.len(),
215 solutions: solutions_json,
216 })
217 }
218
219 async fn prove(
221 &self,
222 ctx: &Context<'_>,
223 predicate: String,
224 terms: Vec<String>,
225 ) -> Result<ProofInfo> {
226 let tensorlogic = ctx.data::<Arc<TensorLogicStore<SledBlockStore>>>()?;
227
228 let parsed_terms: Vec<Term> = terms
230 .iter()
231 .map(|t| {
232 if t.starts_with('?') || t.chars().next().unwrap().is_uppercase() {
233 Term::Var(t.to_string())
234 } else {
235 Term::Const(ipfrs_tensorlogic::Constant::String(t.to_string()))
236 }
237 })
238 .collect();
239
240 let goal = Predicate::new(predicate.clone(), parsed_terms);
241
242 match tensorlogic.prove(&goal)? {
243 Some(proof) => {
244 let proof_cid = tensorlogic.store_proof(&proof).await?;
245 Ok(ProofInfo {
246 exists: true,
247 cid: Some(proof_cid.to_string()),
248 goal: predicate,
249 })
250 }
251 None => Ok(ProofInfo {
252 exists: false,
253 cid: None,
254 goal: predicate,
255 }),
256 }
257 }
258
259 async fn kb_stats(&self, ctx: &Context<'_>) -> Result<KbStats> {
261 let tensorlogic = ctx.data::<Arc<TensorLogicStore<SledBlockStore>>>()?;
262 let stats = tensorlogic.kb_stats();
263
264 Ok(KbStats {
265 num_facts: stats.num_facts,
266 num_rules: stats.num_rules,
267 })
268 }
269
270 async fn version(&self) -> Result<String> {
272 Ok(env!("CARGO_PKG_VERSION").to_string())
273 }
274}
275
276#[Object]
277impl MutationRoot {
278 async fn add_block(&self, ctx: &Context<'_>, data: String) -> Result<BlockInfo> {
280 use ipfrs_core::Block;
281
282 let store = ctx.data::<Arc<SledBlockStore>>()?;
283
284 let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, data)
286 .map_err(|e| format!("Invalid base64: {}", e))?;
287
288 let block = Block::new(bytes::Bytes::from(bytes))
290 .map_err(|e| format!("Failed to create block: {}", e))?;
291
292 let cid = *block.cid();
293 let size = block.size();
294
295 store.put(&block).await?;
297
298 Ok(BlockInfo {
299 cid: cid.to_string(),
300 size,
301 data: None,
302 })
303 }
304
305 async fn index_content(
307 &self,
308 ctx: &Context<'_>,
309 cid: String,
310 embedding: Vec<f32>,
311 ) -> Result<bool> {
312 let router = ctx.data::<Arc<SemanticRouter>>()?;
313
314 let cid_parsed = cid
315 .parse::<Cid>()
316 .map_err(|e| format!("Invalid CID: {}", e))?;
317
318 router.add(&cid_parsed, &embedding)?;
319
320 Ok(true)
321 }
322
323 async fn add_fact(
325 &self,
326 ctx: &Context<'_>,
327 predicate: String,
328 terms: Vec<String>,
329 ) -> Result<bool> {
330 let tensorlogic = ctx.data::<Arc<TensorLogicStore<SledBlockStore>>>()?;
331
332 let parsed_terms: Vec<Term> = terms
334 .iter()
335 .map(|t| Term::Const(ipfrs_tensorlogic::Constant::String(t.to_string())))
336 .collect();
337
338 let fact = Predicate::new(predicate, parsed_terms);
339 tensorlogic.add_fact(fact)?;
340
341 Ok(true)
342 }
343
344 async fn add_rule(&self, ctx: &Context<'_>, datalog: String) -> Result<bool> {
346 let tensorlogic = ctx.data::<Arc<TensorLogicStore<SledBlockStore>>>()?;
347
348 let rule = ipfrs_tensorlogic::parse_rule(&datalog)
350 .map_err(|e| format!("Failed to parse rule: {}", e))?;
351
352 tensorlogic.add_rule(rule)?;
353
354 Ok(true)
355 }
356
357 async fn delete_block(&self, ctx: &Context<'_>, cid: String) -> Result<bool> {
359 let store = ctx.data::<Arc<SledBlockStore>>()?;
360
361 let cid_parsed = cid
362 .parse::<Cid>()
363 .map_err(|e| format!("Invalid CID: {}", e))?;
364
365 store.delete(&cid_parsed).await?;
366
367 Ok(true)
368 }
369}
370
371pub fn create_schema(
373 store: Arc<SledBlockStore>,
374 semantic: Option<Arc<SemanticRouter>>,
375 tensorlogic: Option<Arc<TensorLogicStore<SledBlockStore>>>,
376) -> IpfrsSchema {
377 let mut schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription).data(store);
378
379 if let Some(router) = semantic {
380 schema = schema.data(router);
381 }
382
383 if let Some(tl) = tensorlogic {
384 schema = schema.data(tl);
385 }
386
387 schema.finish()
388}