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, "a statement is required");
508 }
509 let dialect = match crate::neql::route(&body.nql) {
521 Ok(d) => d,
522 Err(why) => return err(StatusCode::BAD_REQUEST, &why),
523 };
524 let (seq, head) = db_seq_head(&db);
525 match dialect {
526 crate::neql::Dialect::Nql => match nql::query(&db, &body.nql) {
527 Ok((rows, count)) => ok(json!({
528 "rows": rows, "count": count, "seq": seq, "head": head,
529 "dialect": "nql",
530 })),
531 Err(e) => err(StatusCode::BAD_REQUEST, &format!("NQL error: {}", e)),
532 },
533 crate::neql::Dialect::Sql => match crate::pgwire::execute_sql(&db, &body.nql, false) {
534 Ok(done) => {
535 let n = done.rows.len();
536 ok(json!({
537 "rows": done.rows, "count": n, "seq": seq, "head": head,
538 "dialect": "sql", "tag": done.tag,
539 }))
540 }
541 Err(why) => err(StatusCode::BAD_REQUEST, &format!("SQL error: {}", why)),
542 },
543 }
544}
545
546#[derive(Deserialize)]
547struct PutBody {
548 coll: String,
549 id: String,
550 doc: Value,
551 caused_by: Option<Vec<serde_json::Value>>,
552 valid_from: Option<String>,
553 valid_to: Option<String>,
554 #[allow(dead_code)] evidence: Option<String>,
555 #[allow(dead_code)] confidence: Option<f64>,
556 #[allow(dead_code)] client: Option<String>,
557 #[allow(dead_code)] nonce: Option<u64>,
558 #[allow(dead_code)] idem: Option<String>,
559}
560
561#[derive(Deserialize)]
562struct LinkBody {
563 frm: String,
564 rel: String,
565 to: String,
566}
567
568async fn put_document(
569 State(mgr): State<Manager>,
570 headers: HeaderMap,
571 AxPath(name): AxPath<String>,
572 Json(body): Json<PutBody>,
573) -> Response {
574 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
575 let db = match mgr.get_db(&name).await {
576 None => {
577 match mgr.create_db(&name).await {
579 Ok(db) => db,
580 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
581 }
582 }
583 Some(db) => db,
584 };
585 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
588 return err(StatusCode::SERVICE_UNAVAILABLE,
589 "database startup in progress — reads available, writes retry in a moment");
590 }
591 let caused_by: Vec<String> = body.caused_by.unwrap_or_default()
593 .into_iter()
594 .filter_map(|v| match v {
595 serde_json::Value::String(s) => Some(s),
596 serde_json::Value::Number(n) => {
597 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
598 }
599 _ => None,
600 })
601 .collect();
602 let coll = body.coll.clone();
605 let id = body.id.clone();
606 let doc = body.doc.clone();
607 let vf = body.valid_from.clone();
608 let vt = body.valid_to.clone();
609 let db2 = Arc::clone(&db);
610 let result = tokio::task::spawn_blocking(move || {
611 db2.put(&coll, &id, doc, caused_by, vf, vt)
612 }).await;
613 match result {
614 Err(join_err) => err(StatusCode::INTERNAL_SERVER_ERROR, &join_err.to_string()),
615 Ok(Err(e)) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
616 Ok(Ok(node)) => {
617 let (seq, head) = db_seq_head(&db);
618 mgr.notify_subscribers(&name, &db);
619 ok(json!({"ok": true, "doc": node_to_response(&node), "seq": seq, "head": head}))
620 }
621 }
622}
623
624fn node_to_response(node: &Node) -> Value {
625 json!({
626 "_id": node.id,
627 "_hash": node.hash,
628 "_seq": node.seq,
629 "_coll": node.coll,
630 "data": node.data,
631 })
632}
633
634async fn link_document(
635 State(mgr): State<Manager>,
636 headers: HeaderMap,
637 AxPath(name): AxPath<String>,
638 Json(body): Json<LinkBody>,
639) -> Response {
640 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
641 let db = match mgr.get_db(&name).await {
642 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
643 Some(db) => db,
644 };
645 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
646 return err(StatusCode::SERVICE_UNAVAILABLE, "startup scan in progress");
647 }
648 match db.link(&body.frm, &body.rel, &body.to) {
649 Ok(()) => {
650 let (seq, head) = db_seq_head(&db);
651 ok(json!({"ok": true, "frm": body.frm, "rel": body.rel, "to": body.to, "seq": seq, "head": head}))
652 }
653 Err(e) => err(StatusCode::BAD_REQUEST, &e.to_string()),
654 }
655}
656
657async fn get_document(
681 State(mgr): State<Manager>,
682 headers: HeaderMap,
683 AxPath((name, coll, id)): AxPath<(String, String, String)>,
684 AxQuery(q): AxQuery<GetRowQuery>,
685) -> Response {
686 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
687 let db = match mgr.get_db(&name).await {
688 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
689 Some(db) => db,
690 };
691 let node = match q.as_of {
692 Some(seq) => db.get_as_of(&coll, &id, seq),
693 None => db.get(&coll, &id),
694 };
695 let (seq, head) = db_seq_head(&db);
706 let row = match node {
707 None => Value::Null,
708 Some(n) => crate::nql::node_to_json(&n),
709 };
710 ok(json!({"row": row, "seq": seq, "head": head}))
711}
712
713#[derive(Deserialize, Default)]
714struct GetRowQuery {
715 as_of: Option<u64>,
716}
717
718async fn delete_document(
719 State(mgr): State<Manager>,
720 headers: HeaderMap,
721 AxPath((name, coll, id)): AxPath<(String, String, String)>,
722) -> Response {
723 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
724 let db = match mgr.get_db(&name).await {
725 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
726 Some(db) => db,
727 };
728 let existed = match db.delete(&coll, &id) {
731 Ok(v) => v,
732 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
733 };
734 let (seq, head) = db_seq_head(&db);
735 ok(json!({"ok": existed, "seq": seq, "head": head}))
736}
737
738#[derive(Deserialize)]
739struct BatchOp {
740 op: String,
741 coll: Option<String>,
742 id: Option<String>,
743 doc: Option<Value>,
744 caused_by: Option<Vec<serde_json::Value>>,
745}
746#[derive(Deserialize)]
747struct BatchBody { ops: Vec<BatchOp> }
748
749async fn batch_operations(
750 State(mgr): State<Manager>,
751 headers: HeaderMap,
752 AxPath(name): AxPath<String>,
753 Json(body): Json<BatchBody>,
754) -> Response {
755 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
756 let db = match mgr.get_db(&name).await {
757 None => match mgr.create_db(&name).await {
758 Ok(db) => db,
759 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
760 },
761 Some(db) => db,
762 };
763
764 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
765 return err(StatusCode::SERVICE_UNAVAILABLE,
766 "database startup in progress — reads available, writes retry in a moment");
767 }
768
769 let mut put_ops = vec![];
773 let mut del_ops: Vec<(String, String)> = vec![];
774 let mut op_order: Vec<(&str, usize)> = vec![]; for op in &body.ops {
777 let t = op.op.to_lowercase();
778 match t.as_str() {
779 "put" => {
780 let caused_by: Vec<String> = op.caused_by.clone().unwrap_or_default()
782 .into_iter()
783 .filter_map(|v| match v {
784 serde_json::Value::String(s) => Some(s),
785 serde_json::Value::Number(n) => {
786 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
787 }
788 _ => None,
789 })
790 .collect();
791 op_order.push(("put", put_ops.len()));
792 put_ops.push((
793 op.coll.clone().unwrap_or_default(),
794 op.id.clone().unwrap_or_default(),
795 op.doc.clone().unwrap_or(json!({})),
796 caused_by,
797 None::<String>,
798 None::<String>,
799 ));
800 }
801 "del" | "delete" => {
802 op_order.push(("del", del_ops.len()));
803 del_ops.push((
804 op.coll.clone().unwrap_or_default(),
805 op.id.clone().unwrap_or_default(),
806 ));
807 }
808 _ => { op_order.push(("unknown", 0)); }
809 }
810 }
811
812 let put_results = if put_ops.is_empty() {
814 vec![]
815 } else {
816 match db.put_batch(put_ops) {
817 Ok(nodes) => nodes.into_iter().map(|n| json!({"op":"put","id":n.id,"seq":n.seq,"hash":n.hash})).collect(),
818 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
819 }
820 };
821
822 let del_results: Vec<serde_json::Value> = del_ops.iter().map(|(coll, id)| {
824 match db.delete(coll, id) {
825 Ok(existed) => json!({"op":"del","id":id,"ok":existed}),
826 Err(e) => json!({"op":"del","id":id,"error":e.to_string()}),
827 }
828 }).collect();
829
830 let mut results = vec![];
832 for (kind, idx) in &op_order {
833 let r = match *kind {
834 "put" => put_results.get(*idx).cloned().unwrap_or(json!({"op":"put","error":"missing"})),
835 "del" => del_results.get(*idx).cloned().unwrap_or(json!({"op":"del","error":"missing"})),
836 _ => json!({"op": kind, "error": "unknown op"}),
837 };
838 results.push(r);
839 }
840 let (seq, head) = db_seq_head(&db);
841 mgr.notify_subscribers(&name, &db);
843 ok(json!({"results": results, "count": results.len(), "seq": seq, "head": head}))
844}
845
846#[derive(Deserialize)]
847struct IndexBody { coll: String, field: String, kind: Option<String> }
848
849async fn create_index(
850 State(mgr): State<Manager>,
851 headers: HeaderMap,
852 AxPath(name): AxPath<String>,
853 Json(body): Json<IndexBody>,
854) -> Response {
855 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
856 let db = match mgr.get_db(&name).await {
857 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
858 Some(db) => db,
859 };
860 let kind = body.kind.as_deref().unwrap_or("eq");
861 match kind {
862 "sorted" | "eq" => {
863 db.create_sorted_index(&body.coll, &body.field);
864 ok(json!({"ok": true, "coll": body.coll, "field": body.field, "kind": kind}))
865 }
866 _ => err(StatusCode::BAD_REQUEST, &format!("unknown index kind: {}", kind)),
867 }
868}
869
870async fn verify_database(
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 let (ok_count, tampered) = db.verify();
881 let (seq, head) = db_seq_head(&db);
882 ok(json!({
883 "ok": tampered.is_empty(),
884 "seq": seq,
885 "head": head,
886 "tamper_evident": true,
887 "objects_checked": ok_count,
888 "tampered": tampered,
889 }))
890}
891
892async fn root_current(
899 State(mgr): State<Manager>,
900 headers: HeaderMap,
901 AxPath(name): AxPath<String>,
902) -> Response {
903 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
904 let db = match mgr.get_db(&name).await {
905 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
906 Some(db) => db,
907 };
908 match db.state_root() {
909 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
910 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e),
911 }
912}
913
914async fn root_list(
915 State(mgr): State<Manager>,
916 headers: HeaderMap,
917 AxPath(name): AxPath<String>,
918) -> Response {
919 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
920 let db = match mgr.get_db(&name).await {
921 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
922 Some(db) => db,
923 };
924 ok(json!({
925 "roots": db.list_roots(),
926 "history_floor": db.history_floor(),
927 }))
928}
929
930async fn root_create(
931 State(mgr): State<Manager>,
932 headers: HeaderMap,
933 AxPath(name): AxPath<String>,
934 AxQuery(q): AxQuery<std::collections::HashMap<String, String>>,
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 at: Option<u64> = match q.get("at").map(|v| v.parse::<u64>()) {
945 None => None,
946 Some(Ok(v)) => Some(v),
947 Some(Err(_)) => return err(StatusCode::BAD_REQUEST, "at must be a sequence number"),
948 };
949 let made = match at {
950 Some(seq) => db.create_root_at(seq),
951 None => db.create_root(),
952 };
953 match made {
954 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
955 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
956 }
957}
958
959async fn root_verify(
960 State(mgr): State<Manager>,
961 headers: HeaderMap,
962 AxPath((name, seq)): AxPath<(String, u64)>,
963) -> Response {
964 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
965 let db = match mgr.get_db(&name).await {
966 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
967 Some(db) => db,
968 };
969 let v = db.verify_root(seq);
970 ok(json!({
975 "verified": v.is_verified(),
976 "mismatch": v.is_mismatch(),
977 "exit_code": v.exit_code(),
978 "result": v,
979 }))
980}
981
982async fn checkpoint(
983 State(mgr): State<Manager>,
984 headers: HeaderMap,
985 AxPath(name): AxPath<String>,
986) -> Response {
987 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
988 let db = match mgr.get_db(&name).await {
989 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
990 Some(db) => db,
991 };
992 let (seq, head) = db_seq_head(&db);
993 ok(json!({"ok": true, "head": head, "seq": seq}))
995}
996
997#[derive(Deserialize)]
998struct LogQuery { limit: Option<usize> }
999
1000async fn get_log(
1001 State(mgr): State<Manager>,
1002 headers: HeaderMap,
1003 AxPath(name): AxPath<String>,
1004 AxQuery(q): AxQuery<LogQuery>,
1005) -> Response {
1006 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1007 let db = match mgr.get_db(&name).await {
1008 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1009 Some(db) => db,
1010 };
1011 let limit = q.limit.unwrap_or(50);
1012 let mut log_entries: Vec<Value> = db.objects.all_hashes()
1014 .filter_map(|h| db.objects.read(&h).ok())
1015 .take(limit)
1016 .map(|n| json!({
1017 "seq": n.seq, "coll": n.coll, "id": n.id,
1018 "hash": n.hash, "ts": n.ts, "op": "put"
1019 }))
1020 .collect();
1021 log_entries.sort_by(|a, b|
1022 b["seq"].as_u64().cmp(&a["seq"].as_u64())
1023 );
1024 log_entries.truncate(limit);
1025 let (seq, head) = db_seq_head(&db);
1026 ok(json!({"log": log_entries, "seq": seq, "head": head}))
1027}
1028
1029async fn tip_database(
1035 State(mgr): State<Manager>,
1036 headers: HeaderMap,
1037 AxPath(name): AxPath<String>,
1038) -> Response {
1039 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1040 let db = match mgr.get_db(&name).await {
1041 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1042 Some(db) => db,
1043 };
1044 let (seq, head) = db_seq_head(&db);
1045 let tip = db.tip().map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1046 ok(json!({"tip": tip, "seq": seq, "head": head}))
1047}
1048
1049async fn tip_collection_database(
1051 State(mgr): State<Manager>,
1052 headers: HeaderMap,
1053 AxPath((name, coll)): AxPath<(String, String)>,
1054) -> Response {
1055 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1056 let db = match mgr.get_db(&name).await {
1057 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1058 Some(db) => db,
1059 };
1060 let (seq, head) = db_seq_head(&db);
1061 let tip = db.tip_collection(&coll).map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1062 ok(json!({"coll": coll, "tip": tip, "seq": seq, "head": head}))
1063}
1064
1065#[derive(Deserialize)]
1066struct SinceQuery { after_seq: Option<u64>, limit: Option<usize> }
1067
1068async fn since_database(
1069 State(mgr): State<Manager>,
1070 headers: HeaderMap,
1071 AxPath(name): AxPath<String>,
1072 AxQuery(q): AxQuery<SinceQuery>,
1073) -> Response {
1074 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1075 let db = match mgr.get_db(&name).await {
1076 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1077 Some(db) => db,
1078 };
1079 let after = q.after_seq.unwrap_or(0);
1080 let b = db.since(after, q.limit.unwrap_or(0));
1081 let nodes: Vec<Value> = b.nodes.iter()
1082 .map(|n| serde_json::to_value(n).unwrap_or(Value::Null))
1083 .collect();
1084 let (seq, head) = db_seq_head(&db);
1085 ok(json!({
1086 "nodes": nodes, "count": nodes.len(),
1087 "from_seq": b.from_seq, "to_seq": b.to_seq, "head_seq": b.head_seq, "has_more": b.has_more,
1088 "seq": seq, "head": head
1089 }))
1090}
1091
1092async fn status_database(
1095 State(mgr): State<Manager>,
1096 headers: HeaderMap,
1097 AxPath(name): AxPath<String>,
1098) -> Response {
1099 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1100 let db = match mgr.get_db(&name).await {
1101 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1102 Some(db) => db,
1103 };
1104 let s = db.scan_status();
1105 ok(json!({
1106 "ok": true,
1107 "scan_complete": s.scan_complete,
1108 "tip_seq": s.tip_seq,
1109 "indexed_seq_min": s.indexed_seq_min,
1110 "indexed_seq_max": s.indexed_seq_max,
1111 "indexed_count": s.indexed_count
1112 }))
1113}
1114
1115#[derive(Deserialize)]
1118struct SubscribeBody { nql: String }
1119
1120async fn subscribe_query(
1121 State(mgr): State<Manager>,
1122 headers: HeaderMap,
1123 AxPath(name): AxPath<String>,
1124 Json(body): Json<SubscribeBody>,
1125) -> Response {
1126 if !mgr.check_auth(&headers) {
1127 return err(StatusCode::UNAUTHORIZED, "unauthorized");
1128 }
1129 let db = match mgr.get_db(&name).await {
1130 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1131 Some(db) => db,
1132 };
1133
1134 let (sub_id, rx) = mgr.subscribe(&name, body.nql.clone());
1135
1136 if let Ok((rows, _)) = crate::nql::query(&db, &body.nql) {
1138 let init = json!({
1139 "sub_id": sub_id,
1140 "db": &name,
1141 "nql": &body.nql,
1142 "rows": rows,
1143 "count": rows.len(),
1144 "event": "initial",
1145 });
1146 if let Some(mut entry) = mgr.subs.get_mut(&(name.clone(), sub_id)) {
1148 let hash = format!("{:?}", rows);
1149 entry.value_mut().1 = hash;
1150 }
1151 if let Some(entry) = mgr.subs.get(&(name.clone(), sub_id)) {
1153 let _ = entry.value().2.send(init.to_string());
1154 }
1155 }
1156
1157 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1158 match msg {
1159 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1160 Err(_) => None,
1161 }
1162 });
1163 Sse::new(stream)
1164 .keep_alive(KeepAlive::default())
1165 .into_response()
1166}
1167
1168async fn unsubscribe_query(
1169 State(mgr): State<Manager>,
1170 headers: HeaderMap,
1171 AxPath((name, sub_id)): AxPath<(String, u64)>,
1172) -> Response {
1173 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1174 mgr.unsubscribe(&name, sub_id);
1175 ok(json!({"ok": true, "sub_id": sub_id}))
1176}
1177
1178async fn log_events(State(mgr): State<Manager>) -> Sse<impl futures_core::Stream<Item = Result<Event, std::convert::Infallible>>> {
1181 let rx = mgr.log_tx.subscribe();
1182 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1183 match msg {
1184 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1185 Err(_) => None, }
1187 });
1188 Sse::new(stream).keep_alive(KeepAlive::default())
1189}
1190
1191pub fn router(mgr: Manager) -> Router {
1194 Router::new()
1195 .route("/health", get(health))
1196 .route("/events", get(log_events))
1197 .route("/v1/databases", get(list_databases).post(create_database))
1198 .route("/v1/databases/:name", get(get_database).delete(drop_database))
1199 .route("/v1/databases/:name/query", post(query_database))
1200 .route("/v1/databases/:name/cast", post(cast_prompt))
1201 .route("/v1/databases/:name/put", post(put_document))
1202 .route("/v1/databases/:name/link", post(link_document))
1203 .route("/v1/databases/:name/rows/:coll/:id",
1206 get(get_document).delete(delete_document))
1207 .route("/v1/databases/:name/batch", post(batch_operations))
1208 .route("/v1/databases/:name/index", post(create_index))
1209 .route("/v1/databases/:name/verify", get(verify_database))
1210 .route("/v1/databases/:name/root", get(root_current).post(root_create))
1211 .route("/v1/databases/:name/roots", get(root_list))
1212 .route("/v1/databases/:name/roots/:seq/verify", get(root_verify))
1213 .route("/v1/databases/:name/checkpoint", post(checkpoint))
1214 .route("/v1/databases/:name/log", get(get_log))
1215 .route("/v1/databases/:name/tip", get(tip_database))
1216 .route("/v1/databases/:name/collections/:coll/tip", get(tip_collection_database))
1217 .route("/v1/databases/:name/since", get(since_database))
1218 .route("/v1/databases/:name/status", get(status_database))
1219 .route("/v1/databases/:name/subscribe", post(subscribe_query))
1220 .route("/v1/databases/:name/subscribe/:sub_id", delete(unsubscribe_query))
1221 .with_state(mgr)
1222}
1223
1224impl crate::pgwire::DbResolver for Manager {
1229 fn resolve(&self, name: &str) -> Option<Arc<Db>> {
1230 let inner = self.inner.blocking_read();
1233 if name.is_empty() {
1237 if inner.dbs.len() == 1 {
1238 return inner.dbs.values().next().cloned();
1239 }
1240 return None;
1241 }
1242 inner.dbs.get(name).cloned()
1243 }
1244 fn token(&self) -> Option<String> {
1245 self.token.clone()
1246 }
1247}
1248
1249pub async fn run(host: &str, port: u16, data_dir: &str, tmk: Option<[u8; 32]>, token: Option<String>, memory_mode: bool) -> anyhow::Result<()> {
1250 #[cfg(feature = "cast")]
1255 let mut mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1256 #[cfg(not(feature = "cast"))]
1257 let mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1258
1259 mgr.open_all().await?;
1260
1261 #[cfg(feature = "cast")]
1265 {
1266 let want = std::env::var("NEDBD_CAST").map(|v| v == "1").unwrap_or(false);
1267 if want {
1268 match crate::cast::Caster::load(Path::new(data_dir)) {
1269 Ok(c) => {
1270 println!(" cast enabled — {:.2}M params, vocab {}, {}",
1271 c.n_params() as f64 / 1e6, c.vocab_size(), c.source());
1272 mgr.caster = Some(c);
1273 }
1274 Err(e) => {
1275 eprintln!(" cast DISABLED — {}", e);
1276 }
1277 }
1278 }
1279 }
1280 #[cfg(feature = "cast")]
1283 let mgr = mgr;
1284
1285 let has_token = mgr.token.is_some();
1286 let mgr_for_shutdown = mgr.clone();
1287 if let Ok(raw) = std::env::var("NEDBD_PG_PORT") {
1292 match raw.trim().parse::<u16>() {
1293 Ok(pg_port) if pg_port > 0 => {
1294 let pg_host = host.to_string();
1295 let resolver: Arc<dyn crate::pgwire::DbResolver> = Arc::new(mgr.clone());
1296 tokio::spawn(async move {
1297 if let Err(e) = crate::pgwire::run(&pg_host, pg_port, resolver).await {
1298 eprintln!(" [pgwire] listener stopped: {}", e);
1299 }
1300 });
1301 }
1302 _ => eprintln!(" [pgwire] ignoring NEDBD_PG_PORT={:?} — not a valid port", raw),
1303 }
1304 }
1305
1306 let app = router(mgr);
1307 let addr = format!("{}:{}", host, port).parse::<std::net::SocketAddr>()?;
1308 let banner = format!(r#"
1309 ◆
1310 ╱ ╲ N E D B · DAG ENGINE {}
1311 ◆ ◆ ─────────────────────────────────────────────
1312 ╱ ╲ ╱ ╲ content-addressed · tamper-evident · causal
1313 ◆ ◆ ◆ bi-temporal · replay-protected · encrypted
1314 ╱ ╲ ╱ ╲ ╱ ╲
1315 ◆ ◆ ◆ ◆ © INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)
1316 ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ interchained.org · hyperagent.com/refer/J2G6TCD7
1317
1318 ─────────────────────────────────────────────────────────────
1319 listen http://{}
1320 data {}
1321 enc {}
1322 token {}
1323 memory {}
1324 ─────────────────────────────────────────────────────────────
1325"#,
1326 env!("CARGO_PKG_VERSION"),
1327 addr,
1328 data_dir,
1329 if tmk.is_some() { "AES-256-GCM" } else { "off" },
1330 if has_token { "on" } else { "off (set NEDBD_TOKEN to require auth)" },
1331 if memory_mode { "yes — all data lost on exit (NEDBD_MEMORY=1)" } else { "no — durable DAG on disk" }
1332 );
1333 print!("{}", banner);
1334
1335 let listener = tokio::net::TcpListener::bind(addr).await?;
1336
1337 let mgr_hourly = mgr_for_shutdown.clone();
1341 tokio::spawn(async move {
1342 loop {
1343 let now_secs = std::time::SystemTime::now()
1345 .duration_since(std::time::UNIX_EPOCH)
1346 .map(|d| d.as_secs()).unwrap_or(0);
1347 let secs_into_hour = now_secs % 3600;
1348 let sleep_secs = 3600 - secs_into_hour;
1349 tokio::time::sleep(tokio::time::Duration::from_secs(sleep_secs)).await;
1350 mgr_hourly.flush_all().await;
1351 println!(" [nedbd] hourly checkpoint — manifests flushed");
1352 }
1353 });
1354
1355 let shutdown = async {
1357 #[cfg(unix)]
1358 {
1359 use tokio::signal::unix::{signal, SignalKind};
1360 let mut sigterm = signal(SignalKind::terminate()).unwrap();
1361 let mut sigint = signal(SignalKind::interrupt()).unwrap();
1362 tokio::select! {
1363 _ = sigterm.recv() => println!(" [nedbd] SIGTERM — flushing and exiting..."),
1364 _ = sigint.recv() => println!(" [nedbd] SIGINT — flushing and exiting..."),
1365 }
1366 }
1367 #[cfg(not(unix))]
1368 {
1369 tokio::signal::ctrl_c().await.ok();
1370 println!(" [nedbd] shutting down — flushing manifests...");
1371 }
1372 };
1373
1374 axum::serve(listener, app)
1375 .tcp_nodelay(true)
1376 .with_graceful_shutdown(shutdown)
1377 .await?;
1378
1379 mgr_for_shutdown.flush_all().await;
1381 println!(" [nedbd] goodbye");
1382 Ok(())
1383}