1use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15use std::sync::Arc;
16use std::sync::atomic::AtomicU64;
17
18use axum::{
19 extract::{Path as AxPath, State, Query as AxQuery},
20 http::{HeaderMap, StatusCode},
21 response::{IntoResponse, Response, sse::{Event, KeepAlive, Sse}},
22 routing::{delete, get, post},
23 Json, Router,
24};
25use dashmap::DashMap;
26use serde::Deserialize;
27use serde_json::{json, Value};
28use tokio::sync::{broadcast, RwLock};
29use tokio_stream::wrappers::BroadcastStream;
30use tokio_stream::StreamExt as _;
31
32use crate::db::Db;
33use crate::nql;
34use crate::store::Node;
35
36const LOG_CHANNEL_CAP: usize = 512;
39const SUB_CHANNEL_CAP: usize = 256;
40
41type SubKey = (String, u64); type SubVal = (String, String, broadcast::Sender<String>); macro_rules! nlog {
51 ($tx:expr, $($arg:tt)*) => {{
52 let line = format!($($arg)*);
53 println!("{}", line);
54 let _ = $tx.send(line);
55 }};
56}
57
58#[derive(Clone)]
61pub struct Manager {
62 inner: Arc<RwLock<ManagerInner>>,
63 pub token: Option<String>,
64 pub log_tx: broadcast::Sender<String>,
66 subs: Arc<DashMap<SubKey, SubVal>>,
68 sub_ctr: Arc<AtomicU64>,
69 #[cfg(feature = "cast")]
73 pub caster: Option<crate::cast::Caster>,
74}
75
76struct ManagerInner {
77 data_dir: PathBuf,
78 dbs: HashMap<String, Arc<Db>>,
79 tmk: Option<[u8; 32]>,
80 memory_mode: bool,
81}
82
83impl Manager {
84 pub fn new(data_dir: &Path, tmk: Option<[u8; 32]>, token: Option<String>, memory_mode: bool) -> Self {
85 let (log_tx, _) = broadcast::channel(LOG_CHANNEL_CAP);
86 Self {
87 inner: Arc::new(RwLock::new(ManagerInner {
88 data_dir: data_dir.to_path_buf(),
89 dbs: HashMap::new(),
90 tmk,
91 memory_mode,
92 })),
93 token,
94 log_tx,
95 #[cfg(feature = "cast")]
96 caster: None,
97 subs: Arc::new(DashMap::new()),
98 sub_ctr: Arc::new(AtomicU64::new(1)),
99 }
100 }
101
102 fn subscribe(&self, db: &str, nql: String) -> (u64, broadcast::Receiver<String>) {
104 use std::sync::atomic::Ordering;
105 let id = self.sub_ctr.fetch_add(1, Ordering::Relaxed);
106 let (tx, rx) = broadcast::channel(SUB_CHANNEL_CAP);
107 self.subs.insert((db.to_string(), id), (nql, String::new(), tx));
108 (id, rx)
109 }
110
111 fn unsubscribe(&self, db: &str, sub_id: u64) {
113 self.subs.remove(&(db.to_string(), sub_id));
114 }
115
116 fn notify_subscribers(&self, db: &str, db_arc: &Arc<crate::db::Db>) {
118 let keys: Vec<SubKey> = self.subs.iter()
119 .filter(|e| e.key().0 == db)
120 .map(|e| e.key().clone())
121 .collect();
122
123 for key in keys {
124 if let Some(mut entry) = self.subs.get_mut(&key) {
125 let (nql, last_hash, tx) = entry.value_mut();
126 let rows = match crate::nql::query(db_arc, nql) {
128 Ok((rows, _)) => rows,
129 Err(_) => continue,
130 };
131 let new_hash = format!("{:?}", rows.iter().map(|r| r.to_string()).collect::<Vec<_>>());
133 if new_hash == *last_hash { continue; }
134 *last_hash = new_hash;
135 let event = json!({
137 "sub_id": key.1,
138 "db": &key.0,
139 "nql": nql.as_str(),
140 "rows": rows,
141 "count": rows.len(),
142 });
143 let _ = tx.send(event.to_string());
144 }
145 }
146 }
147
148 pub async fn open_all(&self) -> anyhow::Result<()> {
150 let (data_dir, tmk, memory_mode) = {
151 let inner = self.inner.read().await;
152 (inner.data_dir.clone(), inner.tmk, inner.memory_mode)
153 };
154 if memory_mode { return Ok(()); }
156 if !data_dir.exists() {
157 std::fs::create_dir_all(&data_dir)?;
158 return Ok(());
159 }
160 let mut names = vec![];
161 for entry in std::fs::read_dir(&data_dir)? {
162 let entry = entry?;
163 if entry.file_type()?.is_dir() {
164 names.push(entry.file_name().to_string_lossy().to_string());
165 }
166 }
167 let log_tx = self.log_tx.clone();
168 let mut inner = self.inner.write().await;
169 for name in names {
170 let db_path = inner.data_dir.join(&name);
171 let dek = tmk.map(|k| crate::store::Dek::from_tmk(&k, name.as_bytes()));
172 match Db::open(&db_path, dek) {
173 Ok(db) => {
174 nlog!(log_tx, " [nedbd] opened database {:?}", name);
175 let db_arc = Arc::new(db);
176 Db::start_cold_scan(Arc::clone(&db_arc));
177 Db::start_manifest_ticker(Arc::clone(&db_arc), 1000);
179 inner.dbs.insert(name, db_arc);
180 }
181 Err(e) => nlog!(log_tx, " [nedbd] ERROR opening {:?}: {}", name, e),
182 }
183 }
184 Ok(())
185 }
186
187 async fn get_db(&self, name: &str) -> Option<Arc<Db>> {
188 self.inner.read().await.dbs.get(name).cloned()
189 }
190
191 async fn create_db(&self, name: &str) -> anyhow::Result<Arc<Db>> {
192 let (data_dir, tmk, memory_mode) = {
193 let inner = self.inner.read().await;
194 (inner.data_dir.clone(), inner.tmk, inner.memory_mode)
195 };
196 let db = if memory_mode {
197 Arc::new(Db::in_memory())
199 } else {
200 let db_path = data_dir.join(name);
201 let dek = tmk.map(|k| crate::store::Dek::from_tmk(&k, name.as_bytes()));
202 let db = Arc::new(Db::open(&db_path, dek)?);
203 Db::start_cold_scan(Arc::clone(&db));
204 Db::start_manifest_ticker(Arc::clone(&db), 1000);
205 db
206 };
207 self.inner.write().await.dbs.insert(name.to_string(), db.clone());
208 Ok(db)
209 }
210
211 async fn drop_db(&self, name: &str) -> bool {
212 let db = self.inner.write().await.dbs.remove(name);
213 if let Some(db) = db {
214 db.flush_manifest_if_dirty();
216 let data_dir = self.inner.read().await.data_dir.clone();
217 let _ = std::fs::remove_dir_all(data_dir.join(name));
218 true
219 } else {
220 false
221 }
222 }
223
224 pub async fn flush_all(&self) {
226 let inner = self.inner.read().await;
227 for db in inner.dbs.values() {
228 db.flush_all(); }
230 }
231
232 async fn names(&self) -> Vec<String> {
233 self.inner.read().await.dbs.keys().cloned().collect()
234 }
235
236 pub fn log(&self, msg: impl Into<String>) {
238 let line = msg.into();
239 println!("{}", line);
240 let _ = self.log_tx.send(line);
241 }
242
243 fn check_auth(&self, headers: &HeaderMap) -> bool {
244 match &self.token {
245 None => true,
246 Some(required) => {
247 if let Some(auth) = headers.get("authorization") {
248 if let Ok(s) = auth.to_str() {
249 return s == format!("Bearer {}", required);
250 }
251 }
252 false
253 }
254 }
255 }
256}
257
258fn err(status: StatusCode, msg: &str) -> Response {
261 (status, Json(json!({"error": msg}))).into_response()
262}
263
264fn ok(body: Value) -> Response {
265 (StatusCode::OK, Json(body)).into_response()
266}
267
268fn db_seq_head(db: &Db) -> (u64, String) {
272 let seq = db.seq.load(std::sync::atomic::Ordering::SeqCst);
273 let head = db.head();
274 (seq, head)
275}
276
277async fn health(State(mgr): State<Manager>) -> Response {
280 let names = mgr.names().await;
281 let inner = mgr.inner.read().await;
282 ok(json!({
283 "ok": true,
284 "service": "nedbd",
285 "version": env!("CARGO_PKG_VERSION"),
286 "engine": "dag",
287 "memory": inner.memory_mode,
288 "databases": names,
289 "encrypted": inner.tmk.is_some(),
290 }))
291}
292
293async fn list_databases(State(mgr): State<Manager>, headers: HeaderMap) -> Response {
294 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
295 let names = mgr.names().await;
296 let summaries: Vec<Value> = {
297 let inner = mgr.inner.read().await;
298 names.iter().map(|n| {
299 if let Some(db) = inner.dbs.get(n) {
300 let (seq, head) = db_seq_head(db);
301 json!({"name": n, "seq": seq, "head": head, "collections": db.collections()})
302 } else {
303 json!({"name": n})
304 }
305 }).collect()
306 };
307 ok(json!({"databases": summaries}))
308}
309
310#[derive(Deserialize)]
311struct CreateDbBody { name: String }
312
313async fn create_database(
314 State(mgr): State<Manager>,
315 headers: HeaderMap,
316 Json(body): Json<CreateDbBody>,
317) -> Response {
318 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
319 if body.name.is_empty() { return err(StatusCode::BAD_REQUEST, "name is required"); }
320 match mgr.create_db(&body.name).await {
321 Ok(db) => {
322 let (seq, head) = db_seq_head(&db);
323 (StatusCode::CREATED, Json(json!({"database": {"name": body.name, "seq": seq, "head": head}}))).into_response()
324 }
325 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
326 }
327}
328
329async fn get_database(
330 State(mgr): State<Manager>,
331 headers: HeaderMap,
332 AxPath(name): AxPath<String>,
333) -> Response {
334 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
335 match mgr.get_db(&name).await {
336 None => err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
337 Some(db) => {
338 let (seq, head) = db_seq_head(&db);
339 ok(json!({"name": name, "seq": seq, "head": head, "collections": db.collections()}))
340 }
341 }
342}
343
344async fn drop_database(
345 State(mgr): State<Manager>,
346 headers: HeaderMap,
347 AxPath(name): AxPath<String>,
348) -> Response {
349 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
350 let dropped = mgr.drop_db(&name).await;
351 ok(json!({"dropped": dropped}))
352}
353
354#[derive(Deserialize)]
355struct QueryBody { nql: String }
356
357#[cfg_attr(not(feature = "cast"), allow(dead_code))]
365#[derive(Deserialize)]
366struct CastBody {
367 prompt: String,
368 #[serde(default)]
372 execute: bool,
373}
374
375#[cfg(feature = "cast")]
381async fn cast_prompt(
382 State(mgr): State<Manager>,
383 headers: HeaderMap,
384 AxPath(name): AxPath<String>,
385 Json(body): Json<CastBody>,
386) -> Response {
387 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
388
389 let caster = match &mgr.caster {
390 Some(c) => c,
391 None => return err(
392 StatusCode::SERVICE_UNAVAILABLE,
393 "cast is not enabled; start nedbd with --cast (or NEDBD_CAST=1) \
394 and place model.cast in the data directory",
395 ),
396 };
397
398 let db = match mgr.get_db(&name).await {
399 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
400 Some(db) => db,
401 };
402 if body.prompt.trim().is_empty() {
403 return err(StatusCode::BAD_REQUEST, "prompt is required");
404 }
405
406 let collections = db.collections();
409 let result = caster.cast_checked(&body.prompt, &collections);
410
411 let parse_err = match nql::parse(&result.nql) {
414 Ok(_) => None,
415 Err(e) => Some(e.to_string()),
416 };
417
418 let (seq, head) = db_seq_head(&db);
419 let mut out = json!({
420 "prompt": body.prompt,
421 "nql": result.nql,
422 "valid": parse_err.is_none(),
423 "collection": result.collection,
424 "collection_known": result.collection_known,
425 "collections": collections,
426 "executed": false,
427 "seq": seq,
428 "head": head,
429 });
430
431 if let Some(d) = &result.drift {
438 out["drift"] = json!(d);
439 }
440
441 if let Some(e) = parse_err {
442 out["error"] = json!(format!("NQL error: {}", e));
445 return (StatusCode::UNPROCESSABLE_ENTITY, Json(out)).into_response();
446 }
447
448 if !result.collection_known {
449 out["error"] = json!(format!(
453 "collection {:?} does not exist in {:?}",
454 result.collection.unwrap_or_default(), name
455 ));
456 return (StatusCode::UNPROCESSABLE_ENTITY, Json(out)).into_response();
457 }
458
459 if !body.execute {
460 return ok(out);
461 }
462
463 let nql_text = out["nql"].as_str().unwrap_or("").to_string();
465 match nql::query(&db, &nql_text) {
466 Ok((rows, count)) => {
467 out["executed"] = json!(true);
468 out["rows"] = json!(rows);
469 out["count"] = json!(count);
470 ok(out)
471 }
472 Err(e) => {
473 out["error"] = json!(format!("NQL error: {}", e));
474 (StatusCode::BAD_REQUEST, Json(out)).into_response()
475 }
476 }
477}
478
479#[cfg(not(feature = "cast"))]
482async fn cast_prompt(
483 State(_mgr): State<Manager>,
484 _headers: HeaderMap,
485 AxPath(_name): AxPath<String>,
486 Json(_body): Json<CastBody>,
487) -> Response {
488 err(
489 StatusCode::NOT_IMPLEMENTED,
490 "this nedbd was built without the `cast` feature; \
491 rebuild with --features cast to enable natural-language planning",
492 )
493}
494
495async fn query_database(
496 State(mgr): State<Manager>,
497 headers: HeaderMap,
498 AxPath(name): AxPath<String>,
499 Json(body): Json<QueryBody>,
500) -> Response {
501 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
502 let db = match mgr.get_db(&name).await {
503 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
504 Some(db) => db,
505 };
506 if body.nql.trim().is_empty() {
507 return err(StatusCode::BAD_REQUEST, "nql is required");
508 }
509 match nql::query(&db, &body.nql) {
510 Ok((rows, count)) => {
511 let (seq, head) = db_seq_head(&db);
512 ok(json!({"rows": rows, "count": count, "seq": seq, "head": head}))
513 }
514 Err(e) => err(StatusCode::BAD_REQUEST, &format!("NQL error: {}", e)),
515 }
516}
517
518#[derive(Deserialize)]
519struct PutBody {
520 coll: String,
521 id: String,
522 doc: Value,
523 caused_by: Option<Vec<serde_json::Value>>,
524 valid_from: Option<String>,
525 valid_to: Option<String>,
526 #[allow(dead_code)] evidence: Option<String>,
527 #[allow(dead_code)] confidence: Option<f64>,
528 #[allow(dead_code)] client: Option<String>,
529 #[allow(dead_code)] nonce: Option<u64>,
530 #[allow(dead_code)] idem: Option<String>,
531}
532
533#[derive(Deserialize)]
534struct LinkBody {
535 frm: String,
536 rel: String,
537 to: String,
538}
539
540async fn put_document(
541 State(mgr): State<Manager>,
542 headers: HeaderMap,
543 AxPath(name): AxPath<String>,
544 Json(body): Json<PutBody>,
545) -> Response {
546 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
547 let db = match mgr.get_db(&name).await {
548 None => {
549 match mgr.create_db(&name).await {
551 Ok(db) => db,
552 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
553 }
554 }
555 Some(db) => db,
556 };
557 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
560 return err(StatusCode::SERVICE_UNAVAILABLE,
561 "database startup in progress — reads available, writes retry in a moment");
562 }
563 let caused_by: Vec<String> = body.caused_by.unwrap_or_default()
565 .into_iter()
566 .filter_map(|v| match v {
567 serde_json::Value::String(s) => Some(s),
568 serde_json::Value::Number(n) => {
569 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
570 }
571 _ => None,
572 })
573 .collect();
574 let coll = body.coll.clone();
577 let id = body.id.clone();
578 let doc = body.doc.clone();
579 let vf = body.valid_from.clone();
580 let vt = body.valid_to.clone();
581 let db2 = Arc::clone(&db);
582 let result = tokio::task::spawn_blocking(move || {
583 db2.put(&coll, &id, doc, caused_by, vf, vt)
584 }).await;
585 match result {
586 Err(join_err) => err(StatusCode::INTERNAL_SERVER_ERROR, &join_err.to_string()),
587 Ok(Err(e)) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
588 Ok(Ok(node)) => {
589 let (seq, head) = db_seq_head(&db);
590 mgr.notify_subscribers(&name, &db);
591 ok(json!({"ok": true, "doc": node_to_response(&node), "seq": seq, "head": head}))
592 }
593 }
594}
595
596fn node_to_response(node: &Node) -> Value {
597 json!({
598 "_id": node.id,
599 "_hash": node.hash,
600 "_seq": node.seq,
601 "_coll": node.coll,
602 "data": node.data,
603 })
604}
605
606async fn link_document(
607 State(mgr): State<Manager>,
608 headers: HeaderMap,
609 AxPath(name): AxPath<String>,
610 Json(body): Json<LinkBody>,
611) -> Response {
612 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
613 let db = match mgr.get_db(&name).await {
614 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
615 Some(db) => db,
616 };
617 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
618 return err(StatusCode::SERVICE_UNAVAILABLE, "startup scan in progress");
619 }
620 match db.link(&body.frm, &body.rel, &body.to) {
621 Ok(()) => {
622 let (seq, head) = db_seq_head(&db);
623 ok(json!({"ok": true, "frm": body.frm, "rel": body.rel, "to": body.to, "seq": seq, "head": head}))
624 }
625 Err(e) => err(StatusCode::BAD_REQUEST, &e.to_string()),
626 }
627}
628
629async fn get_document(
653 State(mgr): State<Manager>,
654 headers: HeaderMap,
655 AxPath((name, coll, id)): AxPath<(String, String, String)>,
656 AxQuery(q): AxQuery<GetRowQuery>,
657) -> Response {
658 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
659 let db = match mgr.get_db(&name).await {
660 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
661 Some(db) => db,
662 };
663 let node = match q.as_of {
664 Some(seq) => db.get_as_of(&coll, &id, seq),
665 None => db.get(&coll, &id),
666 };
667 let (seq, head) = db_seq_head(&db);
678 let row = match node {
679 None => Value::Null,
680 Some(n) => crate::nql::node_to_json(&n),
681 };
682 ok(json!({"row": row, "seq": seq, "head": head}))
683}
684
685#[derive(Deserialize, Default)]
686struct GetRowQuery {
687 as_of: Option<u64>,
688}
689
690async fn delete_document(
691 State(mgr): State<Manager>,
692 headers: HeaderMap,
693 AxPath((name, coll, id)): AxPath<(String, String, String)>,
694) -> Response {
695 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
696 let db = match mgr.get_db(&name).await {
697 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
698 Some(db) => db,
699 };
700 let existed = match db.delete(&coll, &id) {
703 Ok(v) => v,
704 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
705 };
706 let (seq, head) = db_seq_head(&db);
707 ok(json!({"ok": existed, "seq": seq, "head": head}))
708}
709
710#[derive(Deserialize)]
711struct BatchOp {
712 op: String,
713 coll: Option<String>,
714 id: Option<String>,
715 doc: Option<Value>,
716 caused_by: Option<Vec<serde_json::Value>>,
717}
718#[derive(Deserialize)]
719struct BatchBody { ops: Vec<BatchOp> }
720
721async fn batch_operations(
722 State(mgr): State<Manager>,
723 headers: HeaderMap,
724 AxPath(name): AxPath<String>,
725 Json(body): Json<BatchBody>,
726) -> Response {
727 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
728 let db = match mgr.get_db(&name).await {
729 None => match mgr.create_db(&name).await {
730 Ok(db) => db,
731 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
732 },
733 Some(db) => db,
734 };
735
736 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
737 return err(StatusCode::SERVICE_UNAVAILABLE,
738 "database startup in progress — reads available, writes retry in a moment");
739 }
740
741 let mut put_ops = vec![];
745 let mut del_ops: Vec<(String, String)> = vec![];
746 let mut op_order: Vec<(&str, usize)> = vec![]; for op in &body.ops {
749 let t = op.op.to_lowercase();
750 match t.as_str() {
751 "put" => {
752 let caused_by: Vec<String> = op.caused_by.clone().unwrap_or_default()
754 .into_iter()
755 .filter_map(|v| match v {
756 serde_json::Value::String(s) => Some(s),
757 serde_json::Value::Number(n) => {
758 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
759 }
760 _ => None,
761 })
762 .collect();
763 op_order.push(("put", put_ops.len()));
764 put_ops.push((
765 op.coll.clone().unwrap_or_default(),
766 op.id.clone().unwrap_or_default(),
767 op.doc.clone().unwrap_or(json!({})),
768 caused_by,
769 None::<String>,
770 None::<String>,
771 ));
772 }
773 "del" | "delete" => {
774 op_order.push(("del", del_ops.len()));
775 del_ops.push((
776 op.coll.clone().unwrap_or_default(),
777 op.id.clone().unwrap_or_default(),
778 ));
779 }
780 _ => { op_order.push(("unknown", 0)); }
781 }
782 }
783
784 let put_results = if put_ops.is_empty() {
786 vec![]
787 } else {
788 match db.put_batch(put_ops) {
789 Ok(nodes) => nodes.into_iter().map(|n| json!({"op":"put","id":n.id,"seq":n.seq,"hash":n.hash})).collect(),
790 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
791 }
792 };
793
794 let del_results: Vec<serde_json::Value> = del_ops.iter().map(|(coll, id)| {
796 match db.delete(coll, id) {
797 Ok(existed) => json!({"op":"del","id":id,"ok":existed}),
798 Err(e) => json!({"op":"del","id":id,"error":e.to_string()}),
799 }
800 }).collect();
801
802 let mut results = vec![];
804 for (kind, idx) in &op_order {
805 let r = match *kind {
806 "put" => put_results.get(*idx).cloned().unwrap_or(json!({"op":"put","error":"missing"})),
807 "del" => del_results.get(*idx).cloned().unwrap_or(json!({"op":"del","error":"missing"})),
808 _ => json!({"op": kind, "error": "unknown op"}),
809 };
810 results.push(r);
811 }
812 let (seq, head) = db_seq_head(&db);
813 mgr.notify_subscribers(&name, &db);
815 ok(json!({"results": results, "count": results.len(), "seq": seq, "head": head}))
816}
817
818#[derive(Deserialize)]
819struct IndexBody { coll: String, field: String, kind: Option<String> }
820
821async fn create_index(
822 State(mgr): State<Manager>,
823 headers: HeaderMap,
824 AxPath(name): AxPath<String>,
825 Json(body): Json<IndexBody>,
826) -> Response {
827 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
828 let db = match mgr.get_db(&name).await {
829 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
830 Some(db) => db,
831 };
832 let kind = body.kind.as_deref().unwrap_or("eq");
833 match kind {
834 "sorted" | "eq" => {
835 db.create_sorted_index(&body.coll, &body.field);
836 ok(json!({"ok": true, "coll": body.coll, "field": body.field, "kind": kind}))
837 }
838 _ => err(StatusCode::BAD_REQUEST, &format!("unknown index kind: {}", kind)),
839 }
840}
841
842async fn verify_database(
843 State(mgr): State<Manager>,
844 headers: HeaderMap,
845 AxPath(name): AxPath<String>,
846) -> Response {
847 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
848 let db = match mgr.get_db(&name).await {
849 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
850 Some(db) => db,
851 };
852 let (ok_count, tampered) = db.verify();
853 let (seq, head) = db_seq_head(&db);
854 ok(json!({
855 "ok": tampered.is_empty(),
856 "seq": seq,
857 "head": head,
858 "tamper_evident": true,
859 "objects_checked": ok_count,
860 "tampered": tampered,
861 }))
862}
863
864async fn root_current(
871 State(mgr): State<Manager>,
872 headers: HeaderMap,
873 AxPath(name): AxPath<String>,
874) -> Response {
875 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
876 let db = match mgr.get_db(&name).await {
877 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
878 Some(db) => db,
879 };
880 match db.state_root() {
881 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
882 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e),
883 }
884}
885
886async fn root_list(
887 State(mgr): State<Manager>,
888 headers: HeaderMap,
889 AxPath(name): AxPath<String>,
890) -> Response {
891 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
892 let db = match mgr.get_db(&name).await {
893 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
894 Some(db) => db,
895 };
896 ok(json!({
897 "roots": db.list_roots(),
898 "history_floor": db.history_floor(),
899 }))
900}
901
902async fn root_create(
903 State(mgr): State<Manager>,
904 headers: HeaderMap,
905 AxPath(name): AxPath<String>,
906 AxQuery(q): AxQuery<std::collections::HashMap<String, String>>,
907) -> Response {
908 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
909 let db = match mgr.get_db(&name).await {
910 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
911 Some(db) => db,
912 };
913 let at: Option<u64> = match q.get("at").map(|v| v.parse::<u64>()) {
917 None => None,
918 Some(Ok(v)) => Some(v),
919 Some(Err(_)) => return err(StatusCode::BAD_REQUEST, "at must be a sequence number"),
920 };
921 let made = match at {
922 Some(seq) => db.create_root_at(seq),
923 None => db.create_root(),
924 };
925 match made {
926 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
927 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
928 }
929}
930
931async fn root_verify(
932 State(mgr): State<Manager>,
933 headers: HeaderMap,
934 AxPath((name, seq)): AxPath<(String, u64)>,
935) -> Response {
936 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
937 let db = match mgr.get_db(&name).await {
938 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
939 Some(db) => db,
940 };
941 let v = db.verify_root(seq);
942 ok(json!({
947 "verified": v.is_verified(),
948 "mismatch": v.is_mismatch(),
949 "exit_code": v.exit_code(),
950 "result": v,
951 }))
952}
953
954async fn checkpoint(
955 State(mgr): State<Manager>,
956 headers: HeaderMap,
957 AxPath(name): AxPath<String>,
958) -> Response {
959 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
960 let db = match mgr.get_db(&name).await {
961 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
962 Some(db) => db,
963 };
964 let (seq, head) = db_seq_head(&db);
965 ok(json!({"ok": true, "head": head, "seq": seq}))
967}
968
969#[derive(Deserialize)]
970struct LogQuery { limit: Option<usize> }
971
972async fn get_log(
973 State(mgr): State<Manager>,
974 headers: HeaderMap,
975 AxPath(name): AxPath<String>,
976 AxQuery(q): AxQuery<LogQuery>,
977) -> Response {
978 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
979 let db = match mgr.get_db(&name).await {
980 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
981 Some(db) => db,
982 };
983 let limit = q.limit.unwrap_or(50);
984 let mut log_entries: Vec<Value> = db.objects.all_hashes()
986 .filter_map(|h| db.objects.read(&h).ok())
987 .take(limit)
988 .map(|n| json!({
989 "seq": n.seq, "coll": n.coll, "id": n.id,
990 "hash": n.hash, "ts": n.ts, "op": "put"
991 }))
992 .collect();
993 log_entries.sort_by(|a, b|
994 b["seq"].as_u64().cmp(&a["seq"].as_u64())
995 );
996 log_entries.truncate(limit);
997 let (seq, head) = db_seq_head(&db);
998 ok(json!({"log": log_entries, "seq": seq, "head": head}))
999}
1000
1001async fn tip_database(
1007 State(mgr): State<Manager>,
1008 headers: HeaderMap,
1009 AxPath(name): AxPath<String>,
1010) -> Response {
1011 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1012 let db = match mgr.get_db(&name).await {
1013 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1014 Some(db) => db,
1015 };
1016 let (seq, head) = db_seq_head(&db);
1017 let tip = db.tip().map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1018 ok(json!({"tip": tip, "seq": seq, "head": head}))
1019}
1020
1021async fn tip_collection_database(
1023 State(mgr): State<Manager>,
1024 headers: HeaderMap,
1025 AxPath((name, coll)): AxPath<(String, String)>,
1026) -> Response {
1027 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1028 let db = match mgr.get_db(&name).await {
1029 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1030 Some(db) => db,
1031 };
1032 let (seq, head) = db_seq_head(&db);
1033 let tip = db.tip_collection(&coll).map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1034 ok(json!({"coll": coll, "tip": tip, "seq": seq, "head": head}))
1035}
1036
1037#[derive(Deserialize)]
1038struct SinceQuery { after_seq: Option<u64>, limit: Option<usize> }
1039
1040async fn since_database(
1041 State(mgr): State<Manager>,
1042 headers: HeaderMap,
1043 AxPath(name): AxPath<String>,
1044 AxQuery(q): AxQuery<SinceQuery>,
1045) -> Response {
1046 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1047 let db = match mgr.get_db(&name).await {
1048 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1049 Some(db) => db,
1050 };
1051 let after = q.after_seq.unwrap_or(0);
1052 let b = db.since(after, q.limit.unwrap_or(0));
1053 let nodes: Vec<Value> = b.nodes.iter()
1054 .map(|n| serde_json::to_value(n).unwrap_or(Value::Null))
1055 .collect();
1056 let (seq, head) = db_seq_head(&db);
1057 ok(json!({
1058 "nodes": nodes, "count": nodes.len(),
1059 "from_seq": b.from_seq, "to_seq": b.to_seq, "head_seq": b.head_seq, "has_more": b.has_more,
1060 "seq": seq, "head": head
1061 }))
1062}
1063
1064async fn status_database(
1067 State(mgr): State<Manager>,
1068 headers: HeaderMap,
1069 AxPath(name): AxPath<String>,
1070) -> Response {
1071 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1072 let db = match mgr.get_db(&name).await {
1073 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1074 Some(db) => db,
1075 };
1076 let s = db.scan_status();
1077 ok(json!({
1078 "ok": true,
1079 "scan_complete": s.scan_complete,
1080 "tip_seq": s.tip_seq,
1081 "indexed_seq_min": s.indexed_seq_min,
1082 "indexed_seq_max": s.indexed_seq_max,
1083 "indexed_count": s.indexed_count
1084 }))
1085}
1086
1087#[derive(Deserialize)]
1090struct SubscribeBody { nql: String }
1091
1092async fn subscribe_query(
1093 State(mgr): State<Manager>,
1094 headers: HeaderMap,
1095 AxPath(name): AxPath<String>,
1096 Json(body): Json<SubscribeBody>,
1097) -> Response {
1098 if !mgr.check_auth(&headers) {
1099 return err(StatusCode::UNAUTHORIZED, "unauthorized");
1100 }
1101 let db = match mgr.get_db(&name).await {
1102 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1103 Some(db) => db,
1104 };
1105
1106 let (sub_id, rx) = mgr.subscribe(&name, body.nql.clone());
1107
1108 if let Ok((rows, _)) = crate::nql::query(&db, &body.nql) {
1110 let init = json!({
1111 "sub_id": sub_id,
1112 "db": &name,
1113 "nql": &body.nql,
1114 "rows": rows,
1115 "count": rows.len(),
1116 "event": "initial",
1117 });
1118 if let Some(mut entry) = mgr.subs.get_mut(&(name.clone(), sub_id)) {
1120 let hash = format!("{:?}", rows);
1121 entry.value_mut().1 = hash;
1122 }
1123 if let Some(entry) = mgr.subs.get(&(name.clone(), sub_id)) {
1125 let _ = entry.value().2.send(init.to_string());
1126 }
1127 }
1128
1129 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1130 match msg {
1131 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1132 Err(_) => None,
1133 }
1134 });
1135 Sse::new(stream)
1136 .keep_alive(KeepAlive::default())
1137 .into_response()
1138}
1139
1140async fn unsubscribe_query(
1141 State(mgr): State<Manager>,
1142 headers: HeaderMap,
1143 AxPath((name, sub_id)): AxPath<(String, u64)>,
1144) -> Response {
1145 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1146 mgr.unsubscribe(&name, sub_id);
1147 ok(json!({"ok": true, "sub_id": sub_id}))
1148}
1149
1150async fn log_events(State(mgr): State<Manager>) -> Sse<impl futures_core::Stream<Item = Result<Event, std::convert::Infallible>>> {
1153 let rx = mgr.log_tx.subscribe();
1154 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1155 match msg {
1156 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1157 Err(_) => None, }
1159 });
1160 Sse::new(stream).keep_alive(KeepAlive::default())
1161}
1162
1163pub fn router(mgr: Manager) -> Router {
1166 Router::new()
1167 .route("/health", get(health))
1168 .route("/events", get(log_events))
1169 .route("/v1/databases", get(list_databases).post(create_database))
1170 .route("/v1/databases/:name", get(get_database).delete(drop_database))
1171 .route("/v1/databases/:name/query", post(query_database))
1172 .route("/v1/databases/:name/cast", post(cast_prompt))
1173 .route("/v1/databases/:name/put", post(put_document))
1174 .route("/v1/databases/:name/link", post(link_document))
1175 .route("/v1/databases/:name/rows/:coll/:id",
1178 get(get_document).delete(delete_document))
1179 .route("/v1/databases/:name/batch", post(batch_operations))
1180 .route("/v1/databases/:name/index", post(create_index))
1181 .route("/v1/databases/:name/verify", get(verify_database))
1182 .route("/v1/databases/:name/root", get(root_current).post(root_create))
1183 .route("/v1/databases/:name/roots", get(root_list))
1184 .route("/v1/databases/:name/roots/:seq/verify", get(root_verify))
1185 .route("/v1/databases/:name/checkpoint", post(checkpoint))
1186 .route("/v1/databases/:name/log", get(get_log))
1187 .route("/v1/databases/:name/tip", get(tip_database))
1188 .route("/v1/databases/:name/collections/:coll/tip", get(tip_collection_database))
1189 .route("/v1/databases/:name/since", get(since_database))
1190 .route("/v1/databases/:name/status", get(status_database))
1191 .route("/v1/databases/:name/subscribe", post(subscribe_query))
1192 .route("/v1/databases/:name/subscribe/:sub_id", delete(unsubscribe_query))
1193 .with_state(mgr)
1194}
1195
1196impl crate::pgwire::DbResolver for Manager {
1201 fn resolve(&self, name: &str) -> Option<Arc<Db>> {
1202 let inner = self.inner.blocking_read();
1205 if name.is_empty() {
1209 if inner.dbs.len() == 1 {
1210 return inner.dbs.values().next().cloned();
1211 }
1212 return None;
1213 }
1214 inner.dbs.get(name).cloned()
1215 }
1216 fn token(&self) -> Option<String> {
1217 self.token.clone()
1218 }
1219}
1220
1221pub async fn run(host: &str, port: u16, data_dir: &str, tmk: Option<[u8; 32]>, token: Option<String>, memory_mode: bool) -> anyhow::Result<()> {
1222 #[cfg(feature = "cast")]
1227 let mut mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1228 #[cfg(not(feature = "cast"))]
1229 let mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1230
1231 mgr.open_all().await?;
1232
1233 #[cfg(feature = "cast")]
1237 {
1238 let want = std::env::var("NEDBD_CAST").map(|v| v == "1").unwrap_or(false);
1239 if want {
1240 match crate::cast::Caster::load(Path::new(data_dir)) {
1241 Ok(c) => {
1242 println!(" cast enabled — {:.2}M params, vocab {}, {}",
1243 c.n_params() as f64 / 1e6, c.vocab_size(), c.source());
1244 mgr.caster = Some(c);
1245 }
1246 Err(e) => {
1247 eprintln!(" cast DISABLED — {}", e);
1248 }
1249 }
1250 }
1251 }
1252 #[cfg(feature = "cast")]
1255 let mgr = mgr;
1256
1257 let has_token = mgr.token.is_some();
1258 let mgr_for_shutdown = mgr.clone();
1259 if let Ok(raw) = std::env::var("NEDBD_PG_PORT") {
1264 match raw.trim().parse::<u16>() {
1265 Ok(pg_port) if pg_port > 0 => {
1266 let pg_host = host.to_string();
1267 let resolver: Arc<dyn crate::pgwire::DbResolver> = Arc::new(mgr.clone());
1268 tokio::spawn(async move {
1269 if let Err(e) = crate::pgwire::run(&pg_host, pg_port, resolver).await {
1270 eprintln!(" [pgwire] listener stopped: {}", e);
1271 }
1272 });
1273 }
1274 _ => eprintln!(" [pgwire] ignoring NEDBD_PG_PORT={:?} — not a valid port", raw),
1275 }
1276 }
1277
1278 let app = router(mgr);
1279 let addr = format!("{}:{}", host, port).parse::<std::net::SocketAddr>()?;
1280 let banner = format!(r#"
1281 ◆
1282 ╱ ╲ N E D B · DAG ENGINE {}
1283 ◆ ◆ ─────────────────────────────────────────────
1284 ╱ ╲ ╱ ╲ content-addressed · tamper-evident · causal
1285 ◆ ◆ ◆ bi-temporal · replay-protected · encrypted
1286 ╱ ╲ ╱ ╲ ╱ ╲
1287 ◆ ◆ ◆ ◆ © INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)
1288 ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ interchained.org · hyperagent.com/refer/J2G6TCD7
1289
1290 ─────────────────────────────────────────────────────────────
1291 listen http://{}
1292 data {}
1293 enc {}
1294 token {}
1295 memory {}
1296 ─────────────────────────────────────────────────────────────
1297"#,
1298 env!("CARGO_PKG_VERSION"),
1299 addr,
1300 data_dir,
1301 if tmk.is_some() { "AES-256-GCM" } else { "off" },
1302 if has_token { "on" } else { "off (set NEDBD_TOKEN to require auth)" },
1303 if memory_mode { "yes — all data lost on exit (NEDBD_MEMORY=1)" } else { "no — durable DAG on disk" }
1304 );
1305 print!("{}", banner);
1306
1307 let listener = tokio::net::TcpListener::bind(addr).await?;
1308
1309 let mgr_hourly = mgr_for_shutdown.clone();
1313 tokio::spawn(async move {
1314 loop {
1315 let now_secs = std::time::SystemTime::now()
1317 .duration_since(std::time::UNIX_EPOCH)
1318 .map(|d| d.as_secs()).unwrap_or(0);
1319 let secs_into_hour = now_secs % 3600;
1320 let sleep_secs = 3600 - secs_into_hour;
1321 tokio::time::sleep(tokio::time::Duration::from_secs(sleep_secs)).await;
1322 mgr_hourly.flush_all().await;
1323 println!(" [nedbd] hourly checkpoint — manifests flushed");
1324 }
1325 });
1326
1327 let shutdown = async {
1329 #[cfg(unix)]
1330 {
1331 use tokio::signal::unix::{signal, SignalKind};
1332 let mut sigterm = signal(SignalKind::terminate()).unwrap();
1333 let mut sigint = signal(SignalKind::interrupt()).unwrap();
1334 tokio::select! {
1335 _ = sigterm.recv() => println!(" [nedbd] SIGTERM — flushing and exiting..."),
1336 _ = sigint.recv() => println!(" [nedbd] SIGINT — flushing and exiting..."),
1337 }
1338 }
1339 #[cfg(not(unix))]
1340 {
1341 tokio::signal::ctrl_c().await.ok();
1342 println!(" [nedbd] shutting down — flushing manifests...");
1343 }
1344 };
1345
1346 axum::serve(listener, app)
1347 .tcp_nodelay(true)
1348 .with_graceful_shutdown(shutdown)
1349 .await?;
1350
1351 mgr_for_shutdown.flush_all().await;
1353 println!(" [nedbd] goodbye");
1354 Ok(())
1355}