feat(tree): close rust family shell cutover
This commit is contained in:
@@ -294,6 +294,8 @@ pub struct RuntimeCommandExecutionPlan {
|
||||
pub struct RuntimeCommandArtifactPlan {
|
||||
pub command_log: RuntimeCommandLogArtifactPlan,
|
||||
pub domain_event: Option<RuntimeDomainEventArtifactPlan>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub domain_events: Vec<RuntimeDomainEventArtifactPlan>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
@@ -627,7 +629,8 @@ struct DocumentEmbedCommandPayload {
|
||||
document_id: String,
|
||||
workspace_id: Option<String>,
|
||||
revision: Option<u64>,
|
||||
content: Value,
|
||||
#[serde(default)]
|
||||
content: Option<Value>,
|
||||
conflict_detection_key: Option<String>,
|
||||
source_document_id: String,
|
||||
target_document_id: String,
|
||||
@@ -2247,6 +2250,32 @@ fn materialize_tree_domain_event_plan(
|
||||
result: &Value,
|
||||
) -> Option<(String, Value)> {
|
||||
let event_plan = plan.args_json.get("domainEventPlan")?;
|
||||
materialize_tree_domain_event_value(event_plan, plan, result)
|
||||
}
|
||||
|
||||
fn materialize_tree_domain_event_plans(
|
||||
plan: &RuntimeCommandExecutionPlan,
|
||||
result: &Value,
|
||||
) -> Vec<(String, Value)> {
|
||||
plan.args_json
|
||||
.get("domainEventPlans")
|
||||
.and_then(Value::as_array)
|
||||
.map(|event_plans| {
|
||||
event_plans
|
||||
.iter()
|
||||
.filter_map(|event_plan| materialize_tree_domain_event_value(event_plan, plan, result))
|
||||
.collect()
|
||||
})
|
||||
.filter(|event_plans: &Vec<(String, Value)>| !event_plans.is_empty())
|
||||
.or_else(|| materialize_tree_domain_event_plan(plan, result).map(|event_plan| vec![event_plan]))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn materialize_tree_domain_event_value(
|
||||
event_plan: &Value,
|
||||
plan: &RuntimeCommandExecutionPlan,
|
||||
result: &Value,
|
||||
) -> Option<(String, Value)> {
|
||||
if event_plan.get("family").and_then(Value::as_str) != Some("tree")
|
||||
|| event_plan.get("schema").and_then(Value::as_str) != Some("mnote.tree.domain_event")
|
||||
|| event_plan.get("schemaVersion").and_then(Value::as_i64) != Some(1)
|
||||
@@ -2354,8 +2383,8 @@ pub fn build_runtime_command_artifact_plan(
|
||||
.and_then(|target| target.block_id.as_ref())
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
let materialized_event_plan = materialize_tree_domain_event_plan(plan, result);
|
||||
let command_payload = if let Some((_, domain_event_plan)) = &materialized_event_plan {
|
||||
let materialized_event_plans = materialize_tree_domain_event_plans(plan, result);
|
||||
let command_payload = if let Some((_, domain_event_plan)) = materialized_event_plans.first() {
|
||||
if let Some(stream_delta) = domain_event_plan.get("streamDelta") {
|
||||
let mut payload = command.payload.clone();
|
||||
if let Some(map) = payload.as_object_mut() {
|
||||
@@ -2374,6 +2403,18 @@ pub fn build_runtime_command_artifact_plan(
|
||||
command.payload.clone()
|
||||
};
|
||||
|
||||
let aggregate_type = if target_block_id.is_some() {
|
||||
"block"
|
||||
} else if target_page_id.is_some() {
|
||||
"page"
|
||||
} else {
|
||||
"workspace"
|
||||
};
|
||||
let aggregate_id = target_block_id
|
||||
.as_deref()
|
||||
.or(target_page_id.as_deref())
|
||||
.unwrap_or(workspace_id);
|
||||
|
||||
let command_log = RuntimeCommandLogArtifactPlan {
|
||||
workspace_id: workspace_id.into(),
|
||||
id: command_log_id.clone(),
|
||||
@@ -2400,25 +2441,22 @@ pub fn build_runtime_command_artifact_plan(
|
||||
finished_at: Some(now.into()),
|
||||
};
|
||||
|
||||
let domain_event = materialized_event_plan.map(|(event_type, domain_event_plan)| {
|
||||
let aggregate_type = if target_block_id.is_some() {
|
||||
"block"
|
||||
} else if target_page_id.is_some() {
|
||||
"page"
|
||||
} else {
|
||||
"workspace"
|
||||
};
|
||||
let aggregate_id = target_block_id
|
||||
.as_deref()
|
||||
.or(target_page_id.as_deref())
|
||||
.unwrap_or(workspace_id);
|
||||
let domain_events: Vec<RuntimeDomainEventArtifactPlan> = materialized_event_plans
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (event_type, domain_event_plan))| {
|
||||
RuntimeDomainEventArtifactPlan {
|
||||
workspace_id: workspace_id.into(),
|
||||
id: format!("evt_{}", command.command_id),
|
||||
id: tree_domain_event_artifact_id(
|
||||
&command.command_id,
|
||||
event_type,
|
||||
index,
|
||||
materialized_event_plans.len(),
|
||||
),
|
||||
request_id: context.request_id.clone(),
|
||||
trace_id: context.trace_id.clone(),
|
||||
command_id: command.command_id.clone(),
|
||||
command_log_id,
|
||||
command_log_id: command_log_id.clone(),
|
||||
event_type: event_type.clone(),
|
||||
aggregate_type: aggregate_type.into(),
|
||||
aggregate_id: aggregate_id.into(),
|
||||
@@ -2435,14 +2473,110 @@ pub fn build_runtime_command_artifact_plan(
|
||||
),
|
||||
created_at: now.into(),
|
||||
}
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
let domain_event = domain_events.first().cloned();
|
||||
|
||||
Some(RuntimeCommandArtifactPlan {
|
||||
command_log,
|
||||
domain_event,
|
||||
domain_events,
|
||||
})
|
||||
}
|
||||
|
||||
fn tree_domain_event_artifact_id(command_id: &str, event_type: &str, index: usize, total: usize) -> String {
|
||||
if total <= 1 || index == 0 {
|
||||
return format!("evt_{command_id}");
|
||||
}
|
||||
let suffix: String = event_type
|
||||
.chars()
|
||||
.map(|ch| {
|
||||
if ch.is_ascii_alphanumeric() {
|
||||
ch.to_ascii_lowercase()
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
format!("evt_{command_id}_{:02}_{suffix}", index + 1)
|
||||
}
|
||||
|
||||
fn read_preflight_field<'a>(command: &'a RuntimeCommandEnvelopeWire, field: &str) -> Option<&'a Value> {
|
||||
command
|
||||
.preflight_data
|
||||
.as_ref()
|
||||
.and_then(|preflight| preflight.get(field))
|
||||
}
|
||||
|
||||
fn compose_content_with_blocks(content: &Value, blocks: Vec<Value>) -> Value {
|
||||
if content.is_array() {
|
||||
return Value::Array(blocks);
|
||||
}
|
||||
if let Some(map) = content.as_object() {
|
||||
let mut next = map.clone();
|
||||
next.insert("blocks".into(), Value::Array(blocks));
|
||||
return Value::Object(next);
|
||||
}
|
||||
json!({ "blocks": blocks })
|
||||
}
|
||||
|
||||
fn build_page_aggregate_embed_plan(
|
||||
command_wire: &RuntimeCommandEnvelopeWire,
|
||||
payload: &DocumentEmbedCommandPayload,
|
||||
) -> Result<Option<Value>, BridgeError> {
|
||||
let Some(input) = read_preflight_field(command_wire, "pageAggregateEmbed") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let target_content = input
|
||||
.get("targetContent")
|
||||
.ok_or_else(|| BridgeError::validation("pageAggregateEmbed 缺少 targetContent"))?;
|
||||
let source_title = read_trimmed_str_field(input, "sourceTitle").unwrap_or("无标题");
|
||||
let anchor_block_id = payload
|
||||
.anchor_block_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(|| read_trimmed_str_field(input, "anchorBlockId"));
|
||||
let block_id = read_trimmed_str_field(input, "blockId")
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| format!("page_ref_{}", payload.source_document_id));
|
||||
let current_blocks = normalize_blocks_from_value(target_content);
|
||||
let insert_index = anchor_block_id
|
||||
.and_then(|anchor_id| {
|
||||
current_blocks.iter().position(|block| {
|
||||
read_trimmed_str_field(block, "id")
|
||||
.map(|block_id| block_id == anchor_id)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
})
|
||||
.map(|index| index + 1)
|
||||
.unwrap_or(current_blocks.len());
|
||||
let page_reference_block = json!({
|
||||
"id": block_id,
|
||||
"type": "pageReference",
|
||||
"props": {
|
||||
"pageId": payload.source_document_id,
|
||||
"title": source_title,
|
||||
},
|
||||
});
|
||||
let mut next_blocks = Vec::with_capacity(current_blocks.len() + 1);
|
||||
next_blocks.extend(current_blocks.iter().take(insert_index).cloned());
|
||||
next_blocks.push(page_reference_block.clone());
|
||||
next_blocks.extend(current_blocks.iter().skip(insert_index).cloned());
|
||||
let next_content = compose_content_with_blocks(target_content, next_blocks);
|
||||
Ok(Some(json!({
|
||||
"schema": "mnote.page_aggregate.embed_plan",
|
||||
"schemaVersion": 1,
|
||||
"sourceDocumentId": payload.source_document_id,
|
||||
"targetDocumentId": payload.target_document_id,
|
||||
"anchorBlockId": anchor_block_id,
|
||||
"insertIndex": insert_index,
|
||||
"block": page_reference_block,
|
||||
"content": next_content,
|
||||
"blockCount": normalize_blocks_from_value(&next_content).len(),
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RuntimeBlockSummary {
|
||||
@@ -6288,6 +6422,8 @@ fn build_file_tree_projection_result(
|
||||
filters: &KernelProjectionFilter,
|
||||
) -> KernelProjectionResult {
|
||||
let (assets_by_doc, asset_by_id, child_assets_by_parent) = build_file_tree_assets(data);
|
||||
let requested_query = normalize_projection_query(filters.query.as_deref());
|
||||
let requested_max_results = filters.max_results;
|
||||
let page_ids = subtree
|
||||
.nodes
|
||||
.iter()
|
||||
@@ -6595,6 +6731,8 @@ fn build_file_tree_projection_result(
|
||||
}
|
||||
|
||||
apply_file_tree_projection_search_filter(&mut items, &mut edges, filters);
|
||||
let visible_rows = items.len();
|
||||
let visible_edges = edges.len();
|
||||
|
||||
KernelProjectionResult {
|
||||
projection_id: format!(
|
||||
@@ -6605,6 +6743,31 @@ fn build_file_tree_projection_result(
|
||||
root_node_id: root_node_id.map(ToOwned::to_owned),
|
||||
items,
|
||||
edges,
|
||||
meta: BTreeMap::from([(
|
||||
"search".into(),
|
||||
json!({
|
||||
"query": requested_query.clone(),
|
||||
"maxResults": requested_max_results,
|
||||
"maxResultsRule": "matches_only_before_ancestor_completion",
|
||||
"ancestorCompletion": "include_all_ancestors_after_match_truncation",
|
||||
"ordering": "kernel_file_tree_preorder",
|
||||
"indexingVisibility": {
|
||||
"schema": "mnote.file_tree.indexing_visibility",
|
||||
"schemaVersion": 1,
|
||||
"source": "kernel.project_view",
|
||||
"status": "visible",
|
||||
"requestKey": requested_query
|
||||
.as_ref()
|
||||
.map(|query| format!("{}:{query}", root_node_id.unwrap_or("root"))),
|
||||
"indexedResourceKinds": ["document", "index", "asset", "asset_folder", "mindmap", "book", "pdf"],
|
||||
"visibleResourceKinds": ["document", "index", "asset", "asset_folder", "mindmap", "book", "pdf"],
|
||||
"metrics": {
|
||||
"visibleRows": visible_rows,
|
||||
"visibleEdges": visible_edges,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6883,6 +7046,7 @@ fn build_kernel_projection_result(
|
||||
root_node_id: root_node_id.map(ToOwned::to_owned),
|
||||
items,
|
||||
edges: subtree.edges,
|
||||
meta: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7677,11 +7841,24 @@ fn execute_command(
|
||||
"documentId": payload.document_id,
|
||||
}),
|
||||
);
|
||||
let domain_event_payload = document_save_domain_event_payload(
|
||||
let page_body_event_payload = document_save_page_body_domain_event_payload(
|
||||
&payload,
|
||||
&editor_document,
|
||||
);
|
||||
let snapshot_event_payload = document_save_snapshot_domain_event_payload(
|
||||
&payload,
|
||||
&canonical_content,
|
||||
)?;
|
||||
let page_body_event_plan = tree_domain_event_plan_with_payload(
|
||||
"page.body.saved",
|
||||
page_body_event_payload,
|
||||
stream_delta_hint.clone(),
|
||||
);
|
||||
let snapshot_event_plan = tree_domain_event_plan_with_payload(
|
||||
"document.snapshot.saved",
|
||||
snapshot_event_payload,
|
||||
stream_delta_hint.clone(),
|
||||
);
|
||||
json!({
|
||||
"id": payload.document_id,
|
||||
"content": canonical_content,
|
||||
@@ -7691,11 +7868,8 @@ fn execute_command(
|
||||
"conflictDetectionKey": payload.conflict_detection_key,
|
||||
"streamDeltaHint": stream_delta_hint,
|
||||
"domainEventHint": tree_domain_event_hint("page.body.saved"),
|
||||
"domainEventPlan": tree_domain_event_plan_with_payload(
|
||||
"page.body.saved",
|
||||
domain_event_payload,
|
||||
stream_delta_hint,
|
||||
),
|
||||
"domainEventPlan": page_body_event_plan.clone(),
|
||||
"domainEventPlans": [page_body_event_plan, snapshot_event_plan],
|
||||
})
|
||||
},
|
||||
}))
|
||||
@@ -7707,6 +7881,17 @@ fn execute_command(
|
||||
} else {
|
||||
"documents.embed"
|
||||
};
|
||||
let page_aggregate_embed_plan = build_page_aggregate_embed_plan(&command_wire, &payload)?;
|
||||
let embed_content = page_aggregate_embed_plan
|
||||
.as_ref()
|
||||
.and_then(|plan| plan.get("content"))
|
||||
.cloned()
|
||||
.or_else(|| payload.content.clone())
|
||||
.ok_or_else(|| {
|
||||
BridgeError::validation(format!(
|
||||
"{command_name} 缺少 content 或 pageAggregateEmbed preflight"
|
||||
))
|
||||
})?;
|
||||
let command = CommandEnvelope {
|
||||
name: command_name.into(),
|
||||
command_id: command_wire.command_id.clone(),
|
||||
@@ -7718,7 +7903,7 @@ fn execute_command(
|
||||
page_id: payload.document_id.clone(),
|
||||
workspace_id: payload.workspace_id.clone(),
|
||||
revision: payload.revision,
|
||||
content_json: serde_json::to_string(&payload.content).map_err(|error| {
|
||||
content_json: serde_json::to_string(&embed_content).map_err(|error| {
|
||||
BridgeError::validation(format!(
|
||||
"{command_name} content 序列化失败: {error}"
|
||||
))
|
||||
@@ -7743,12 +7928,13 @@ fn execute_command(
|
||||
payload_json: request.payload_json,
|
||||
args_json: json!({
|
||||
"id": payload.document_id,
|
||||
"content": payload.content,
|
||||
"content": embed_content,
|
||||
"expectedRevision": payload.revision,
|
||||
"conflictDetectionKey": payload.conflict_detection_key,
|
||||
"sourceDocumentId": payload.source_document_id,
|
||||
"targetDocumentId": payload.target_document_id,
|
||||
"anchorBlockId": payload.anchor_block_id,
|
||||
"pageAggregateEmbedPlan": page_aggregate_embed_plan,
|
||||
"streamDeltaHint": tree_stream_delta_hint("noop", json!({})),
|
||||
"domainEventHint": tree_domain_event_hint("tree.node.embedded"),
|
||||
"domainEventPlan": tree_domain_event_plan(
|
||||
@@ -7979,8 +8165,12 @@ fn execute_command(
|
||||
"documents.move" | "tree.subtree.move" => {
|
||||
let payload: DocumentMoveCommandPayload = parse_payload(command_wire.payload.clone())?;
|
||||
validate_document_move_legality(&payload, command_wire.preflight_data.as_ref())?;
|
||||
let normalized_move =
|
||||
let move_order_plan =
|
||||
resolve_document_move_order_plan(&payload, command_wire.preflight_data.as_ref())?;
|
||||
let tree_write_operation = document_move_write_operation(
|
||||
context.workspace_id.as_deref(),
|
||||
move_order_plan.as_ref(),
|
||||
);
|
||||
let command_name = if command_wire.name == "tree.subtree.move" {
|
||||
"tree.subtree.move"
|
||||
} else {
|
||||
@@ -8014,11 +8204,7 @@ fn execute_command(
|
||||
"id": payload.document_id,
|
||||
"parentId": payload.parent_id,
|
||||
"sortOrder": payload.sort_order,
|
||||
"normalizedMove": normalized_move,
|
||||
"treeWriteOperation": document_move_write_operation(
|
||||
context.workspace_id.as_deref(),
|
||||
normalized_move.as_ref(),
|
||||
),
|
||||
"treeWriteOperation": tree_write_operation,
|
||||
"streamDeltaHint": tree_stream_delta_hint("move_document", json!({
|
||||
"documentId": payload.document_id,
|
||||
"parentId": payload.parent_id,
|
||||
@@ -8792,9 +8978,24 @@ fn editor_document_block_ids(document: &EditorBlockDocument) -> Vec<String> {
|
||||
ids
|
||||
}
|
||||
|
||||
fn document_save_domain_event_payload(
|
||||
fn document_save_page_body_domain_event_payload(
|
||||
payload: &DocumentSaveCommandPayload,
|
||||
editor_document: &EditorBlockDocument,
|
||||
) -> Value {
|
||||
json!({
|
||||
"page": {
|
||||
"id": payload.document_id.clone(),
|
||||
"workspaceId": payload.workspace_id.clone(),
|
||||
},
|
||||
"blocks": {
|
||||
"ids": editor_document_block_ids(editor_document),
|
||||
"count": editor_document.blocks.len(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn document_save_snapshot_domain_event_payload(
|
||||
payload: &DocumentSaveCommandPayload,
|
||||
canonical_content: &Value,
|
||||
) -> Result<Value, BridgeError> {
|
||||
Ok(json!({
|
||||
@@ -8807,10 +9008,6 @@ fn document_save_domain_event_payload(
|
||||
"contentHash": stable_json_content_hash(canonical_content)?,
|
||||
"updatedAt": Value::Null,
|
||||
},
|
||||
"blocks": {
|
||||
"ids": editor_document_block_ids(editor_document),
|
||||
"count": editor_document.blocks.len(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -9023,26 +9220,47 @@ mod tests {
|
||||
"id": "block_1",
|
||||
"type": "paragraph",
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "noop",
|
||||
"args": {}
|
||||
},
|
||||
"domainEventHint": {
|
||||
"family": "tree",
|
||||
"eventType": "tree.block.patched"
|
||||
},
|
||||
"domainEventPlan": {
|
||||
"family": "tree",
|
||||
"schema": "mnote.tree.domain_event",
|
||||
"schemaVersion": 1,
|
||||
"eventType": "tree.block.patched",
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "noop",
|
||||
"args": {}
|
||||
}
|
||||
}
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "resync_required",
|
||||
"args": {
|
||||
"reason": "blocks.patch",
|
||||
"documentId": "doc_1",
|
||||
"blockId": "block_1"
|
||||
}
|
||||
},
|
||||
"domainEventHint": {
|
||||
"family": "tree",
|
||||
"eventType": "block.patched"
|
||||
},
|
||||
"domainEventPlan": {
|
||||
"family": "tree",
|
||||
"schema": "mnote.tree.domain_event",
|
||||
"schemaVersion": 1,
|
||||
"eventType": "block.patched",
|
||||
"payload": {
|
||||
"document": {
|
||||
"id": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
},
|
||||
"block": {
|
||||
"id": "block_1"
|
||||
},
|
||||
"patch": {
|
||||
"summary": "replace_block",
|
||||
"nextType": "paragraph"
|
||||
}
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "resync_required",
|
||||
"args": {
|
||||
"reason": "blocks.patch",
|
||||
"documentId": "doc_1",
|
||||
"blockId": "block_1"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -10197,11 +10415,6 @@ mod tests {
|
||||
"id": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
},
|
||||
"snapshot": {
|
||||
"version": 8,
|
||||
"contentHash": "fnv1a64:d039f8f3496411e8",
|
||||
"updatedAt": null
|
||||
},
|
||||
"blocks": {
|
||||
"ids": ["legacy_content_1"],
|
||||
"count": 1
|
||||
@@ -10218,6 +10431,62 @@ mod tests {
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
plan.args_json["domainEventPlans"],
|
||||
json!([
|
||||
{
|
||||
"family": "tree",
|
||||
"schema": "mnote.tree.domain_event",
|
||||
"schemaVersion": 1,
|
||||
"eventType": "page.body.saved",
|
||||
"payload": {
|
||||
"page": {
|
||||
"id": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
},
|
||||
"blocks": {
|
||||
"ids": ["legacy_content_1"],
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "resync_required",
|
||||
"args": {
|
||||
"reason": "page_body_saved",
|
||||
"pageId": "doc_1",
|
||||
"documentId": "doc_1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"family": "tree",
|
||||
"schema": "mnote.tree.domain_event",
|
||||
"schemaVersion": 1,
|
||||
"eventType": "document.snapshot.saved",
|
||||
"payload": {
|
||||
"page": {
|
||||
"id": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
},
|
||||
"snapshot": {
|
||||
"version": 8,
|
||||
"contentHash": "fnv1a64:d039f8f3496411e8",
|
||||
"updatedAt": null
|
||||
}
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "resync_required",
|
||||
"args": {
|
||||
"reason": "page_body_saved",
|
||||
"pageId": "doc_1",
|
||||
"documentId": "doc_1"
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10610,10 +10879,11 @@ mod tests {
|
||||
"content": [{ "id": "block_1", "type": "pageReference" }],
|
||||
"expectedRevision": 5,
|
||||
"conflictDetectionKey": "conflict_5",
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
"streamDeltaHint": {
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
"pageAggregateEmbedPlan": null,
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "noop",
|
||||
"args": {}
|
||||
@@ -11177,7 +11447,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_subtree_move_command_includes_normalized_move_plan_from_snapshot() {
|
||||
fn tree_subtree_move_command_does_not_emit_legacy_normalized_move_fallback() {
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
context: demo_context(),
|
||||
command: RuntimeCommandEnvelopeWire {
|
||||
@@ -11226,9 +11496,15 @@ mod tests {
|
||||
assert_eq!(plan.command_name, "tree.subtree.move");
|
||||
assert_eq!(plan.function_name, "documents:move");
|
||||
assert_eq!(plan.args_json["sortOrder"], json!(-2));
|
||||
assert!(plan.args_json.get("normalizedMove").is_none());
|
||||
assert_eq!(
|
||||
plan.args_json["normalizedMove"],
|
||||
plan.args_json["treeWriteOperation"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"schema": "mnote.tree.write_operation",
|
||||
"schemaVersion": 1,
|
||||
"operation": "tree.subtree.move.write",
|
||||
"workspaceId": "ws_1",
|
||||
"documentId": "doc_b",
|
||||
"fromParentId": "source",
|
||||
"toParentId": "target",
|
||||
@@ -11711,20 +11987,33 @@ mod tests {
|
||||
page_id: Some("doc_2".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: json!({
|
||||
"documentId": "doc_2",
|
||||
"workspaceId": "ws_1",
|
||||
"revision": 5,
|
||||
"content": [{ "id": "block_1", "type": "pageReference" }],
|
||||
"conflictDetectionKey": "conflict_5",
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
}),
|
||||
preflight_data: None,
|
||||
reason: Some("树命令嵌入页面".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
payload: json!({
|
||||
"documentId": "doc_2",
|
||||
"workspaceId": "ws_1",
|
||||
"revision": 5,
|
||||
"conflictDetectionKey": "conflict_5",
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
}),
|
||||
preflight_data: Some(json!({
|
||||
"pageAggregateEmbed": {
|
||||
"sourceDocumentId": "doc_1",
|
||||
"sourceTitle": "来源页面",
|
||||
"targetDocumentId": "doc_2",
|
||||
"targetContent": {
|
||||
"blocks": [
|
||||
{ "id": "anchor_1", "type": "paragraph" }
|
||||
],
|
||||
"format": "editor"
|
||||
},
|
||||
"anchorBlockId": "anchor_1",
|
||||
"blockId": "page_ref_doc_1"
|
||||
}
|
||||
})),
|
||||
reason: Some("树命令嵌入页面".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
},
|
||||
})
|
||||
@@ -11736,15 +12025,59 @@ mod tests {
|
||||
assert_eq!(plan.command_name, "tree.node.embed");
|
||||
assert_eq!(
|
||||
plan.args_json,
|
||||
json!({
|
||||
"id": "doc_2",
|
||||
"content": [{ "id": "block_1", "type": "pageReference" }],
|
||||
"expectedRevision": 5,
|
||||
"conflictDetectionKey": "conflict_5",
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
"streamDeltaHint": {
|
||||
json!({
|
||||
"id": "doc_2",
|
||||
"content": {
|
||||
"blocks": [
|
||||
{ "id": "anchor_1", "type": "paragraph" },
|
||||
{
|
||||
"id": "page_ref_doc_1",
|
||||
"type": "pageReference",
|
||||
"props": {
|
||||
"pageId": "doc_1",
|
||||
"title": "来源页面"
|
||||
}
|
||||
}
|
||||
],
|
||||
"format": "editor"
|
||||
},
|
||||
"expectedRevision": 5,
|
||||
"conflictDetectionKey": "conflict_5",
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
"pageAggregateEmbedPlan": {
|
||||
"schema": "mnote.page_aggregate.embed_plan",
|
||||
"schemaVersion": 1,
|
||||
"sourceDocumentId": "doc_1",
|
||||
"targetDocumentId": "doc_2",
|
||||
"anchorBlockId": "anchor_1",
|
||||
"insertIndex": 1,
|
||||
"block": {
|
||||
"id": "page_ref_doc_1",
|
||||
"type": "pageReference",
|
||||
"props": {
|
||||
"pageId": "doc_1",
|
||||
"title": "来源页面"
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"blocks": [
|
||||
{ "id": "anchor_1", "type": "paragraph" },
|
||||
{
|
||||
"id": "page_ref_doc_1",
|
||||
"type": "pageReference",
|
||||
"props": {
|
||||
"pageId": "doc_1",
|
||||
"title": "来源页面"
|
||||
}
|
||||
}
|
||||
],
|
||||
"format": "editor"
|
||||
},
|
||||
"blockCount": 2
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "noop",
|
||||
"args": {}
|
||||
@@ -13181,6 +13514,18 @@ mod tests {
|
||||
result["projectionId"],
|
||||
json!("kernel_projection:file_tree:page_root")
|
||||
);
|
||||
assert_eq!(
|
||||
result["meta"]["search"]["indexingVisibility"]["schema"],
|
||||
json!("mnote.file_tree.indexing_visibility")
|
||||
);
|
||||
assert_eq!(
|
||||
result["meta"]["search"]["indexingVisibility"]["status"],
|
||||
json!("visible")
|
||||
);
|
||||
assert_eq!(
|
||||
result["meta"]["search"]["indexingVisibility"]["metrics"]["visibleRows"],
|
||||
json!(items.len())
|
||||
);
|
||||
assert_eq!(
|
||||
item_by_row_id["doc:page_root"]["rowKind"],
|
||||
json!("document")
|
||||
|
||||
Reference in New Issue
Block a user