feat: 收口 Rust Web 3000 主链
This commit is contained in:
@@ -455,6 +455,114 @@ pub fn read_stream_cursor_from_payload(payload: &Value) -> Option<String> {
|
||||
read_string_field(payload, &["cursor"])
|
||||
}
|
||||
|
||||
fn attach_workspace_dataset_projections(
|
||||
mut dataset: Value,
|
||||
sidebar_projection: Value,
|
||||
file_tree_projection: Value,
|
||||
) -> Value {
|
||||
let Some(map) = dataset.as_object_mut() else {
|
||||
return dataset;
|
||||
};
|
||||
map.insert(
|
||||
"kernel_sidebar_projection".into(),
|
||||
sidebar_projection.clone(),
|
||||
);
|
||||
map.insert("kernelSidebarProjection".into(), sidebar_projection);
|
||||
map.insert(
|
||||
"kernel_file_tree_projection".into(),
|
||||
file_tree_projection.clone(),
|
||||
);
|
||||
map.insert("kernelFileTreeProjection".into(), file_tree_projection);
|
||||
dataset
|
||||
}
|
||||
|
||||
async fn load_workspace_stream_snapshot(
|
||||
config: &AppConfig,
|
||||
context: &RequestContext,
|
||||
workspace_id: &str,
|
||||
depth: Option<u32>,
|
||||
) -> Result<Value, WebError> {
|
||||
let sidebar_loaded = load_projection_snapshot(
|
||||
config,
|
||||
context,
|
||||
&ProjectionSnapshotSpec {
|
||||
workspace_id,
|
||||
root_node_id: None,
|
||||
depth,
|
||||
query: None,
|
||||
max_results: None,
|
||||
projection: KernelProjectionKind::SidebarTree,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let file_tree_loaded = load_projection_snapshot(
|
||||
config,
|
||||
context,
|
||||
&ProjectionSnapshotSpec {
|
||||
workspace_id,
|
||||
root_node_id: None,
|
||||
depth,
|
||||
query: None,
|
||||
max_results: None,
|
||||
projection: KernelProjectionKind::FileTree,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let dataset = attach_workspace_dataset_projections(
|
||||
sidebar_loaded.dataset,
|
||||
sidebar_loaded.projection.clone(),
|
||||
file_tree_loaded.projection,
|
||||
);
|
||||
|
||||
Ok(json!({
|
||||
"dataset": dataset,
|
||||
"tree": sidebar_loaded.projection,
|
||||
}))
|
||||
}
|
||||
|
||||
fn delta_document_patch_is_title_only(candidate: &Value) -> bool {
|
||||
let Some(map) = candidate.as_object() else {
|
||||
return false;
|
||||
};
|
||||
map.keys().all(|key| {
|
||||
matches!(
|
||||
key.as_str(),
|
||||
"id" | "documentId" | "title" | "updatedAt" | "updated_at"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn delta_requires_projection_snapshot(delta: &Value) -> bool {
|
||||
let Some(map) = delta.as_object() else {
|
||||
return false;
|
||||
};
|
||||
let op = map
|
||||
.get("op")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or("");
|
||||
if op.is_empty() || op == "noop" {
|
||||
return false;
|
||||
}
|
||||
if op == "upsert_document" {
|
||||
return !map
|
||||
.get("node")
|
||||
.or_else(|| map.get("document"))
|
||||
.is_some_and(delta_document_patch_is_title_only);
|
||||
}
|
||||
if op == "upsert_documents" {
|
||||
let Some(items) = map
|
||||
.get("upsertDocuments")
|
||||
.or_else(|| map.get("upsert_documents"))
|
||||
.and_then(Value::as_array)
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
return !items.iter().all(delta_document_patch_is_title_only);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn with_stream_kind(payload: &Value, kind: &str) -> Value {
|
||||
if let Some(mut map) = payload.as_object().cloned() {
|
||||
map.insert("kind".into(), Value::String(kind.into()));
|
||||
@@ -466,16 +574,23 @@ pub fn with_stream_kind(payload: &Value, kind: &str) -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_stream_delta_payload(
|
||||
pub async fn build_stream_delta_payload(
|
||||
config: &AppConfig,
|
||||
context: &RequestContext,
|
||||
query: &StreamSnapshotQuery,
|
||||
workspace_id: &str,
|
||||
overview: &Value,
|
||||
cursor: Option<String>,
|
||||
delta: Value,
|
||||
) -> Value {
|
||||
) -> Result<Value, WebError> {
|
||||
let scope = resolve_stream_scope(query);
|
||||
json!({
|
||||
let snapshot =
|
||||
if scope == StreamSnapshotScope::Workspace && delta_requires_projection_snapshot(&delta) {
|
||||
load_workspace_stream_snapshot(config, context, workspace_id, query.depth).await?
|
||||
} else {
|
||||
Value::Null
|
||||
};
|
||||
Ok(json!({
|
||||
"kind": "delta",
|
||||
"revision": cursor.clone().unwrap_or_else(|| "0".into()),
|
||||
"stream": scope.as_str(),
|
||||
@@ -487,9 +602,9 @@ pub fn build_stream_delta_payload(
|
||||
"depth": query.depth,
|
||||
"cursor": cursor,
|
||||
"data": delta,
|
||||
"snapshot": Value::Null,
|
||||
"snapshot": snapshot,
|
||||
"overview": overview,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn load_stream_overview(
|
||||
@@ -522,24 +637,8 @@ pub async fn load_stream_snapshot(
|
||||
|
||||
let snapshot = match scope {
|
||||
StreamSnapshotScope::Workspace => {
|
||||
let loaded = load_projection_snapshot(
|
||||
config,
|
||||
context,
|
||||
&ProjectionSnapshotSpec {
|
||||
workspace_id: &effective_workspace_id,
|
||||
root_node_id: None,
|
||||
depth: query.depth,
|
||||
query: None,
|
||||
max_results: None,
|
||||
projection: KernelProjectionKind::SidebarTree,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
json!({
|
||||
"dataset": loaded.dataset,
|
||||
"tree": loaded.projection,
|
||||
})
|
||||
load_workspace_stream_snapshot(config, context, &effective_workspace_id, query.depth)
|
||||
.await?
|
||||
}
|
||||
StreamSnapshotScope::Subtree => {
|
||||
let root_node_id =
|
||||
@@ -590,8 +689,8 @@ pub async fn load_stream_snapshot(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
resolve_stream_change, resolve_stream_cursor, resolve_stream_scope, StreamChangeKind,
|
||||
StreamSnapshotQuery, StreamSnapshotScope,
|
||||
delta_requires_projection_snapshot, resolve_stream_change, resolve_stream_cursor,
|
||||
resolve_stream_scope, StreamChangeKind, StreamSnapshotQuery, StreamSnapshotScope,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -1192,4 +1291,39 @@ mod tests {
|
||||
assert_eq!(change.kind, StreamChangeKind::Resync);
|
||||
assert_eq!(change.delta, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_only_delta_does_not_require_projection_snapshot() {
|
||||
assert!(!delta_requires_projection_snapshot(&json!({
|
||||
"op": "upsert_document",
|
||||
"document": {
|
||||
"id": "page_1",
|
||||
"title": "新标题"
|
||||
}
|
||||
})));
|
||||
assert!(!delta_requires_projection_snapshot(&json!({
|
||||
"op": "upsert_documents",
|
||||
"upsertDocuments": [
|
||||
{
|
||||
"id": "page_1",
|
||||
"title": "新标题"
|
||||
}
|
||||
]
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_delta_requires_projection_snapshot() {
|
||||
assert!(delta_requires_projection_snapshot(&json!({
|
||||
"op": "move_document",
|
||||
"documentId": "page_1",
|
||||
"parentId": "page_2"
|
||||
})));
|
||||
assert!(delta_requires_projection_snapshot(&json!({
|
||||
"op": "upsert_assets",
|
||||
"upsertAssets": [
|
||||
{ "id": "asset_1" }
|
||||
]
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user