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::nesql::run(db_arc, nql) {
129 Ok(rows) => rows,
130 Err(why) => {
131 eprintln!(
138 "[nedbd] subscription {}/{} could not be re-evaluated \
139 and will not update: {} (statement: {})",
140 key.0, key.1, why, nql
141 );
142 continue;
143 }
144 };
145 let new_hash = format!("{:?}", rows.iter().map(|r| r.to_string()).collect::<Vec<_>>());
147 if new_hash == *last_hash { continue; }
148 *last_hash = new_hash;
149 let event = json!({
151 "sub_id": key.1,
152 "db": &key.0,
153 "nql": nql.as_str(),
154 "rows": rows,
155 "count": rows.len(),
156 });
157 let _ = tx.send(event.to_string());
158 }
159 }
160 }
161
162 pub async fn open_all(&self) -> anyhow::Result<()> {
164 let (data_dir, tmk, memory_mode) = {
165 let inner = self.inner.read().await;
166 (inner.data_dir.clone(), inner.tmk, inner.memory_mode)
167 };
168 if memory_mode { return Ok(()); }
170 if !data_dir.exists() {
171 std::fs::create_dir_all(&data_dir)?;
172 return Ok(());
173 }
174 let mut names = vec![];
175 for entry in std::fs::read_dir(&data_dir)? {
176 let entry = entry?;
177 if entry.file_type()?.is_dir() {
178 names.push(entry.file_name().to_string_lossy().to_string());
179 }
180 }
181 let log_tx = self.log_tx.clone();
182 let mut inner = self.inner.write().await;
183 for name in names {
184 let db_path = inner.data_dir.join(&name);
185 let dek = tmk.map(|k| crate::store::Dek::from_tmk(&k, name.as_bytes()));
186 match Db::open(&db_path, dek) {
187 Ok(db) => {
188 nlog!(log_tx, " [nedbd] opened database {:?}", name);
189 let db_arc = Arc::new(db);
190 Db::start_cold_scan(Arc::clone(&db_arc));
191 Db::start_manifest_ticker(Arc::clone(&db_arc), 1000);
193 inner.dbs.insert(name, db_arc);
194 }
195 Err(e) => nlog!(log_tx, " [nedbd] ERROR opening {:?}: {}", name, e),
196 }
197 }
198 Ok(())
199 }
200
201 async fn get_db(&self, name: &str) -> Option<Arc<Db>> {
202 self.inner.read().await.dbs.get(name).cloned()
203 }
204
205 async fn create_db(&self, name: &str) -> anyhow::Result<Arc<Db>> {
206 let (data_dir, tmk, memory_mode) = {
207 let inner = self.inner.read().await;
208 (inner.data_dir.clone(), inner.tmk, inner.memory_mode)
209 };
210 let db = if memory_mode {
211 Arc::new(Db::in_memory())
213 } else {
214 let db_path = data_dir.join(name);
215 let dek = tmk.map(|k| crate::store::Dek::from_tmk(&k, name.as_bytes()));
216 let db = Arc::new(Db::open(&db_path, dek)?);
217 Db::start_cold_scan(Arc::clone(&db));
218 Db::start_manifest_ticker(Arc::clone(&db), 1000);
219 db
220 };
221 self.inner.write().await.dbs.insert(name.to_string(), db.clone());
222 Ok(db)
223 }
224
225 async fn drop_db(&self, name: &str) -> bool {
226 let db = self.inner.write().await.dbs.remove(name);
227 if let Some(db) = db {
228 db.flush_manifest_if_dirty();
230 let data_dir = self.inner.read().await.data_dir.clone();
231 let _ = std::fs::remove_dir_all(data_dir.join(name));
232 true
233 } else {
234 false
235 }
236 }
237
238 pub async fn flush_all(&self) {
240 let inner = self.inner.read().await;
241 for db in inner.dbs.values() {
242 db.flush_all(); }
244 }
245
246 async fn names(&self) -> Vec<String> {
247 self.inner.read().await.dbs.keys().cloned().collect()
248 }
249
250 pub fn log(&self, msg: impl Into<String>) {
252 let line = msg.into();
253 println!("{}", line);
254 let _ = self.log_tx.send(line);
255 }
256
257 fn check_auth(&self, headers: &HeaderMap) -> bool {
258 match &self.token {
259 None => true,
260 Some(required) => {
261 if let Some(auth) = headers.get("authorization") {
262 if let Ok(s) = auth.to_str() {
263 return s == format!("Bearer {}", required);
264 }
265 }
266 false
267 }
268 }
269 }
270}
271
272fn err(status: StatusCode, msg: &str) -> Response {
275 (status, Json(json!({"error": msg}))).into_response()
276}
277
278fn ok(body: Value) -> Response {
279 (StatusCode::OK, Json(body)).into_response()
280}
281
282fn db_seq_head(db: &Db) -> (u64, String) {
286 let seq = db.seq.load(std::sync::atomic::Ordering::SeqCst);
287 let head = db.head();
288 (seq, head)
289}
290
291async fn health(State(mgr): State<Manager>) -> Response {
294 let names = mgr.names().await;
295 let inner = mgr.inner.read().await;
296 ok(json!({
297 "ok": true,
298 "service": "nedbd",
299 "version": env!("CARGO_PKG_VERSION"),
300 "engine": "dag",
301 "memory": inner.memory_mode,
302 "databases": names,
303 "encrypted": inner.tmk.is_some(),
304 }))
305}
306
307async fn list_databases(State(mgr): State<Manager>, headers: HeaderMap) -> Response {
308 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
309 let names = mgr.names().await;
310 let summaries: Vec<Value> = {
311 let inner = mgr.inner.read().await;
312 names.iter().map(|n| {
313 if let Some(db) = inner.dbs.get(n) {
314 let (seq, head) = db_seq_head(db);
315 json!({"name": n, "seq": seq, "head": head, "collections": db.collections()})
316 } else {
317 json!({"name": n})
318 }
319 }).collect()
320 };
321 ok(json!({"databases": summaries}))
322}
323
324#[derive(Deserialize)]
325struct CreateDbBody { name: String }
326
327async fn create_database(
328 State(mgr): State<Manager>,
329 headers: HeaderMap,
330 Json(body): Json<CreateDbBody>,
331) -> Response {
332 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
333 if body.name.is_empty() { return err(StatusCode::BAD_REQUEST, "name is required"); }
334 match mgr.create_db(&body.name).await {
335 Ok(db) => {
336 let (seq, head) = db_seq_head(&db);
337 (StatusCode::CREATED, Json(json!({"database": {"name": body.name, "seq": seq, "head": head}}))).into_response()
338 }
339 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
340 }
341}
342
343async fn get_database(
344 State(mgr): State<Manager>,
345 headers: HeaderMap,
346 AxPath(name): AxPath<String>,
347) -> Response {
348 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
349 match mgr.get_db(&name).await {
350 None => err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
351 Some(db) => {
352 let (seq, head) = db_seq_head(&db);
353 ok(json!({"name": name, "seq": seq, "head": head, "collections": db.collections()}))
354 }
355 }
356}
357
358async fn drop_database(
359 State(mgr): State<Manager>,
360 headers: HeaderMap,
361 AxPath(name): AxPath<String>,
362) -> Response {
363 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
364 let dropped = mgr.drop_db(&name).await;
365 ok(json!({"dropped": dropped}))
366}
367
368#[derive(Deserialize)]
369struct QueryBody { nql: String }
370
371#[cfg_attr(not(feature = "cast"), allow(dead_code))]
379#[derive(Deserialize)]
380struct CastBody {
381 prompt: String,
382 #[serde(default)]
386 execute: bool,
387}
388
389#[cfg(feature = "cast")]
395async fn cast_prompt(
396 State(mgr): State<Manager>,
397 headers: HeaderMap,
398 AxPath(name): AxPath<String>,
399 Json(body): Json<CastBody>,
400) -> Response {
401 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
402
403 let caster = match &mgr.caster {
404 Some(c) => c,
405 None => return err(
406 StatusCode::SERVICE_UNAVAILABLE,
407 "cast is not enabled; start nedbd with --cast (or NEDBD_CAST=1) \
408 and place model.cast in the data directory",
409 ),
410 };
411
412 let db = match mgr.get_db(&name).await {
413 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
414 Some(db) => db,
415 };
416 if body.prompt.trim().is_empty() {
417 return err(StatusCode::BAD_REQUEST, "prompt is required");
418 }
419
420 let collections = db.collections();
423 let result = caster.cast_checked(&body.prompt, &collections);
424
425 let parse_err = match nql::parse(&result.nql) {
428 Ok(_) => None,
429 Err(e) => Some(e.to_string()),
430 };
431
432 let (seq, head) = db_seq_head(&db);
433 let mut out = json!({
434 "prompt": body.prompt,
435 "nql": result.nql,
436 "valid": parse_err.is_none(),
437 "collection": result.collection,
438 "collection_known": result.collection_known,
439 "collections": collections,
440 "executed": false,
441 "seq": seq,
442 "head": head,
443 });
444
445 if let Some(d) = &result.drift {
452 out["drift"] = json!(d);
453 }
454
455 if let Some(e) = parse_err {
456 out["error"] = json!(format!("NQL error: {}", e));
459 return (StatusCode::UNPROCESSABLE_ENTITY, Json(out)).into_response();
460 }
461
462 if !result.collection_known {
463 out["error"] = json!(format!(
467 "collection {:?} does not exist in {:?}",
468 result.collection.unwrap_or_default(), name
469 ));
470 return (StatusCode::UNPROCESSABLE_ENTITY, Json(out)).into_response();
471 }
472
473 if !body.execute {
474 return ok(out);
475 }
476
477 let nql_text = out["nql"].as_str().unwrap_or("").to_string();
479 match nql::query(&db, &nql_text) {
480 Ok((rows, count)) => {
481 out["executed"] = json!(true);
482 out["rows"] = json!(rows);
483 out["count"] = json!(count);
484 ok(out)
485 }
486 Err(e) => {
487 out["error"] = json!(format!("NQL error: {}", e));
488 (StatusCode::BAD_REQUEST, Json(out)).into_response()
489 }
490 }
491}
492
493#[cfg(not(feature = "cast"))]
496async fn cast_prompt(
497 State(_mgr): State<Manager>,
498 _headers: HeaderMap,
499 AxPath(_name): AxPath<String>,
500 Json(_body): Json<CastBody>,
501) -> Response {
502 err(
503 StatusCode::NOT_IMPLEMENTED,
504 "this nedbd was built without the `cast` feature; \
505 rebuild with --features cast to enable natural-language planning",
506 )
507}
508
509async fn query_database(
510 State(mgr): State<Manager>,
511 headers: HeaderMap,
512 AxPath(name): AxPath<String>,
513 Json(body): Json<QueryBody>,
514) -> Response {
515 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
516 let db = match mgr.get_db(&name).await {
517 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
518 Some(db) => db,
519 };
520 if body.nql.trim().is_empty() {
521 return err(StatusCode::BAD_REQUEST, "a statement is required");
522 }
523 let dialect = match crate::nesql::route(&body.nql) {
535 Ok(d) => d,
536 Err(why) => return err(StatusCode::BAD_REQUEST, &why),
537 };
538 let (seq, head) = db_seq_head(&db);
539 match dialect {
540 crate::nesql::Dialect::Nql => match nql::query(&db, &body.nql) {
541 Ok((rows, count)) => ok(json!({
542 "rows": rows, "count": count, "seq": seq, "head": head,
543 "dialect": "nql",
544 })),
545 Err(e) => err(StatusCode::BAD_REQUEST, &format!("NQL error: {}", e)),
546 },
547 crate::nesql::Dialect::Sql => match crate::pgwire::execute_sql(&db, &body.nql, false) {
548 Ok(done) => {
549 let n = done.rows.len();
550 ok(json!({
551 "rows": done.rows, "count": n, "seq": seq, "head": head,
552 "dialect": "sql", "tag": done.tag,
553 }))
554 }
555 Err(why) => err(StatusCode::BAD_REQUEST, &format!("SQL error: {}", why)),
556 },
557 }
558}
559
560#[derive(Deserialize)]
561struct PutBody {
562 coll: String,
563 id: String,
564 doc: Value,
565 caused_by: Option<Vec<serde_json::Value>>,
566 valid_from: Option<String>,
567 valid_to: Option<String>,
568 #[allow(dead_code)] evidence: Option<String>,
569 #[allow(dead_code)] confidence: Option<f64>,
570 #[allow(dead_code)] client: Option<String>,
571 #[allow(dead_code)] nonce: Option<u64>,
572 #[allow(dead_code)] idem: Option<String>,
573}
574
575#[derive(Deserialize)]
576struct LinkBody {
577 frm: String,
578 rel: String,
579 to: String,
580}
581
582async fn put_document(
583 State(mgr): State<Manager>,
584 headers: HeaderMap,
585 AxPath(name): AxPath<String>,
586 Json(body): Json<PutBody>,
587) -> Response {
588 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
589 let db = match mgr.get_db(&name).await {
590 None => {
591 match mgr.create_db(&name).await {
593 Ok(db) => db,
594 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
595 }
596 }
597 Some(db) => db,
598 };
599 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
602 return err(StatusCode::SERVICE_UNAVAILABLE,
603 "database startup in progress — reads available, writes retry in a moment");
604 }
605 let caused_by: Vec<String> = body.caused_by.unwrap_or_default()
607 .into_iter()
608 .filter_map(|v| match v {
609 serde_json::Value::String(s) => Some(s),
610 serde_json::Value::Number(n) => {
611 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
612 }
613 _ => None,
614 })
615 .collect();
616 let coll = body.coll.clone();
619 let id = body.id.clone();
620 let doc = body.doc.clone();
621 let vf = body.valid_from.clone();
622 let vt = body.valid_to.clone();
623 let db2 = Arc::clone(&db);
624 let result = tokio::task::spawn_blocking(move || {
625 db2.put(&coll, &id, doc, caused_by, vf, vt)
626 }).await;
627 match result {
628 Err(join_err) => err(StatusCode::INTERNAL_SERVER_ERROR, &join_err.to_string()),
629 Ok(Err(e)) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
630 Ok(Ok(node)) => {
631 let (seq, head) = db_seq_head(&db);
632 mgr.notify_subscribers(&name, &db);
633 ok(json!({"ok": true, "doc": node_to_response(&node), "seq": seq, "head": head}))
634 }
635 }
636}
637
638fn node_to_response(node: &Node) -> Value {
639 json!({
640 "_id": node.id,
641 "_hash": node.hash,
642 "_seq": node.seq,
643 "_coll": node.coll,
644 "data": node.data,
645 })
646}
647
648async fn link_document(
649 State(mgr): State<Manager>,
650 headers: HeaderMap,
651 AxPath(name): AxPath<String>,
652 Json(body): Json<LinkBody>,
653) -> Response {
654 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
655 let db = match mgr.get_db(&name).await {
656 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
657 Some(db) => db,
658 };
659 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
660 return err(StatusCode::SERVICE_UNAVAILABLE, "startup scan in progress");
661 }
662 match db.link(&body.frm, &body.rel, &body.to) {
663 Ok(()) => {
664 let (seq, head) = db_seq_head(&db);
665 ok(json!({"ok": true, "frm": body.frm, "rel": body.rel, "to": body.to, "seq": seq, "head": head}))
666 }
667 Err(e) => err(StatusCode::BAD_REQUEST, &e.to_string()),
668 }
669}
670
671async fn get_document(
695 State(mgr): State<Manager>,
696 headers: HeaderMap,
697 AxPath((name, coll, id)): AxPath<(String, String, String)>,
698 AxQuery(q): AxQuery<GetRowQuery>,
699) -> Response {
700 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
701 let db = match mgr.get_db(&name).await {
702 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
703 Some(db) => db,
704 };
705 let node = match q.as_of {
706 Some(seq) => db.get_as_of(&coll, &id, seq),
707 None => db.get(&coll, &id),
708 };
709 let (seq, head) = db_seq_head(&db);
720 let row = match node {
721 None => Value::Null,
722 Some(n) => crate::nql::node_to_json(&n),
723 };
724 ok(json!({"row": row, "seq": seq, "head": head}))
725}
726
727#[derive(Deserialize, Default)]
728struct GetRowQuery {
729 as_of: Option<u64>,
730}
731
732async fn delete_document(
733 State(mgr): State<Manager>,
734 headers: HeaderMap,
735 AxPath((name, coll, id)): AxPath<(String, String, String)>,
736) -> Response {
737 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
738 let db = match mgr.get_db(&name).await {
739 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
740 Some(db) => db,
741 };
742 let existed = match db.delete(&coll, &id) {
745 Ok(v) => v,
746 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
747 };
748 let (seq, head) = db_seq_head(&db);
749 ok(json!({"ok": existed, "seq": seq, "head": head}))
750}
751
752#[derive(Deserialize)]
753struct BatchOp {
754 op: String,
755 coll: Option<String>,
756 id: Option<String>,
757 doc: Option<Value>,
758 caused_by: Option<Vec<serde_json::Value>>,
759}
760#[derive(Deserialize)]
761struct BatchBody { ops: Vec<BatchOp> }
762
763async fn batch_operations(
764 State(mgr): State<Manager>,
765 headers: HeaderMap,
766 AxPath(name): AxPath<String>,
767 Json(body): Json<BatchBody>,
768) -> Response {
769 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
770 let db = match mgr.get_db(&name).await {
771 None => match mgr.create_db(&name).await {
772 Ok(db) => db,
773 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
774 },
775 Some(db) => db,
776 };
777
778 if !db.startup_ready.load(std::sync::atomic::Ordering::SeqCst) {
779 return err(StatusCode::SERVICE_UNAVAILABLE,
780 "database startup in progress — reads available, writes retry in a moment");
781 }
782
783 let mut put_ops = vec![];
787 let mut del_ops: Vec<(String, String)> = vec![];
788 let mut op_order: Vec<(&str, usize)> = vec![]; for op in &body.ops {
791 let t = op.op.to_lowercase();
792 match t.as_str() {
793 "put" => {
794 let caused_by: Vec<String> = op.caused_by.clone().unwrap_or_default()
796 .into_iter()
797 .filter_map(|v| match v {
798 serde_json::Value::String(s) => Some(s),
799 serde_json::Value::Number(n) => {
800 n.as_u64().and_then(|seq| db.get_hash_by_seq(seq))
801 }
802 _ => None,
803 })
804 .collect();
805 op_order.push(("put", put_ops.len()));
806 put_ops.push((
807 op.coll.clone().unwrap_or_default(),
808 op.id.clone().unwrap_or_default(),
809 op.doc.clone().unwrap_or(json!({})),
810 caused_by,
811 None::<String>,
812 None::<String>,
813 ));
814 }
815 "del" | "delete" => {
816 op_order.push(("del", del_ops.len()));
817 del_ops.push((
818 op.coll.clone().unwrap_or_default(),
819 op.id.clone().unwrap_or_default(),
820 ));
821 }
822 _ => { op_order.push(("unknown", 0)); }
823 }
824 }
825
826 let put_results = if put_ops.is_empty() {
828 vec![]
829 } else {
830 match db.put_batch(put_ops) {
831 Ok(nodes) => nodes.into_iter().map(|n| json!({"op":"put","id":n.id,"seq":n.seq,"hash":n.hash})).collect(),
832 Err(e) => return err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
833 }
834 };
835
836 let del_results: Vec<serde_json::Value> = del_ops.iter().map(|(coll, id)| {
838 match db.delete(coll, id) {
839 Ok(existed) => json!({"op":"del","id":id,"ok":existed}),
840 Err(e) => json!({"op":"del","id":id,"error":e.to_string()}),
841 }
842 }).collect();
843
844 let mut results = vec![];
846 for (kind, idx) in &op_order {
847 let r = match *kind {
848 "put" => put_results.get(*idx).cloned().unwrap_or(json!({"op":"put","error":"missing"})),
849 "del" => del_results.get(*idx).cloned().unwrap_or(json!({"op":"del","error":"missing"})),
850 _ => json!({"op": kind, "error": "unknown op"}),
851 };
852 results.push(r);
853 }
854 let (seq, head) = db_seq_head(&db);
855 mgr.notify_subscribers(&name, &db);
857 ok(json!({"results": results, "count": results.len(), "seq": seq, "head": head}))
858}
859
860#[derive(Deserialize)]
861struct IndexBody { coll: String, field: String, kind: Option<String> }
862
863async fn create_index(
864 State(mgr): State<Manager>,
865 headers: HeaderMap,
866 AxPath(name): AxPath<String>,
867 Json(body): Json<IndexBody>,
868) -> Response {
869 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
870 let db = match mgr.get_db(&name).await {
871 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
872 Some(db) => db,
873 };
874 let kind = body.kind.as_deref().unwrap_or("eq");
875 match kind {
876 "sorted" | "eq" => {
877 db.create_sorted_index(&body.coll, &body.field);
878 ok(json!({"ok": true, "coll": body.coll, "field": body.field, "kind": kind}))
879 }
880 _ => err(StatusCode::BAD_REQUEST, &format!("unknown index kind: {}", kind)),
881 }
882}
883
884async fn verify_database(
885 State(mgr): State<Manager>,
886 headers: HeaderMap,
887 AxPath(name): AxPath<String>,
888) -> Response {
889 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
890 let db = match mgr.get_db(&name).await {
891 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
892 Some(db) => db,
893 };
894 let (ok_count, tampered) = db.verify();
895 let (seq, head) = db_seq_head(&db);
896 ok(json!({
897 "ok": tampered.is_empty(),
898 "seq": seq,
899 "head": head,
900 "tamper_evident": true,
901 "objects_checked": ok_count,
902 "tampered": tampered,
903 }))
904}
905
906async fn root_current(
913 State(mgr): State<Manager>,
914 headers: HeaderMap,
915 AxPath(name): AxPath<String>,
916) -> Response {
917 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
918 let db = match mgr.get_db(&name).await {
919 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
920 Some(db) => db,
921 };
922 match db.state_root() {
923 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
924 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e),
925 }
926}
927
928async fn root_list(
929 State(mgr): State<Manager>,
930 headers: HeaderMap,
931 AxPath(name): AxPath<String>,
932) -> Response {
933 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
934 let db = match mgr.get_db(&name).await {
935 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
936 Some(db) => db,
937 };
938 ok(json!({
939 "roots": db.list_roots(),
940 "history_floor": db.history_floor(),
941 }))
942}
943
944async fn root_create(
945 State(mgr): State<Manager>,
946 headers: HeaderMap,
947 AxPath(name): AxPath<String>,
948 AxQuery(q): AxQuery<std::collections::HashMap<String, String>>,
949) -> Response {
950 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
951 let db = match mgr.get_db(&name).await {
952 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
953 Some(db) => db,
954 };
955 let at: Option<u64> = match q.get("at").map(|v| v.parse::<u64>()) {
959 None => None,
960 Some(Ok(v)) => Some(v),
961 Some(Err(_)) => return err(StatusCode::BAD_REQUEST, "at must be a sequence number"),
962 };
963 let made = match at {
964 Some(seq) => db.create_root_at(seq),
965 None => db.create_root(),
966 };
967 match made {
968 Ok(r) => ok(serde_json::to_value(r).unwrap_or(json!({}))),
969 Err(e) => err(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
970 }
971}
972
973async fn root_verify(
974 State(mgr): State<Manager>,
975 headers: HeaderMap,
976 AxPath((name, seq)): AxPath<(String, u64)>,
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 v = db.verify_root(seq);
984 ok(json!({
989 "verified": v.is_verified(),
990 "mismatch": v.is_mismatch(),
991 "exit_code": v.exit_code(),
992 "result": v,
993 }))
994}
995
996async fn checkpoint(
997 State(mgr): State<Manager>,
998 headers: HeaderMap,
999 AxPath(name): AxPath<String>,
1000) -> Response {
1001 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1002 let db = match mgr.get_db(&name).await {
1003 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1004 Some(db) => db,
1005 };
1006 let (seq, head) = db_seq_head(&db);
1007 ok(json!({"ok": true, "head": head, "seq": seq}))
1009}
1010
1011#[derive(Deserialize)]
1012struct LogQuery { limit: Option<usize> }
1013
1014async fn get_log(
1015 State(mgr): State<Manager>,
1016 headers: HeaderMap,
1017 AxPath(name): AxPath<String>,
1018 AxQuery(q): AxQuery<LogQuery>,
1019) -> Response {
1020 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1021 let db = match mgr.get_db(&name).await {
1022 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1023 Some(db) => db,
1024 };
1025 let limit = q.limit.unwrap_or(50);
1026 let mut log_entries: Vec<Value> = db.objects.all_hashes()
1028 .filter_map(|h| db.objects.read(&h).ok())
1029 .take(limit)
1030 .map(|n| json!({
1031 "seq": n.seq, "coll": n.coll, "id": n.id,
1032 "hash": n.hash, "ts": n.ts, "op": "put"
1033 }))
1034 .collect();
1035 log_entries.sort_by(|a, b|
1036 b["seq"].as_u64().cmp(&a["seq"].as_u64())
1037 );
1038 log_entries.truncate(limit);
1039 let (seq, head) = db_seq_head(&db);
1040 ok(json!({"log": log_entries, "seq": seq, "head": head}))
1041}
1042
1043async fn tip_database(
1049 State(mgr): State<Manager>,
1050 headers: HeaderMap,
1051 AxPath(name): AxPath<String>,
1052) -> Response {
1053 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1054 let db = match mgr.get_db(&name).await {
1055 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1056 Some(db) => db,
1057 };
1058 let (seq, head) = db_seq_head(&db);
1059 let tip = db.tip().map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1060 ok(json!({"tip": tip, "seq": seq, "head": head}))
1061}
1062
1063async fn tip_collection_database(
1065 State(mgr): State<Manager>,
1066 headers: HeaderMap,
1067 AxPath((name, coll)): AxPath<(String, String)>,
1068) -> Response {
1069 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1070 let db = match mgr.get_db(&name).await {
1071 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1072 Some(db) => db,
1073 };
1074 let (seq, head) = db_seq_head(&db);
1075 let tip = db.tip_collection(&coll).map(|n| serde_json::to_value(&n).unwrap_or(Value::Null));
1076 ok(json!({"coll": coll, "tip": tip, "seq": seq, "head": head}))
1077}
1078
1079#[derive(Deserialize)]
1080struct SinceQuery { after_seq: Option<u64>, limit: Option<usize> }
1081
1082async fn since_database(
1083 State(mgr): State<Manager>,
1084 headers: HeaderMap,
1085 AxPath(name): AxPath<String>,
1086 AxQuery(q): AxQuery<SinceQuery>,
1087) -> Response {
1088 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1089 let db = match mgr.get_db(&name).await {
1090 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1091 Some(db) => db,
1092 };
1093 let after = q.after_seq.unwrap_or(0);
1094 let b = db.since(after, q.limit.unwrap_or(0));
1095 let nodes: Vec<Value> = b.nodes.iter()
1096 .map(|n| serde_json::to_value(n).unwrap_or(Value::Null))
1097 .collect();
1098 let (seq, head) = db_seq_head(&db);
1099 ok(json!({
1100 "nodes": nodes, "count": nodes.len(),
1101 "from_seq": b.from_seq, "to_seq": b.to_seq, "head_seq": b.head_seq, "has_more": b.has_more,
1102 "seq": seq, "head": head
1103 }))
1104}
1105
1106async fn status_database(
1109 State(mgr): State<Manager>,
1110 headers: HeaderMap,
1111 AxPath(name): AxPath<String>,
1112) -> Response {
1113 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1114 let db = match mgr.get_db(&name).await {
1115 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1116 Some(db) => db,
1117 };
1118 let s = db.scan_status();
1119 ok(json!({
1120 "ok": true,
1121 "scan_complete": s.scan_complete,
1122 "tip_seq": s.tip_seq,
1123 "indexed_seq_min": s.indexed_seq_min,
1124 "indexed_seq_max": s.indexed_seq_max,
1125 "indexed_count": s.indexed_count
1126 }))
1127}
1128
1129#[derive(Deserialize)]
1132struct SubscribeBody { nql: String }
1133
1134async fn subscribe_query(
1135 State(mgr): State<Manager>,
1136 headers: HeaderMap,
1137 AxPath(name): AxPath<String>,
1138 Json(body): Json<SubscribeBody>,
1139) -> Response {
1140 if !mgr.check_auth(&headers) {
1141 return err(StatusCode::UNAUTHORIZED, "unauthorized");
1142 }
1143 let db = match mgr.get_db(&name).await {
1144 None => return err(StatusCode::NOT_FOUND, &format!("database not found: {}", name)),
1145 Some(db) => db,
1146 };
1147
1148 if let Err(why) = crate::nesql::route(&body.nql) {
1155 return err(StatusCode::BAD_REQUEST, &why);
1156 }
1157
1158 let (sub_id, rx) = mgr.subscribe(&name, body.nql.clone());
1159
1160 let initial = crate::nesql::run(&db, &body.nql);
1169 if let Err(ref why) = initial {
1170 eprintln!(
1171 "[nedbd] subscription {}/{} opened but its first evaluation failed: {} (statement: {})",
1172 name, sub_id, why, body.nql
1173 );
1174 }
1175 if let Ok(rows) = initial {
1176 let init = json!({
1177 "sub_id": sub_id,
1178 "db": &name,
1179 "nql": &body.nql,
1180 "rows": rows,
1181 "count": rows.len(),
1182 "event": "initial",
1183 });
1184 if let Some(mut entry) = mgr.subs.get_mut(&(name.clone(), sub_id)) {
1186 let hash = format!("{:?}", rows);
1187 entry.value_mut().1 = hash;
1188 }
1189 if let Some(entry) = mgr.subs.get(&(name.clone(), sub_id)) {
1191 let _ = entry.value().2.send(init.to_string());
1192 }
1193 }
1194
1195 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1196 match msg {
1197 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1198 Err(_) => None,
1199 }
1200 });
1201 Sse::new(stream)
1202 .keep_alive(KeepAlive::default())
1203 .into_response()
1204}
1205
1206async fn unsubscribe_query(
1207 State(mgr): State<Manager>,
1208 headers: HeaderMap,
1209 AxPath((name, sub_id)): AxPath<(String, u64)>,
1210) -> Response {
1211 if !mgr.check_auth(&headers) { return err(StatusCode::UNAUTHORIZED, "unauthorized"); }
1212 mgr.unsubscribe(&name, sub_id);
1213 ok(json!({"ok": true, "sub_id": sub_id}))
1214}
1215
1216async fn log_events(State(mgr): State<Manager>) -> Sse<impl futures_core::Stream<Item = Result<Event, std::convert::Infallible>>> {
1219 let rx = mgr.log_tx.subscribe();
1220 let stream = BroadcastStream::new(rx).filter_map(|msg| {
1221 match msg {
1222 Ok(line) => Some(Ok::<Event, std::convert::Infallible>(Event::default().data(line))),
1223 Err(_) => None, }
1225 });
1226 Sse::new(stream).keep_alive(KeepAlive::default())
1227}
1228
1229pub fn router(mgr: Manager) -> Router {
1232 Router::new()
1233 .route("/health", get(health))
1234 .route("/events", get(log_events))
1235 .route("/v1/databases", get(list_databases).post(create_database))
1236 .route("/v1/databases/:name", get(get_database).delete(drop_database))
1237 .route("/v1/databases/:name/query", post(query_database))
1238 .route("/v1/databases/:name/cast", post(cast_prompt))
1239 .route("/v1/databases/:name/put", post(put_document))
1240 .route("/v1/databases/:name/link", post(link_document))
1241 .route("/v1/databases/:name/rows/:coll/:id",
1244 get(get_document).delete(delete_document))
1245 .route("/v1/databases/:name/batch", post(batch_operations))
1246 .route("/v1/databases/:name/index", post(create_index))
1247 .route("/v1/databases/:name/verify", get(verify_database))
1248 .route("/v1/databases/:name/root", get(root_current).post(root_create))
1249 .route("/v1/databases/:name/roots", get(root_list))
1250 .route("/v1/databases/:name/roots/:seq/verify", get(root_verify))
1251 .route("/v1/databases/:name/checkpoint", post(checkpoint))
1252 .route("/v1/databases/:name/log", get(get_log))
1253 .route("/v1/databases/:name/tip", get(tip_database))
1254 .route("/v1/databases/:name/collections/:coll/tip", get(tip_collection_database))
1255 .route("/v1/databases/:name/since", get(since_database))
1256 .route("/v1/databases/:name/status", get(status_database))
1257 .route("/v1/databases/:name/subscribe", post(subscribe_query))
1258 .route("/v1/databases/:name/subscribe/:sub_id", delete(unsubscribe_query))
1259 .with_state(mgr)
1260}
1261
1262impl crate::pgwire::DbResolver for Manager {
1267 fn resolve(&self, name: &str) -> Option<Arc<Db>> {
1268 let inner = self.inner.blocking_read();
1271 if name.is_empty() {
1275 if inner.dbs.len() == 1 {
1276 return inner.dbs.values().next().cloned();
1277 }
1278 return None;
1279 }
1280 inner.dbs.get(name).cloned()
1281 }
1282 fn token(&self) -> Option<String> {
1283 self.token.clone()
1284 }
1285}
1286
1287pub async fn run(host: &str, port: u16, data_dir: &str, tmk: Option<[u8; 32]>, token: Option<String>, memory_mode: bool) -> anyhow::Result<()> {
1288 #[cfg(feature = "cast")]
1293 let mut mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1294 #[cfg(not(feature = "cast"))]
1295 let mgr = Manager::new(Path::new(data_dir), tmk, token, memory_mode);
1296
1297 mgr.open_all().await?;
1298
1299 #[cfg(feature = "cast")]
1303 {
1304 let want = std::env::var("NEDBD_CAST").map(|v| v == "1").unwrap_or(false);
1305 if want {
1306 match crate::cast::Caster::load(Path::new(data_dir)) {
1307 Ok(c) => {
1308 println!(" cast enabled — {:.2}M params, vocab {}, {}",
1309 c.n_params() as f64 / 1e6, c.vocab_size(), c.source());
1310 mgr.caster = Some(c);
1311 }
1312 Err(e) => {
1313 eprintln!(" cast DISABLED — {}", e);
1314 }
1315 }
1316 }
1317 }
1318 #[cfg(feature = "cast")]
1321 let mgr = mgr;
1322
1323 let has_token = mgr.token.is_some();
1324 let mgr_for_shutdown = mgr.clone();
1325 if let Ok(raw) = std::env::var("NEDBD_PG_PORT") {
1330 match raw.trim().parse::<u16>() {
1331 Ok(pg_port) if pg_port > 0 => {
1332 let pg_host = host.to_string();
1333 let resolver: Arc<dyn crate::pgwire::DbResolver> = Arc::new(mgr.clone());
1334 tokio::spawn(async move {
1335 if let Err(e) = crate::pgwire::run(&pg_host, pg_port, resolver).await {
1336 eprintln!(" [pgwire] listener stopped: {}", e);
1337 }
1338 });
1339 }
1340 _ => eprintln!(" [pgwire] ignoring NEDBD_PG_PORT={:?} — not a valid port", raw),
1341 }
1342 }
1343
1344 let app = router(mgr);
1345 let addr = format!("{}:{}", host, port).parse::<std::net::SocketAddr>()?;
1346 let banner = format!(r#"
1347 ◆
1348 ╱ ╲ N E D B · DAG ENGINE {}
1349 ◆ ◆ ─────────────────────────────────────────────
1350 ╱ ╲ ╱ ╲ content-addressed · tamper-evident · causal
1351 ◆ ◆ ◆ bi-temporal · replay-protected · encrypted
1352 ╱ ╲ ╱ ╲ ╱ ╲
1353 ◆ ◆ ◆ ◆ © INTERCHAINED LLC × Vex (Interchained AI fleet: GLM · Claude · Opus · Fable · GPT-6)
1354 ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ interchained.org · hyperagent.com/refer/J2G6TCD7
1355
1356 ─────────────────────────────────────────────────────────────
1357 listen http://{}
1358 data {}
1359 enc {}
1360 token {}
1361 memory {}
1362 ─────────────────────────────────────────────────────────────
1363"#,
1364 env!("CARGO_PKG_VERSION"),
1365 addr,
1366 data_dir,
1367 if tmk.is_some() { "AES-256-GCM" } else { "off" },
1368 if has_token { "on" } else { "off (set NEDBD_TOKEN to require auth)" },
1369 if memory_mode { "yes — all data lost on exit (NEDBD_MEMORY=1)" } else { "no — durable DAG on disk" }
1370 );
1371 print!("{}", banner);
1372
1373 let listener = tokio::net::TcpListener::bind(addr).await?;
1374
1375 let mgr_hourly = mgr_for_shutdown.clone();
1379 tokio::spawn(async move {
1380 loop {
1381 let now_secs = std::time::SystemTime::now()
1383 .duration_since(std::time::UNIX_EPOCH)
1384 .map(|d| d.as_secs()).unwrap_or(0);
1385 let secs_into_hour = now_secs % 3600;
1386 let sleep_secs = 3600 - secs_into_hour;
1387 tokio::time::sleep(tokio::time::Duration::from_secs(sleep_secs)).await;
1388 mgr_hourly.flush_all().await;
1389 println!(" [nedbd] hourly checkpoint — manifests flushed");
1390 }
1391 });
1392
1393 let shutdown = async {
1395 #[cfg(unix)]
1396 {
1397 use tokio::signal::unix::{signal, SignalKind};
1398 let mut sigterm = signal(SignalKind::terminate()).unwrap();
1399 let mut sigint = signal(SignalKind::interrupt()).unwrap();
1400 tokio::select! {
1401 _ = sigterm.recv() => println!(" [nedbd] SIGTERM — flushing and exiting..."),
1402 _ = sigint.recv() => println!(" [nedbd] SIGINT — flushing and exiting..."),
1403 }
1404 }
1405 #[cfg(not(unix))]
1406 {
1407 tokio::signal::ctrl_c().await.ok();
1408 println!(" [nedbd] shutting down — flushing manifests...");
1409 }
1410 };
1411
1412 axum::serve(listener, app)
1413 .tcp_nodelay(true)
1414 .with_graceful_shutdown(shutdown)
1415 .await?;
1416
1417 mgr_for_shutdown.flush_all().await;
1419 println!(" [nedbd] goodbye");
1420 Ok(())
1421}