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

View File

@@ -0,0 +1,12 @@
[package]
name = "rustdb-config"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
description = "Configuration types for RustDb, compatible with SmartDB JSON schema"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }

View File

@@ -0,0 +1,181 @@
use serde::{Deserialize, Serialize};
/// Storage backend type.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum StorageType {
Memory,
File,
}
impl Default for StorageType {
fn default() -> Self {
StorageType::Memory
}
}
/// Top-level configuration for RustDb server.
/// Field names use camelCase to match the TypeScript SmartdbServer options.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RustDbOptions {
/// TCP port to listen on (default: 27017)
#[serde(default = "default_port")]
pub port: u16,
/// Host/IP to bind to (default: "127.0.0.1")
#[serde(default = "default_host")]
pub host: String,
/// Unix socket path (overrides TCP if set)
#[serde(skip_serializing_if = "Option::is_none")]
pub socket_path: Option<String>,
/// Storage backend type
#[serde(default)]
pub storage: StorageType,
/// Base path for file storage (required when storage = "file")
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_path: Option<String>,
/// Path for periodic persistence of in-memory data
#[serde(skip_serializing_if = "Option::is_none")]
pub persist_path: Option<String>,
/// Interval in ms for periodic persistence (default: 60000)
#[serde(default = "default_persist_interval")]
pub persist_interval_ms: u64,
}
fn default_port() -> u16 {
27017
}
fn default_host() -> String {
"127.0.0.1".to_string()
}
fn default_persist_interval() -> u64 {
60000
}
impl Default for RustDbOptions {
fn default() -> Self {
Self {
port: default_port(),
host: default_host(),
socket_path: None,
storage: StorageType::default(),
storage_path: None,
persist_path: None,
persist_interval_ms: default_persist_interval(),
}
}
}
impl RustDbOptions {
/// Load options from a JSON config file.
pub fn from_file(path: &str) -> Result<Self, ConfigError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ConfigError::IoError(e.to_string()))?;
let options: Self = serde_json::from_str(&content)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
options.validate()?;
Ok(options)
}
/// Validate the configuration.
pub fn validate(&self) -> Result<(), ConfigError> {
if self.storage == StorageType::File && self.storage_path.is_none() {
return Err(ConfigError::ValidationError(
"storagePath is required when storage is 'file'".to_string(),
));
}
Ok(())
}
/// Get the connection URI for this server configuration.
pub fn connection_uri(&self) -> String {
if let Some(ref socket_path) = self.socket_path {
let encoded = urlencoding(socket_path);
format!("mongodb://{}", encoded)
} else {
format!("mongodb://{}:{}", self.host, self.port)
}
}
}
/// Simple URL encoding for socket paths (encode / as %2F, etc.)
fn urlencoding(s: &str) -> String {
s.chars()
.map(|c| match c {
'/' => "%2F".to_string(),
':' => "%3A".to_string(),
' ' => "%20".to_string(),
_ => c.to_string(),
})
.collect()
}
/// Configuration errors.
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("IO error: {0}")]
IoError(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Validation error: {0}")]
ValidationError(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_options() {
let opts = RustDbOptions::default();
assert_eq!(opts.port, 27017);
assert_eq!(opts.host, "127.0.0.1");
assert!(opts.socket_path.is_none());
assert_eq!(opts.storage, StorageType::Memory);
}
#[test]
fn test_deserialize_from_json() {
let json = r#"{"port": 27018, "storage": "file", "storagePath": "./data"}"#;
let opts: RustDbOptions = serde_json::from_str(json).unwrap();
assert_eq!(opts.port, 27018);
assert_eq!(opts.storage, StorageType::File);
assert_eq!(opts.storage_path, Some("./data".to_string()));
}
#[test]
fn test_connection_uri_tcp() {
let opts = RustDbOptions::default();
assert_eq!(opts.connection_uri(), "mongodb://127.0.0.1:27017");
}
#[test]
fn test_connection_uri_socket() {
let opts = RustDbOptions {
socket_path: Some("/tmp/smartdb-test.sock".to_string()),
..Default::default()
};
assert_eq!(
opts.connection_uri(),
"mongodb://%2Ftmp%2Fsmartdb-test.sock"
);
}
#[test]
fn test_validation_file_storage_requires_path() {
let opts = RustDbOptions {
storage: StorageType::File,
storage_path: None,
..Default::default()
};
assert!(opts.validate().is_err());
}
}