BREAKING CHANGE(core): replace the TypeScript database engine with a Rust-backed embedded server and bridge

This commit is contained in:
2026-03-26 19:48:27 +00:00
parent 8ec2046908
commit e23a951dbe
106 changed files with 11567 additions and 10678 deletions
+213
View File
@@ -0,0 +1,213 @@
pub mod management;
use std::sync::Arc;
use anyhow::Result;
use dashmap::DashMap;
use tokio::net::TcpListener;
#[cfg(unix)]
use tokio::net::UnixListener;
use tokio_util::codec::Framed;
use tokio_util::sync::CancellationToken;
use rustdb_config::{RustDbOptions, StorageType};
use rustdb_wire::{WireCodec, OP_QUERY};
use rustdb_wire::{encode_op_msg_response, encode_op_reply_response};
use rustdb_storage::{StorageAdapter, MemoryStorageAdapter, FileStorageAdapter};
// IndexEngine is used indirectly via CommandContext
use rustdb_txn::{TransactionEngine, SessionEngine};
use rustdb_commands::{CommandRouter, CommandContext};
/// The main RustDb server.
pub struct RustDb {
options: RustDbOptions,
ctx: Arc<CommandContext>,
router: Arc<CommandRouter>,
cancel_token: CancellationToken,
listener_handle: Option<tokio::task::JoinHandle<()>>,
}
impl RustDb {
/// Create a new RustDb server with the given options.
pub async fn new(options: RustDbOptions) -> Result<Self> {
// Create storage adapter
let storage: Arc<dyn StorageAdapter> = match options.storage {
StorageType::Memory => {
let adapter = MemoryStorageAdapter::new();
Arc::new(adapter)
}
StorageType::File => {
let path = options
.storage_path
.clone()
.unwrap_or_else(|| "./data".to_string());
let adapter = FileStorageAdapter::new(&path);
Arc::new(adapter)
}
};
// Initialize storage
storage.initialize().await?;
let ctx = Arc::new(CommandContext {
storage,
indexes: Arc::new(DashMap::new()),
transactions: Arc::new(TransactionEngine::new()),
sessions: Arc::new(SessionEngine::new(30 * 60 * 1000, 60 * 1000)),
cursors: Arc::new(DashMap::new()),
start_time: std::time::Instant::now(),
});
let router = Arc::new(CommandRouter::new(ctx.clone()));
Ok(Self {
options,
ctx,
router,
cancel_token: CancellationToken::new(),
listener_handle: None,
})
}
/// Start listening for connections.
pub async fn start(&mut self) -> Result<()> {
let cancel = self.cancel_token.clone();
let router = self.router.clone();
if let Some(ref socket_path) = self.options.socket_path {
#[cfg(unix)]
{
// Remove stale socket file
let _ = tokio::fs::remove_file(socket_path).await;
let listener = UnixListener::bind(socket_path)?;
let socket_path_clone = socket_path.clone();
tracing::info!("RustDb listening on unix:{}", socket_path_clone);
let handle = tokio::spawn(async move {
loop {
tokio::select! {
_ = cancel.cancelled() => break,
result = listener.accept() => {
match result {
Ok((stream, _addr)) => {
let router = router.clone();
tokio::spawn(async move {
handle_connection(stream, router).await;
});
}
Err(e) => {
tracing::error!("Accept error: {}", e);
}
}
}
}
}
});
self.listener_handle = Some(handle);
}
#[cfg(not(unix))]
{
anyhow::bail!("Unix sockets are not supported on this platform");
}
} else {
let addr = format!("{}:{}", self.options.host, self.options.port);
let listener = TcpListener::bind(&addr).await?;
tracing::info!("RustDb listening on {}", addr);
let handle = tokio::spawn(async move {
loop {
tokio::select! {
_ = cancel.cancelled() => break,
result = listener.accept() => {
match result {
Ok((stream, _addr)) => {
let _ = stream.set_nodelay(true);
let router = router.clone();
tokio::spawn(async move {
handle_connection(stream, router).await;
});
}
Err(e) => {
tracing::error!("Accept error: {}", e);
}
}
}
}
}
});
self.listener_handle = Some(handle);
}
Ok(())
}
/// Stop the server.
pub async fn stop(&mut self) -> Result<()> {
self.cancel_token.cancel();
if let Some(handle) = self.listener_handle.take() {
handle.abort();
let _ = handle.await;
}
// Close storage (persists if configured)
self.ctx.storage.close().await?;
// Clean up Unix socket file
if let Some(ref socket_path) = self.options.socket_path {
let _ = tokio::fs::remove_file(socket_path).await;
}
Ok(())
}
/// Get the connection URI.
pub fn connection_uri(&self) -> String {
self.options.connection_uri()
}
}
/// Handle a single client connection using the wire protocol codec.
async fn handle_connection<S>(stream: S, router: Arc<CommandRouter>)
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
use futures_util::{SinkExt, StreamExt};
let mut framed = Framed::new(stream, WireCodec);
while let Some(result) = framed.next().await {
match result {
Ok(parsed_cmd) => {
let request_id = parsed_cmd.request_id;
let op_code = parsed_cmd.op_code;
let response_doc = router.route(&parsed_cmd).await;
let response_id = next_request_id();
let response_bytes = if op_code == OP_QUERY {
encode_op_reply_response(request_id, &[response_doc], response_id, 0)
} else {
encode_op_msg_response(request_id, &response_doc, response_id)
};
if let Err(e) = framed.send(response_bytes).await {
tracing::debug!("Failed to send response: {}", e);
break;
}
}
Err(e) => {
tracing::debug!("Wire protocol error: {}", e);
break;
}
}
}
}
fn next_request_id() -> i32 {
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new(1);
COUNTER.fetch_add(1, Ordering::Relaxed)
}