feat(rust-provider): Add Rust-backed provider with XFS-safe durability via IPC bridge, TypeScript provider, tests and docs
This commit is contained in:
9
rust/crates/smartfs-protocol/Cargo.toml
Normal file
9
rust/crates/smartfs-protocol/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "smartfs-protocol"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
243
rust/crates/smartfs-protocol/src/lib.rs
Normal file
243
rust/crates/smartfs-protocol/src/lib.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// ── IPC envelope types ──────────────────────────────────────────────────────
|
||||
|
||||
/// Request from TypeScript (via stdin)
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct IpcRequest {
|
||||
pub id: String,
|
||||
pub method: String,
|
||||
pub params: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Response to TypeScript (via stdout)
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IpcResponse {
|
||||
pub id: String,
|
||||
pub success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl IpcResponse {
|
||||
pub fn ok(id: String, result: serde_json::Value) -> Self {
|
||||
Self { id, success: true, result: Some(result), error: None }
|
||||
}
|
||||
|
||||
pub fn ok_void(id: String) -> Self {
|
||||
Self { id, success: true, result: None, error: None }
|
||||
}
|
||||
|
||||
pub fn err(id: String, error: String) -> Self {
|
||||
Self { id, success: false, result: None, error: Some(error) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream chunk (Rust → TS, before final response)
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IpcStreamChunk {
|
||||
pub id: String,
|
||||
pub stream: bool,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Unsolicited event (Rust → TS)
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct IpcEvent {
|
||||
pub event: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
// ── Filesystem domain types ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct FileStats {
|
||||
pub size: u64,
|
||||
pub birthtime: String,
|
||||
pub mtime: String,
|
||||
pub atime: String,
|
||||
#[serde(rename = "isFile")]
|
||||
pub is_file: bool,
|
||||
#[serde(rename = "isDirectory")]
|
||||
pub is_directory: bool,
|
||||
#[serde(rename = "isSymbolicLink")]
|
||||
pub is_symbolic_link: bool,
|
||||
pub mode: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DirectoryEntry {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
#[serde(rename = "isFile")]
|
||||
pub is_file: bool,
|
||||
#[serde(rename = "isDirectory")]
|
||||
pub is_directory: bool,
|
||||
#[serde(rename = "isSymbolicLink")]
|
||||
pub is_symbolic_link: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stats: Option<FileStats>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct WatchEvent {
|
||||
#[serde(rename = "type")]
|
||||
pub event_type: String,
|
||||
pub path: String,
|
||||
pub timestamp: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stats: Option<FileStats>,
|
||||
}
|
||||
|
||||
// ── Command parameter types ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ReadFileParams {
|
||||
pub path: String,
|
||||
pub encoding: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WriteFileParams {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
pub atomic: Option<bool>,
|
||||
pub mode: Option<u32>,
|
||||
pub encoding: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AppendFileParams {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
pub encoding: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PathParams {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CopyMoveParams {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub overwrite: Option<bool>,
|
||||
#[serde(rename = "preserveTimestamps")]
|
||||
pub preserve_timestamps: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ListDirectoryParams {
|
||||
pub path: String,
|
||||
pub recursive: Option<bool>,
|
||||
#[serde(rename = "includeStats")]
|
||||
pub include_stats: Option<bool>,
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateDirectoryParams {
|
||||
pub path: String,
|
||||
pub recursive: Option<bool>,
|
||||
pub mode: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteDirectoryParams {
|
||||
pub path: String,
|
||||
pub recursive: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WatchParams {
|
||||
pub path: String,
|
||||
pub id: String,
|
||||
pub recursive: Option<bool>,
|
||||
}
|
||||
|
||||
// ── Batch operations ────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BatchOp {
|
||||
#[serde(rename = "type")]
|
||||
pub op_type: String,
|
||||
pub path: String,
|
||||
#[serde(rename = "targetPath")]
|
||||
pub target_path: Option<String>,
|
||||
pub content: Option<String>,
|
||||
pub encoding: Option<String>,
|
||||
pub atomic: Option<bool>,
|
||||
pub mode: Option<u32>,
|
||||
pub overwrite: Option<bool>,
|
||||
pub recursive: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BatchResult {
|
||||
pub index: usize,
|
||||
pub success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BatchParams {
|
||||
pub operations: Vec<BatchOp>,
|
||||
}
|
||||
|
||||
// ── Transaction operations ──────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TransactionOp {
|
||||
#[serde(rename = "type")]
|
||||
pub op_type: String,
|
||||
pub path: String,
|
||||
#[serde(rename = "targetPath")]
|
||||
pub target_path: Option<String>,
|
||||
pub content: Option<String>,
|
||||
pub encoding: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TransactionParams {
|
||||
pub operations: Vec<TransactionOp>,
|
||||
}
|
||||
|
||||
// ── Path operations ─────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NormalizePathParams {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct JoinPathParams {
|
||||
pub segments: Vec<String>,
|
||||
}
|
||||
|
||||
// ── Streaming operations ────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ReadFileStreamParams {
|
||||
pub path: String,
|
||||
#[serde(rename = "chunkSize")]
|
||||
pub chunk_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WriteStreamBeginParams {
|
||||
pub path: String,
|
||||
pub atomic: Option<bool>,
|
||||
pub mode: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WriteStreamChunkParams {
|
||||
#[serde(rename = "streamId")]
|
||||
pub stream_id: String,
|
||||
pub data: String,
|
||||
pub last: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user