0.6 rust重构01
This commit is contained in:
Generated
+38
@@ -0,0 +1,38 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "core-domain"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "core-protocol"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"core-domain",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-log"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"core-domain",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "index-fts"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"core-domain",
|
||||
"event-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "storage-convex-bridge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"core-domain",
|
||||
"core-protocol",
|
||||
"event-log",
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/core-domain",
|
||||
"crates/core-protocol",
|
||||
"crates/event-log",
|
||||
"crates/storage-convex-bridge",
|
||||
"crates/index-fts",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
version = "0.1.0"
|
||||
authors = ["mnote"]
|
||||
|
||||
[workspace.metadata]
|
||||
description = "mnote 单仓收口阶段的 Rust 内核 workspace"
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "core-domain"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,6 @@
|
||||
# core-domain
|
||||
|
||||
阶段 1 骨架 crate。
|
||||
|
||||
职责说明请先看:
|
||||
- ../../design/phase1-b-crate-responsibilities-v0.md
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::ids::{AssetId, AssetVersionId, BlockId, PageId, TaskId};
|
||||
use crate::time::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ActorRef {
|
||||
pub actor_type: String,
|
||||
pub actor_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SourceKind {
|
||||
Web,
|
||||
Cli,
|
||||
Agent,
|
||||
Job,
|
||||
Import,
|
||||
System,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ChangeReason {
|
||||
pub code: String,
|
||||
pub detail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RefLink {
|
||||
Page(PageId),
|
||||
Block(BlockId),
|
||||
Asset(AssetId),
|
||||
AssetVersion(AssetVersionId),
|
||||
Task(TaskId),
|
||||
External(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AuditInfo {
|
||||
pub created_at: Timestamp,
|
||||
pub updated_at: Timestamp,
|
||||
pub created_by: Option<ActorRef>,
|
||||
pub updated_by: Option<ActorRef>,
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
use crate::audit::{ActorRef, AuditInfo, ChangeReason, RefLink, SourceKind};
|
||||
use crate::ids::{
|
||||
AgentSessionId, AssetId, AssetVersionId, BlockId, CommandLogId, EventId, PageId, ReferenceId,
|
||||
TaskId, WorkspaceId,
|
||||
};
|
||||
use crate::time::Timestamp;
|
||||
use crate::types::{Locale, Revision};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Workspace {
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub slug: String,
|
||||
pub title: String,
|
||||
pub owner_actor_id: String,
|
||||
pub default_locale: Locale,
|
||||
pub storage_policy: String,
|
||||
pub sync_policy: String,
|
||||
pub feature_flags: Vec<String>,
|
||||
pub archived_at: Option<Timestamp>,
|
||||
pub audit: AuditInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PageType {
|
||||
Note,
|
||||
Doc,
|
||||
DatabaseRecord,
|
||||
Inbox,
|
||||
Template,
|
||||
System,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PageStatus {
|
||||
Active,
|
||||
Archived,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Page {
|
||||
pub page_id: PageId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub parent_page_id: Option<PageId>,
|
||||
pub title: String,
|
||||
pub slug: String,
|
||||
pub icon: Option<String>,
|
||||
pub cover_asset_id: Option<AssetId>,
|
||||
pub body_root_block_id: Option<BlockId>,
|
||||
pub page_type: PageType,
|
||||
pub status: PageStatus,
|
||||
pub last_edited_at: Option<Timestamp>,
|
||||
pub last_edited_by: Option<ActorRef>,
|
||||
pub current_revision: Revision,
|
||||
pub audit: AuditInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BlockType {
|
||||
Paragraph,
|
||||
Heading,
|
||||
BulletedListItem,
|
||||
NumberedListItem,
|
||||
Todo,
|
||||
Quote,
|
||||
CodeBlock,
|
||||
Divider,
|
||||
Callout,
|
||||
PageReference,
|
||||
BlockReference,
|
||||
EmbedAsset,
|
||||
EmbedView,
|
||||
EmbedOnlyoffice,
|
||||
EmbedMindmap,
|
||||
EmbedTable,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Block {
|
||||
pub block_id: BlockId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub page_id: PageId,
|
||||
pub parent_block_id: Option<BlockId>,
|
||||
pub prev_block_id: Option<BlockId>,
|
||||
pub next_block_id: Option<BlockId>,
|
||||
pub sort_key: String,
|
||||
pub block_type: BlockType,
|
||||
pub content: String,
|
||||
pub props: Vec<(String, String)>,
|
||||
pub annotations: Vec<String>,
|
||||
pub status: String,
|
||||
pub revision: Revision,
|
||||
pub deleted_at: Option<Timestamp>,
|
||||
pub audit: AuditInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AssetKind {
|
||||
Image,
|
||||
Office,
|
||||
Pdf,
|
||||
Audio,
|
||||
Video,
|
||||
Export,
|
||||
OcrDerived,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AssetStatus {
|
||||
Active,
|
||||
Archived,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Asset {
|
||||
pub asset_id: AssetId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub storage_key: String,
|
||||
pub original_name: String,
|
||||
pub mime_type: String,
|
||||
pub size_bytes: u64,
|
||||
pub checksum: Option<String>,
|
||||
pub origin: String,
|
||||
pub asset_kind: AssetKind,
|
||||
pub status: AssetStatus,
|
||||
pub latest_version_id: Option<AssetVersionId>,
|
||||
pub audit: AuditInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AssetVersion {
|
||||
pub asset_version_id: AssetVersionId,
|
||||
pub asset_id: AssetId,
|
||||
pub version_no: u32,
|
||||
pub storage_key: String,
|
||||
pub checksum: Option<String>,
|
||||
pub derived_from_version_id: Option<AssetVersionId>,
|
||||
pub change_reason: Option<ChangeReason>,
|
||||
pub created_at: Timestamp,
|
||||
pub created_by: Option<ActorRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ReferenceKind {
|
||||
PageToPage,
|
||||
BlockToBlock,
|
||||
BlockToPage,
|
||||
BlockToAssetFragment,
|
||||
TaskToObject,
|
||||
External,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Reference {
|
||||
pub reference_id: ReferenceId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub source_object_type: String,
|
||||
pub source_object_id: String,
|
||||
pub target_object_type: String,
|
||||
pub target_object_id: String,
|
||||
pub anchor: String,
|
||||
pub label: Option<String>,
|
||||
pub snippet: Option<String>,
|
||||
pub confidence: Option<String>,
|
||||
pub ref_kind: ReferenceKind,
|
||||
pub created_at: Timestamp,
|
||||
pub created_by: Option<ActorRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TaskType {
|
||||
Import,
|
||||
Ocr,
|
||||
Summary,
|
||||
Reindex,
|
||||
Sync,
|
||||
Transform,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TaskStatus {
|
||||
Pending,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TaskPriority {
|
||||
Low,
|
||||
Normal,
|
||||
High,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Task {
|
||||
pub task_id: TaskId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub task_type: TaskType,
|
||||
pub status: TaskStatus,
|
||||
pub priority: TaskPriority,
|
||||
pub assignee_actor_id: Option<String>,
|
||||
pub source_page_id: Option<PageId>,
|
||||
pub source_block_id: Option<BlockId>,
|
||||
pub input_payload: Option<String>,
|
||||
pub output_payload: Option<String>,
|
||||
pub due_at: Option<Timestamp>,
|
||||
pub completed_at: Option<Timestamp>,
|
||||
pub audit: AuditInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AgentSession {
|
||||
pub agent_session_id: AgentSessionId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub provider: String,
|
||||
pub model: String,
|
||||
pub initiator_actor_id: Option<String>,
|
||||
pub status: String,
|
||||
pub started_at: Timestamp,
|
||||
pub updated_at: Timestamp,
|
||||
pub ended_at: Option<Timestamp>,
|
||||
pub tool_policy: Option<String>,
|
||||
pub confirmation_policy: Option<String>,
|
||||
pub summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CommandStatus {
|
||||
Pending,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommandLog {
|
||||
pub command_log_id: CommandLogId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub command_name: String,
|
||||
pub actor: ActorRef,
|
||||
pub source: SourceKind,
|
||||
pub target_objects: Vec<RefLink>,
|
||||
pub payload_summary: String,
|
||||
pub refs: Vec<RefLink>,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub dry_run: bool,
|
||||
pub status: CommandStatus,
|
||||
pub created_at: Timestamp,
|
||||
pub finished_at: Option<Timestamp>,
|
||||
pub error_code: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventType {
|
||||
PageCreated,
|
||||
PageUpdated,
|
||||
BlockInserted,
|
||||
BlockMoved,
|
||||
BlockUpdated,
|
||||
BlockDeleted,
|
||||
AssetVersionAdded,
|
||||
ReferenceCreated,
|
||||
TaskUpdated,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DomainEvent {
|
||||
pub event_id: EventId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub command_log_id: CommandLogId,
|
||||
pub aggregate_type: String,
|
||||
pub aggregate_id: String,
|
||||
pub event_type: EventType,
|
||||
pub before_revision: Option<Revision>,
|
||||
pub after_revision: Option<Revision>,
|
||||
pub payload_json: String,
|
||||
pub created_at: Timestamp,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
macro_rules! define_id {
|
||||
($name:ident) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct $name(pub String);
|
||||
|
||||
impl $name {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_id!(WorkspaceId);
|
||||
define_id!(PageId);
|
||||
define_id!(BlockId);
|
||||
define_id!(AssetId);
|
||||
define_id!(AssetVersionId);
|
||||
define_id!(ReferenceId);
|
||||
define_id!(TaskId);
|
||||
define_id!(AgentSessionId);
|
||||
define_id!(CommandLogId);
|
||||
define_id!(EventId);
|
||||
@@ -0,0 +1,34 @@
|
||||
pub mod audit;
|
||||
pub mod entities;
|
||||
pub mod ids;
|
||||
pub mod time;
|
||||
pub mod types;
|
||||
|
||||
pub use audit::{ActorRef, AuditInfo, ChangeReason, RefLink, SourceKind};
|
||||
pub use entities::{
|
||||
AgentSession, Asset, AssetKind, AssetStatus, AssetVersion, Block, BlockType, CommandLog,
|
||||
CommandStatus, DomainEvent, EventType, Page, PageStatus, PageType, Reference, ReferenceKind,
|
||||
Task, TaskPriority, TaskStatus, TaskType, Workspace,
|
||||
};
|
||||
pub use ids::{
|
||||
AgentSessionId, AssetId, AssetVersionId, BlockId, CommandLogId, EventId, PageId, ReferenceId,
|
||||
TaskId, WorkspaceId,
|
||||
};
|
||||
pub use time::Timestamp;
|
||||
pub use types::{Locale, Revision};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn revision_initial_is_one() {
|
||||
assert_eq!(Revision::initial().0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_id_keeps_original_value() {
|
||||
let workspace_id = WorkspaceId::new("ws_demo");
|
||||
assert_eq!(workspace_id.as_str(), "ws_demo");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Timestamp(pub String);
|
||||
|
||||
impl Timestamp {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Revision(pub u64);
|
||||
|
||||
impl Revision {
|
||||
pub fn initial() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Locale(pub String);
|
||||
|
||||
impl Locale {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "core-protocol"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
core-domain = { path = "../core-domain" }
|
||||
@@ -0,0 +1,8 @@
|
||||
# core-protocol
|
||||
|
||||
阶段 1 协议模型 crate。
|
||||
|
||||
职责说明请先看:
|
||||
- ../../design/phase1-d-command-api-v0.md
|
||||
- ../../design/phase1-d-query-api-v0.md
|
||||
- ../../design/phase1-d-tool-api-constraints-v0.md
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::common::{ActorPayload, AffectedObject, SourcePayload, TargetRef};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommandEnvelope<T> {
|
||||
pub name: String,
|
||||
pub command_id: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub actor: ActorPayload,
|
||||
pub source: SourcePayload,
|
||||
pub target: Option<TargetRef>,
|
||||
pub payload: T,
|
||||
pub reason: Option<String>,
|
||||
pub refs: Vec<String>,
|
||||
pub dry_run: bool,
|
||||
pub validate_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommandResult {
|
||||
pub ok: bool,
|
||||
pub command_id: String,
|
||||
pub event_ids: Vec<String>,
|
||||
pub affected_objects: Vec<AffectedObject>,
|
||||
pub revision: Option<u64>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CreateWorkspace {
|
||||
pub name: String,
|
||||
pub slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CreatePage {
|
||||
pub title: String,
|
||||
pub parent_page_id: Option<String>,
|
||||
pub position: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdatePageTitle {
|
||||
pub page_id: String,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdatePageStats {
|
||||
pub page_id: String,
|
||||
pub word_count: i64,
|
||||
pub character_count: i64,
|
||||
pub block_count: i64,
|
||||
pub todo_total: i64,
|
||||
pub todo_done: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdatePageOptions {
|
||||
pub page_id: String,
|
||||
pub wide_layout: Option<bool>,
|
||||
pub small_text: Option<bool>,
|
||||
pub show_heading_numbers: Option<bool>,
|
||||
pub show_toc: Option<bool>,
|
||||
pub show_structure: Option<bool>,
|
||||
pub protect_editing: Option<bool>,
|
||||
pub show_word_count: Option<bool>,
|
||||
pub collapse_backlinks: Option<bool>,
|
||||
pub page_font: Option<String>,
|
||||
pub layout_density: Option<String>,
|
||||
pub hide_child_pages: Option<bool>,
|
||||
pub show_block_ref_count: Option<bool>,
|
||||
pub embed_default_block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InsertBlock {
|
||||
pub page_id: String,
|
||||
pub block_type: String,
|
||||
pub content: String,
|
||||
pub parent_block_id: Option<String>,
|
||||
pub prev_block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateBlock {
|
||||
pub block_id: String,
|
||||
pub patch_content: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MoveBlock {
|
||||
pub block_id: String,
|
||||
pub new_parent_block_id: Option<String>,
|
||||
pub new_page_id: Option<String>,
|
||||
pub prev_block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DeleteBlock {
|
||||
pub block_id: String,
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ActorPayload {
|
||||
pub actor_type: String,
|
||||
pub actor_id: String,
|
||||
pub session_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SourcePayload {
|
||||
pub channel: String,
|
||||
pub client: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TargetRef {
|
||||
pub workspace_id: Option<String>,
|
||||
pub page_id: Option<String>,
|
||||
pub block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RequestMeta {
|
||||
pub idempotency_key: Option<String>,
|
||||
pub validate_only: bool,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AffectedObject {
|
||||
pub object_type: String,
|
||||
pub object_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ResponseMeta {
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OkPayload<T> {
|
||||
pub data: T,
|
||||
pub meta: ResponseMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ErrorDetail {
|
||||
pub field: Option<String>,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ErrorPayload {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub details: Vec<ErrorDetail>,
|
||||
pub retryable: bool,
|
||||
pub meta: ResponseMeta,
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccessContext {
|
||||
pub tenant_id: Option<String>,
|
||||
pub workspace_id: String,
|
||||
pub actor_id: String,
|
||||
pub actor_type: String,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccessDecision {
|
||||
Allow,
|
||||
Deny(AccessDenyReason),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AccessDenyReason {
|
||||
MissingTenant,
|
||||
MissingWorkspace,
|
||||
PermissionDenied,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum JobStatus {
|
||||
Pending,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
RetryScheduled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct JobTicket {
|
||||
pub job_id: String,
|
||||
pub job_type: String,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub workspace_id: String,
|
||||
pub status: JobStatus,
|
||||
}
|
||||
|
||||
impl JobTicket {
|
||||
pub fn new(
|
||||
job_id: impl Into<String>,
|
||||
job_type: impl Into<String>,
|
||||
request_id: impl Into<String>,
|
||||
trace_id: impl Into<String>,
|
||||
workspace_id: impl Into<String>,
|
||||
status: JobStatus,
|
||||
) -> Self {
|
||||
Self {
|
||||
job_id: job_id.into(),
|
||||
job_type: job_type.into(),
|
||||
request_id: request_id.into(),
|
||||
trace_id: trace_id.into(),
|
||||
workspace_id: workspace_id.into(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decide_access(context: &AccessContext) -> AccessDecision {
|
||||
if context.tenant_id.is_none() {
|
||||
return AccessDecision::Deny(AccessDenyReason::MissingTenant);
|
||||
}
|
||||
if context.workspace_id.trim().is_empty() {
|
||||
return AccessDecision::Deny(AccessDenyReason::MissingWorkspace);
|
||||
}
|
||||
AccessDecision::Allow
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn access_requires_tenant() {
|
||||
let context = AccessContext {
|
||||
tenant_id: None,
|
||||
workspace_id: "ws-1".to_string(),
|
||||
actor_id: "user-1".to_string(),
|
||||
actor_type: "human".to_string(),
|
||||
source: "react-next".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
decide_access(&context),
|
||||
AccessDecision::Deny(AccessDenyReason::MissingTenant)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_allows_valid_context() {
|
||||
let context = AccessContext {
|
||||
tenant_id: Some("tenant-1".to_string()),
|
||||
workspace_id: "ws-1".to_string(),
|
||||
actor_id: "user-1".to_string(),
|
||||
actor_type: "human".to_string(),
|
||||
source: "react-next".to_string(),
|
||||
};
|
||||
assert_eq!(decide_access(&context), AccessDecision::Allow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn job_ticket_keeps_trace_fields() {
|
||||
let ticket = JobTicket::new(
|
||||
"job-1",
|
||||
"rebuild-index",
|
||||
"req-1",
|
||||
"trace-1",
|
||||
"ws-1",
|
||||
JobStatus::Pending,
|
||||
);
|
||||
assert_eq!(ticket.trace_id, "trace-1");
|
||||
assert_eq!(ticket.status, JobStatus::Pending);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
pub mod command;
|
||||
pub mod common;
|
||||
pub mod governance;
|
||||
pub mod query;
|
||||
pub mod tool;
|
||||
|
||||
pub use command::{
|
||||
CommandEnvelope, CreatePage, CreateWorkspace, DeleteBlock, InsertBlock, MoveBlock,
|
||||
UpdateBlock, UpdatePageStats, UpdatePageTitle,
|
||||
};
|
||||
pub use common::{
|
||||
ActorPayload, AffectedObject, ErrorDetail, ErrorPayload, OkPayload, RequestMeta, ResponseMeta,
|
||||
SourcePayload, TargetRef,
|
||||
};
|
||||
pub use query::{GetPage, ListPageBlocks, QueryEnvelope, SearchBlocks, SearchPages};
|
||||
pub use tool::{InvocationKind, ToolInvocation};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn command_envelope_keeps_validate_only_flag() {
|
||||
let envelope = CommandEnvelope::<CreateWorkspace> {
|
||||
name: "create_workspace".into(),
|
||||
command_id: "cmd_1".into(),
|
||||
idempotency_key: Some("idem_1".into()),
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "cli".into(),
|
||||
client: "mnote-cli".into(),
|
||||
},
|
||||
target: None,
|
||||
payload: CreateWorkspace {
|
||||
name: "demo".into(),
|
||||
slug: Some("demo".into()),
|
||||
},
|
||||
reason: Some("初始化工作区".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: true,
|
||||
};
|
||||
|
||||
assert!(envelope.validate_only);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct QueryEnvelope<T> {
|
||||
pub name: String,
|
||||
pub payload: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Pagination {
|
||||
pub limit: u32,
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GetPage {
|
||||
pub page_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ListPageBlocks {
|
||||
pub page_id: String,
|
||||
pub pagination: Pagination,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SearchPages {
|
||||
pub query: String,
|
||||
pub workspace_id: Option<String>,
|
||||
pub pagination: Pagination,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SearchBlocks {
|
||||
pub query: String,
|
||||
pub page_id: Option<String>,
|
||||
pub pagination: Pagination,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InvocationKind {
|
||||
Command,
|
||||
Query,
|
||||
Job,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolInvocation {
|
||||
pub tool: String,
|
||||
pub kind: InvocationKind,
|
||||
pub args_json: String,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "event-log"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
core-domain = { path = "../core-domain" }
|
||||
@@ -0,0 +1,8 @@
|
||||
# event-log
|
||||
|
||||
阶段 1 事件日志 crate。
|
||||
|
||||
职责说明请先看:
|
||||
- ../../design/phase1-e-command-log-v0.md
|
||||
- ../../design/phase1-e-domain-event-v0.md
|
||||
- ../../design/phase1-e-event-generation-rules-v0.md
|
||||
@@ -0,0 +1,29 @@
|
||||
use core_domain::Timestamp;
|
||||
use core_domain::{RefLink, WorkspaceId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CommandLogStatus {
|
||||
Pending,
|
||||
Succeeded,
|
||||
Failed,
|
||||
RolledBack,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CommandLogRecord {
|
||||
pub command_log_id: String,
|
||||
pub command_name: String,
|
||||
pub actor_type: String,
|
||||
pub actor_id: String,
|
||||
pub source: String,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub target_objects: Vec<RefLink>,
|
||||
pub payload_summary: String,
|
||||
pub refs_json: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub status: CommandLogStatus,
|
||||
pub created_at: Timestamp,
|
||||
pub finished_at: Option<Timestamp>,
|
||||
pub trace_id: String,
|
||||
pub request_id: String,
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use core_domain::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventStatus {
|
||||
Pending,
|
||||
Committed,
|
||||
Rejected,
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DomainEventRecord {
|
||||
pub event_id: String,
|
||||
pub workspace_id: String,
|
||||
pub aggregate_type: String,
|
||||
pub aggregate_id: String,
|
||||
pub event_type: String,
|
||||
pub event_version: u32,
|
||||
pub payload_json: String,
|
||||
pub command_log_id: String,
|
||||
pub actor_type: String,
|
||||
pub created_at: Timestamp,
|
||||
pub status: EventStatus,
|
||||
pub trace_id: String,
|
||||
pub request_id: String,
|
||||
pub command_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EventGenerationOutcome {
|
||||
pub command_log_id: String,
|
||||
pub emitted_event_ids: Vec<String>,
|
||||
pub generated_for_replay: bool,
|
||||
pub generated_for_audit: bool,
|
||||
pub generated_for_index_catchup: bool,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
pub mod command_log;
|
||||
pub mod domain_event;
|
||||
pub mod rules;
|
||||
|
||||
pub use command_log::{CommandLogRecord, CommandLogStatus};
|
||||
pub use domain_event::{DomainEventRecord, EventGenerationOutcome, EventStatus};
|
||||
pub use rules::{should_emit_domain_event, EventGenerationRule};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn failed_command_does_not_emit_success_event() {
|
||||
let should_emit = should_emit_domain_event(&EventGenerationRule {
|
||||
command_succeeded: false,
|
||||
changed_domain_state: false,
|
||||
rolled_back: false,
|
||||
});
|
||||
|
||||
assert!(!should_emit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EventGenerationRule {
|
||||
pub command_succeeded: bool,
|
||||
pub changed_domain_state: bool,
|
||||
pub rolled_back: bool,
|
||||
}
|
||||
|
||||
pub fn should_emit_domain_event(rule: &EventGenerationRule) -> bool {
|
||||
rule.command_succeeded && rule.changed_domain_state && !rule.rolled_back
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "index-fts"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
core-domain = { path = "../core-domain" }
|
||||
event-log = { path = "../event-log" }
|
||||
@@ -0,0 +1,6 @@
|
||||
# index-fts
|
||||
|
||||
阶段 1 骨架 crate。
|
||||
|
||||
职责说明请先看:
|
||||
- ../../design/phase1-b-crate-responsibilities-v0.md
|
||||
@@ -0,0 +1,355 @@
|
||||
use event_log::DomainEventRecord;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IndexedEntityKind {
|
||||
PageTitle,
|
||||
PageSummary,
|
||||
BlockContent,
|
||||
BlockPath,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IndexedDocument {
|
||||
pub workspace_id: String,
|
||||
pub entity_kind: IndexedEntityKind,
|
||||
pub entity_id: String,
|
||||
pub parent_id: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub content: String,
|
||||
pub updated_at: String,
|
||||
pub source_event_id: Option<String>,
|
||||
pub revision: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IndexCursor {
|
||||
pub workspace_id: String,
|
||||
pub last_processed_event_id: String,
|
||||
pub last_processed_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SearchHit {
|
||||
pub workspace_id: String,
|
||||
pub entity_kind: IndexedEntityKind,
|
||||
pub entity_id: String,
|
||||
pub snippet: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SearchResultSet {
|
||||
pub query: String,
|
||||
pub workspace_id: Option<String>,
|
||||
pub page_id: Option<String>,
|
||||
pub hits: Vec<SearchHit>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProjectedDocumentBatch {
|
||||
pub workspace_id: String,
|
||||
pub source_event_id: String,
|
||||
pub documents: Vec<IndexedDocument>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProjectionResult {
|
||||
pub cursor: IndexCursor,
|
||||
pub batches: Vec<ProjectedDocumentBatch>,
|
||||
}
|
||||
|
||||
pub trait DomainEventProjector {
|
||||
fn project(&self, event: &DomainEventRecord) -> Vec<IndexedDocument>;
|
||||
}
|
||||
|
||||
pub fn supported_index_objects() -> Vec<IndexedEntityKind> {
|
||||
vec![
|
||||
IndexedEntityKind::PageTitle,
|
||||
IndexedEntityKind::PageSummary,
|
||||
IndexedEntityKind::BlockContent,
|
||||
IndexedEntityKind::BlockPath,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn can_rebuild_from_events(cursor: &IndexCursor) -> bool {
|
||||
!cursor.workspace_id.trim().is_empty() && !cursor.last_processed_event_id.trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn advance_workspace_cursor(
|
||||
cursor: &IndexCursor,
|
||||
event_id: impl Into<String>,
|
||||
processed_at: impl Into<String>,
|
||||
) -> IndexCursor {
|
||||
IndexCursor {
|
||||
workspace_id: cursor.workspace_id.clone(),
|
||||
last_processed_event_id: event_id.into(),
|
||||
last_processed_at: processed_at.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_domain_event<P: DomainEventProjector>(
|
||||
projector: &P,
|
||||
event: &DomainEventRecord,
|
||||
cursor: &IndexCursor,
|
||||
) -> ProjectionResult {
|
||||
let documents = projector.project(event);
|
||||
ProjectionResult {
|
||||
cursor: advance_workspace_cursor(
|
||||
cursor,
|
||||
event.event_id.clone(),
|
||||
event.created_at.as_str().to_string(),
|
||||
),
|
||||
batches: if documents.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![ProjectedDocumentBatch {
|
||||
workspace_id: event.workspace_id.clone(),
|
||||
source_event_id: event.event_id.clone(),
|
||||
documents,
|
||||
}]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rebuild_from_events<P: DomainEventProjector>(
|
||||
projector: &P,
|
||||
events: &[DomainEventRecord],
|
||||
cursor: &IndexCursor,
|
||||
) -> ProjectionResult {
|
||||
let mut next_cursor = cursor.clone();
|
||||
let mut batches = Vec::new();
|
||||
for event in events {
|
||||
if event.workspace_id != cursor.workspace_id {
|
||||
continue;
|
||||
}
|
||||
let projected = projector.project(event);
|
||||
next_cursor = advance_workspace_cursor(
|
||||
&next_cursor,
|
||||
event.event_id.clone(),
|
||||
event.created_at.as_str().to_string(),
|
||||
);
|
||||
if !projected.is_empty() {
|
||||
batches.push(ProjectedDocumentBatch {
|
||||
workspace_id: event.workspace_id.clone(),
|
||||
source_event_id: event.event_id.clone(),
|
||||
documents: projected,
|
||||
});
|
||||
}
|
||||
}
|
||||
ProjectionResult {
|
||||
cursor: next_cursor,
|
||||
batches,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_pages(
|
||||
query: &str,
|
||||
workspace_id: Option<&str>,
|
||||
documents: &[IndexedDocument],
|
||||
limit: usize,
|
||||
) -> SearchResultSet {
|
||||
let query = query.trim().to_lowercase();
|
||||
let hits = documents
|
||||
.iter()
|
||||
.filter(|document| matches!(document.entity_kind, IndexedEntityKind::PageTitle | IndexedEntityKind::PageSummary))
|
||||
.filter(|document| workspace_id.map_or(true, |workspace| document.workspace_id == workspace))
|
||||
.filter(|document| document.title.as_deref().unwrap_or("").to_lowercase().contains(&query)
|
||||
|| document.content.to_lowercase().contains(&query))
|
||||
.take(limit)
|
||||
.map(|document| SearchHit {
|
||||
workspace_id: document.workspace_id.clone(),
|
||||
entity_kind: document.entity_kind.clone(),
|
||||
entity_id: document.entity_id.clone(),
|
||||
snippet: Some(document.content.chars().take(120).collect()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
SearchResultSet {
|
||||
query: query.into(),
|
||||
workspace_id: workspace_id.map(|value| value.into()),
|
||||
page_id: None,
|
||||
hits,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_blocks(
|
||||
query: &str,
|
||||
page_id: Option<&str>,
|
||||
documents: &[IndexedDocument],
|
||||
limit: usize,
|
||||
) -> SearchResultSet {
|
||||
let query = query.trim().to_lowercase();
|
||||
let hits = documents
|
||||
.iter()
|
||||
.filter(|document| matches!(document.entity_kind, IndexedEntityKind::BlockContent | IndexedEntityKind::BlockPath))
|
||||
.filter(|document| page_id.map_or(true, |page| document.parent_id.as_deref() == Some(page) || document.entity_id == page))
|
||||
.filter(|document| document.content.to_lowercase().contains(&query))
|
||||
.take(limit)
|
||||
.map(|document| SearchHit {
|
||||
workspace_id: document.workspace_id.clone(),
|
||||
entity_kind: document.entity_kind.clone(),
|
||||
entity_id: document.entity_id.clone(),
|
||||
snippet: Some(document.content.chars().take(120).collect()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
SearchResultSet {
|
||||
query: query.into(),
|
||||
workspace_id: None,
|
||||
page_id: page_id.map(|value| value.into()),
|
||||
hits,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MinimalWorkspaceProjector;
|
||||
|
||||
impl DomainEventProjector for MinimalWorkspaceProjector {
|
||||
fn project(&self, event: &DomainEventRecord) -> Vec<IndexedDocument> {
|
||||
let content = event.payload_json.trim();
|
||||
let (entity_kind, title, parent_id) = match event.aggregate_type.as_str() {
|
||||
"page" => (
|
||||
IndexedEntityKind::PageTitle,
|
||||
Some(event.event_type.clone()),
|
||||
None,
|
||||
),
|
||||
"block" => (
|
||||
IndexedEntityKind::BlockContent,
|
||||
None,
|
||||
Some(event.aggregate_id.clone()),
|
||||
),
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
|
||||
vec![IndexedDocument {
|
||||
workspace_id: event.workspace_id.clone(),
|
||||
entity_kind,
|
||||
entity_id: event.aggregate_id.clone(),
|
||||
parent_id,
|
||||
title,
|
||||
content: content.to_string(),
|
||||
updated_at: event.created_at.as_str().to_string(),
|
||||
source_event_id: Some(event.event_id.clone()),
|
||||
revision: Some(event.event_version as u64),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use core_domain::Timestamp;
|
||||
use event_log::EventStatus;
|
||||
|
||||
fn cursor() -> IndexCursor {
|
||||
IndexCursor {
|
||||
workspace_id: "ws_1".into(),
|
||||
last_processed_event_id: "evt_0".into(),
|
||||
last_processed_at: "2026-04-11T00:00:00Z".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event(
|
||||
workspace_id: &str,
|
||||
aggregate_type: &str,
|
||||
aggregate_id: &str,
|
||||
event_id: &str,
|
||||
event_type: &str,
|
||||
) -> DomainEventRecord {
|
||||
DomainEventRecord {
|
||||
event_id: event_id.into(),
|
||||
workspace_id: workspace_id.into(),
|
||||
aggregate_type: aggregate_type.into(),
|
||||
aggregate_id: aggregate_id.into(),
|
||||
event_type: event_type.into(),
|
||||
event_version: 1,
|
||||
payload_json: "{\"text\":\"hello\"}".into(),
|
||||
command_log_id: "cmd_1".into(),
|
||||
actor_type: "human".into(),
|
||||
created_at: Timestamp::new("2026-04-11T00:00:01Z"),
|
||||
status: EventStatus::Committed,
|
||||
trace_id: "trace_1".into(),
|
||||
request_id: "req_1".into(),
|
||||
command_id: "cmd_1".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_page_and_block_projection_objects() {
|
||||
assert_eq!(
|
||||
supported_index_objects(),
|
||||
vec![
|
||||
IndexedEntityKind::PageTitle,
|
||||
IndexedEntityKind::PageSummary,
|
||||
IndexedEntityKind::BlockContent,
|
||||
IndexedEntityKind::BlockPath
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_advances_incrementally() {
|
||||
let next = advance_workspace_cursor(&cursor(), "evt_2", "2026-04-11T00:00:02Z");
|
||||
assert_eq!(next.last_processed_event_id, "evt_2");
|
||||
assert_eq!(next.workspace_id, "ws_1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projector_builds_real_documents_for_page_and_block() {
|
||||
let projector = MinimalWorkspaceProjector;
|
||||
let page_docs =
|
||||
projector.project(&event("ws_1", "page", "page_1", "evt_1", "page.created"));
|
||||
let block_docs =
|
||||
projector.project(&event("ws_1", "block", "block_1", "evt_2", "block.created"));
|
||||
assert_eq!(page_docs.len(), 1);
|
||||
assert_eq!(block_docs.len(), 1);
|
||||
assert_eq!(page_docs[0].entity_kind, IndexedEntityKind::PageTitle);
|
||||
assert_eq!(block_docs[0].entity_kind, IndexedEntityKind::BlockContent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuild_skips_other_workspaces_and_keeps_cursor() {
|
||||
let projector = MinimalWorkspaceProjector;
|
||||
let events = vec![
|
||||
event("ws_2", "page", "page_x", "evt_x", "page.created"),
|
||||
event("ws_1", "block", "block_1", "evt_1", "block.created"),
|
||||
];
|
||||
let result = rebuild_from_events(&projector, &events, &cursor());
|
||||
assert_eq!(result.batches.len(), 1);
|
||||
assert_eq!(result.cursor.last_processed_event_id, "evt_1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_helpers_return_hits() {
|
||||
let docs = vec![
|
||||
IndexedDocument {
|
||||
workspace_id: "ws_1".into(),
|
||||
entity_kind: IndexedEntityKind::PageTitle,
|
||||
entity_id: "page_1".into(),
|
||||
parent_id: None,
|
||||
title: Some("Rust Notes".into()),
|
||||
content: "Rust notes for phase 2".into(),
|
||||
updated_at: "2026-04-11T00:00:01Z".into(),
|
||||
source_event_id: Some("evt_1".into()),
|
||||
revision: Some(1),
|
||||
},
|
||||
IndexedDocument {
|
||||
workspace_id: "ws_1".into(),
|
||||
entity_kind: IndexedEntityKind::BlockContent,
|
||||
entity_id: "block_1".into(),
|
||||
parent_id: Some("page_1".into()),
|
||||
title: None,
|
||||
content: "A block about rust search indexing".into(),
|
||||
updated_at: "2026-04-11T00:00:02Z".into(),
|
||||
source_event_id: Some("evt_2".into()),
|
||||
revision: Some(1),
|
||||
},
|
||||
];
|
||||
|
||||
let pages = search_pages("rust", Some("ws_1"), &docs, 10);
|
||||
assert_eq!(pages.hits.len(), 1);
|
||||
assert_eq!(pages.hits[0].entity_id, "page_1");
|
||||
|
||||
let blocks = search_blocks("search", Some("page_1"), &docs, 10);
|
||||
assert_eq!(blocks.hits.len(), 1);
|
||||
assert_eq!(blocks.hits[0].entity_id, "block_1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "storage-convex-bridge"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
core-domain = { path = "../core-domain" }
|
||||
core-protocol = { path = "../core-protocol" }
|
||||
event-log = { path = "../event-log" }
|
||||
@@ -0,0 +1,18 @@
|
||||
# storage-convex-bridge
|
||||
|
||||
阶段 1 的 Convex 桥接 crate。
|
||||
|
||||
当前职责:
|
||||
- 把 Rust 协议对象映射为 Convex 读写请求
|
||||
- 透传 request / actor / workspace / auth 上下文
|
||||
- 生成最小 CommandLog / DomainEvent 骨架
|
||||
|
||||
当前明确不做:
|
||||
- 不复制主事实层
|
||||
- 不维护第二数据库
|
||||
- 不隐藏 tenant / auth / request context
|
||||
- 不承载复杂业务裁决
|
||||
|
||||
相关设计文档:
|
||||
- `../../design/phase1-f-bridge-responsibilities-v0.md`
|
||||
- `../../design/phase1-f-write-read-flow-v0.md`
|
||||
@@ -0,0 +1,18 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BridgeContext {
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub actor_type: String,
|
||||
pub actor_id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub tenant_id: Option<String>,
|
||||
pub auth_token: Option<String>,
|
||||
pub source_channel: String,
|
||||
pub source_client: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub validate_only: bool,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
pub mod context;
|
||||
pub mod mapping;
|
||||
pub mod read_path;
|
||||
pub mod types;
|
||||
pub mod validation;
|
||||
pub mod write_path;
|
||||
|
||||
pub use context::BridgeContext;
|
||||
pub use mapping::{
|
||||
map_command_name_to_convex, map_query_name_to_convex, ConvexMutationRequest, ConvexQueryRequest,
|
||||
};
|
||||
pub use read_path::{build_query_request, QueryReadResult};
|
||||
pub use types::{BridgeError, BridgeErrorKind, BridgeResult};
|
||||
pub use validation::{validate_command_envelope, validate_query_envelope};
|
||||
pub use write_path::{
|
||||
build_command_log_record, build_domain_event_record, build_write_pipeline, build_write_request,
|
||||
WritePipelineResult,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use core_protocol::{
|
||||
command::{CreatePage, CreateWorkspace, UpdatePageOptions, UpdatePageStats, UpdatePageTitle},
|
||||
query::GetPage,
|
||||
ActorPayload, CommandEnvelope, QueryEnvelope, SourcePayload, TargetRef,
|
||||
};
|
||||
|
||||
fn demo_context() -> BridgeContext {
|
||||
BridgeContext {
|
||||
deployment_id: Some("dep_1".into()),
|
||||
project_id: Some("proj_1".into()),
|
||||
request_id: "req_1".into(),
|
||||
trace_id: "trace_1".into(),
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
workspace_id: Some("ws_1".into()),
|
||||
tenant_id: Some("tenant_1".into()),
|
||||
auth_token: Some("token_1".into()),
|
||||
source_channel: "cli".into(),
|
||||
source_client: "mnote-cli".into(),
|
||||
idempotency_key: Some("idem_ctx".into()),
|
||||
validate_only: false,
|
||||
dry_run: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_write_request_keeps_workspace_scope() {
|
||||
let command = CommandEnvelope {
|
||||
name: "create_page".into(),
|
||||
command_id: "cmd_1".into(),
|
||||
idempotency_key: Some("idem_cmd".into()),
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "cli".into(),
|
||||
client: "mnote-cli".into(),
|
||||
},
|
||||
target: Some(TargetRef {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: None,
|
||||
block_id: None,
|
||||
}),
|
||||
payload: CreatePage {
|
||||
title: "第一页".into(),
|
||||
parent_page_id: None,
|
||||
position: None,
|
||||
},
|
||||
reason: Some("初始化页面".into()),
|
||||
refs: vec!["spec:phase1-f".into()],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
|
||||
let request = build_write_request(&demo_context(), &command).expect("write request should build");
|
||||
assert_eq!(request.function_name, "pages:create");
|
||||
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
|
||||
assert_eq!(request.deployment_id.as_deref(), Some("dep_1"));
|
||||
assert_eq!(request.project_id.as_deref(), Some("proj_1"));
|
||||
assert_eq!(request.idempotency_key.as_deref(), Some("idem_cmd"));
|
||||
assert!(request.payload_json.contains("\"kind\":\"command\""));
|
||||
assert!(request.payload_json.contains("\"name\":\"create_page\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_query_request_includes_trace_and_workspace() {
|
||||
let query = QueryEnvelope {
|
||||
name: "get_page".into(),
|
||||
payload: GetPage {
|
||||
page_id: "page_1".into(),
|
||||
},
|
||||
};
|
||||
|
||||
let request = build_query_request(&demo_context(), &query).expect("query request should build");
|
||||
assert_eq!(request.function_name, "pages:get");
|
||||
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
|
||||
assert_eq!(request.request_id, "req_1");
|
||||
assert_eq!(request.trace_id, "trace_1");
|
||||
assert!(request.payload_json.contains("\"kind\":\"query\""));
|
||||
assert!(request.payload_json.contains("\"name\":\"get_page\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_write_pipeline_sets_trace_fields() {
|
||||
let command = CommandEnvelope {
|
||||
name: "create_workspace".into(),
|
||||
command_id: "cmd_2".into(),
|
||||
idempotency_key: None,
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "cli".into(),
|
||||
client: "mnote-cli".into(),
|
||||
},
|
||||
target: None,
|
||||
payload: CreateWorkspace {
|
||||
name: "demo".into(),
|
||||
slug: Some("demo".into()),
|
||||
},
|
||||
reason: Some("初始化工作区".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
|
||||
let pipeline = build_write_pipeline(&demo_context(), &command).expect("pipeline should build");
|
||||
assert_eq!(pipeline.command_log.command_name, "create_workspace");
|
||||
assert!(pipeline.command_log.payload_summary.contains("request_id=req_1"));
|
||||
assert_eq!(pipeline.command_log.trace_id, "trace_1");
|
||||
assert_eq!(pipeline.command_log.request_id, "req_1");
|
||||
assert!(pipeline.domain_event.payload_json.contains("\"trace_id\":\"trace_1\""));
|
||||
assert_eq!(pipeline.result.event_ids, vec!["evt_cmd_2".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_title_command_maps_to_documents_update_title() {
|
||||
let command = CommandEnvelope {
|
||||
name: "documents.title.update".into(),
|
||||
command_id: "cmd_title_1".into(),
|
||||
idempotency_key: Some("idem_title".into()),
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
},
|
||||
target: Some(TargetRef {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("page_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: UpdatePageTitle {
|
||||
page_id: "page_1".into(),
|
||||
title: "新标题".into(),
|
||||
},
|
||||
reason: None,
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
|
||||
let request = build_write_request(&demo_context(), &command).expect("write request should build");
|
||||
assert_eq!(request.function_name, "documents:updateTitle");
|
||||
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
|
||||
assert!(request.payload_json.contains("\"name\":\"documents.title.update\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_stats_command_maps_to_documents_update_stats() {
|
||||
let command = CommandEnvelope {
|
||||
name: "documents.stats.update".into(),
|
||||
command_id: "cmd_stats_1".into(),
|
||||
idempotency_key: Some("idem_stats".into()),
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
},
|
||||
target: Some(TargetRef {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("page_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: UpdatePageStats {
|
||||
page_id: "page_1".into(),
|
||||
word_count: 10,
|
||||
character_count: 20,
|
||||
block_count: 3,
|
||||
todo_total: 4,
|
||||
todo_done: 1,
|
||||
},
|
||||
reason: None,
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
|
||||
let request = build_write_request(&demo_context(), &command).expect("write request should build");
|
||||
assert_eq!(request.function_name, "documents:updateStats");
|
||||
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
|
||||
assert!(request.payload_json.contains("\"name\":\"documents.stats.update\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_options_command_maps_to_documents_update_options() {
|
||||
let command = CommandEnvelope {
|
||||
name: "documents.options.update".into(),
|
||||
command_id: "cmd_options_1".into(),
|
||||
idempotency_key: Some("idem_options".into()),
|
||||
actor: ActorPayload {
|
||||
actor_type: "human".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("session_1".into()),
|
||||
},
|
||||
source: SourcePayload {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
},
|
||||
target: Some(TargetRef {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("page_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: UpdatePageOptions {
|
||||
page_id: "page_1".into(),
|
||||
wide_layout: None,
|
||||
small_text: None,
|
||||
show_heading_numbers: None,
|
||||
show_toc: Some(true),
|
||||
show_structure: None,
|
||||
protect_editing: None,
|
||||
show_word_count: None,
|
||||
collapse_backlinks: None,
|
||||
page_font: None,
|
||||
layout_density: Some("compact".into()),
|
||||
hide_child_pages: None,
|
||||
show_block_ref_count: None,
|
||||
embed_default_block_id: None,
|
||||
},
|
||||
reason: None,
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
|
||||
let request = build_write_request(&demo_context(), &command).expect("write request should build");
|
||||
assert_eq!(request.function_name, "documents:updateOptions");
|
||||
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
|
||||
assert!(request.payload_json.contains("\"name\":\"documents.options.update\""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::context::BridgeContext;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConvexMutationRequest {
|
||||
pub function_name: String,
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub actor_id: String,
|
||||
pub payload_json: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConvexQueryRequest {
|
||||
pub function_name: String,
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub actor_id: String,
|
||||
pub payload_json: String,
|
||||
}
|
||||
|
||||
pub fn map_command_name_to_convex(command_name: &str) -> &'static str {
|
||||
match command_name {
|
||||
"create_workspace" => "workspaces:create",
|
||||
"create_page" => "pages:create",
|
||||
"documents.title.update" => "documents:updateTitle",
|
||||
"documents.stats.update" => "documents:updateStats",
|
||||
"documents.options.update" => "documents:updateOptions",
|
||||
"insert_block" => "blocks:insert",
|
||||
"update_block" => "blocks:update",
|
||||
"move_block" => "blocks:move",
|
||||
"delete_block" => "blocks:delete",
|
||||
_ => "commands:unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_query_name_to_convex(query_name: &str) -> &'static str {
|
||||
match query_name {
|
||||
"get_page" => "pages:get",
|
||||
"list_page_blocks" => "blocks:list_by_page",
|
||||
"search_pages" => "search:pages",
|
||||
"search_blocks" => "search:blocks",
|
||||
_ => "queries:unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn json_opt(value: &Option<String>) -> String {
|
||||
match value {
|
||||
Some(v) => format!("\"{}\"", v),
|
||||
None => "null".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload_json_for_command(context: &BridgeContext, command_name: &str) -> String {
|
||||
format!(
|
||||
"{{\"kind\":\"command\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"idempotency_key\":{},\"actor\":{{\"type\":\"{}\",\"id\":\"{}\",\"session_id\":{}}},\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
|
||||
command_name,
|
||||
context.request_id,
|
||||
context.trace_id,
|
||||
json_opt(&context.deployment_id),
|
||||
json_opt(&context.project_id),
|
||||
json_opt(&context.workspace_id),
|
||||
json_opt(&context.tenant_id),
|
||||
json_opt(&context.idempotency_key),
|
||||
context.actor_type,
|
||||
context.actor_id,
|
||||
json_opt(&context.session_id),
|
||||
context.source_channel,
|
||||
context.source_client,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn payload_json_for_query(context: &BridgeContext, query_name: &str) -> String {
|
||||
format!(
|
||||
"{{\"kind\":\"query\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"actor_id\":\"{}\",\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
|
||||
query_name,
|
||||
context.request_id,
|
||||
context.trace_id,
|
||||
json_opt(&context.deployment_id),
|
||||
json_opt(&context.project_id),
|
||||
json_opt(&context.workspace_id),
|
||||
json_opt(&context.tenant_id),
|
||||
context.actor_id,
|
||||
context.source_channel,
|
||||
context.source_client,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::mapping::{map_query_name_to_convex, payload_json_for_query, ConvexQueryRequest};
|
||||
use crate::types::BridgeResult;
|
||||
use crate::validation::validate_query_envelope;
|
||||
use core_protocol::QueryEnvelope;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct QueryReadResult {
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
}
|
||||
|
||||
pub fn build_query_request<T>(
|
||||
context: &BridgeContext,
|
||||
query: &QueryEnvelope<T>,
|
||||
) -> BridgeResult<ConvexQueryRequest> {
|
||||
validate_query_envelope(context, query)?;
|
||||
Ok(ConvexQueryRequest {
|
||||
function_name: map_query_name_to_convex(&query.name).to_string(),
|
||||
deployment_id: context.deployment_id.clone(),
|
||||
project_id: context.project_id.clone(),
|
||||
workspace_id: context.workspace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
trace_id: context.trace_id.clone(),
|
||||
actor_id: context.actor_id.clone(),
|
||||
payload_json: payload_json_for_query(context, &query.name),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BridgeErrorKind {
|
||||
Validation,
|
||||
Unauthorized,
|
||||
Conflict,
|
||||
NotFound,
|
||||
Transport,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BridgeError {
|
||||
pub kind: BridgeErrorKind,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl BridgeError {
|
||||
pub fn validation(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: BridgeErrorKind::Validation,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type BridgeResult<T> = Result<T, BridgeError>;
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::types::{BridgeError, BridgeResult};
|
||||
use core_protocol::{CommandEnvelope, QueryEnvelope};
|
||||
|
||||
pub fn validate_command_envelope<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<()> {
|
||||
if command.name.trim().is_empty() {
|
||||
return Err(BridgeError::validation("command name 不能为空"));
|
||||
}
|
||||
if command.command_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("command_id 不能为空"));
|
||||
}
|
||||
if context.request_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("request_id 不能为空"));
|
||||
}
|
||||
if context.trace_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("trace_id 不能为空"));
|
||||
}
|
||||
if context.actor_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("actor_id 不能为空"));
|
||||
}
|
||||
if context
|
||||
.workspace_id
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.trim()
|
||||
.is_empty()
|
||||
{
|
||||
return Err(BridgeError::validation("workspace_id 不能为空"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_query_envelope<T>(
|
||||
context: &BridgeContext,
|
||||
query: &QueryEnvelope<T>,
|
||||
) -> BridgeResult<()> {
|
||||
if query.name.trim().is_empty() {
|
||||
return Err(BridgeError::validation("query name 不能为空"));
|
||||
}
|
||||
if context.request_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("request_id 不能为空"));
|
||||
}
|
||||
if context.trace_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("trace_id 不能为空"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::mapping::{map_command_name_to_convex, payload_json_for_command, ConvexMutationRequest};
|
||||
use crate::types::BridgeResult;
|
||||
use crate::validation::validate_command_envelope;
|
||||
use core_domain::{RefLink, Timestamp, WorkspaceId};
|
||||
use core_protocol::{command::CommandResult, AffectedObject, CommandEnvelope};
|
||||
use event_log::{CommandLogRecord, CommandLogStatus, DomainEventRecord, EventStatus};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WritePipelineResult {
|
||||
pub mutation: ConvexMutationRequest,
|
||||
pub command_log: CommandLogRecord,
|
||||
pub domain_event: DomainEventRecord,
|
||||
pub result: CommandResult,
|
||||
}
|
||||
|
||||
pub fn build_write_request<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<ConvexMutationRequest> {
|
||||
validate_command_envelope(context, command)?;
|
||||
let workspace_id = command
|
||||
.target
|
||||
.as_ref()
|
||||
.and_then(|target| target.workspace_id.clone())
|
||||
.or_else(|| context.workspace_id.clone());
|
||||
Ok(ConvexMutationRequest {
|
||||
function_name: map_command_name_to_convex(&command.name).to_string(),
|
||||
deployment_id: context.deployment_id.clone(),
|
||||
project_id: context.project_id.clone(),
|
||||
workspace_id,
|
||||
request_id: context.request_id.clone(),
|
||||
trace_id: context.trace_id.clone(),
|
||||
idempotency_key: command
|
||||
.idempotency_key
|
||||
.clone()
|
||||
.or_else(|| context.idempotency_key.clone()),
|
||||
actor_id: context.actor_id.clone(),
|
||||
payload_json: payload_json_for_command(context, &command.name),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_command_log_record(
|
||||
command_id: &str,
|
||||
command_name: &str,
|
||||
context: &BridgeContext,
|
||||
workspace_id: &str,
|
||||
) -> CommandLogRecord {
|
||||
CommandLogRecord {
|
||||
command_log_id: format!("clog_{command_id}"),
|
||||
command_name: command_name.into(),
|
||||
actor_type: context.actor_type.clone(),
|
||||
actor_id: context.actor_id.clone(),
|
||||
source: context.source_channel.clone(),
|
||||
workspace_id: WorkspaceId::new(workspace_id),
|
||||
target_objects: vec![RefLink::External(format!("workspace:{workspace_id}"))],
|
||||
payload_summary: format!(
|
||||
"command={command_name};request_id={};trace_id={}",
|
||||
context.request_id, context.trace_id
|
||||
),
|
||||
refs_json: format!(
|
||||
"[\"request:{}\",\"trace:{}\"]",
|
||||
context.request_id, context.trace_id
|
||||
),
|
||||
idempotency_key: context.idempotency_key.clone(),
|
||||
status: CommandLogStatus::Pending,
|
||||
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
|
||||
finished_at: None,
|
||||
trace_id: context.trace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_domain_event_record(
|
||||
command_id: &str,
|
||||
command_name: &str,
|
||||
context: &BridgeContext,
|
||||
workspace_id: &str,
|
||||
) -> DomainEventRecord {
|
||||
DomainEventRecord {
|
||||
event_id: format!("evt_{command_id}"),
|
||||
workspace_id: workspace_id.into(),
|
||||
aggregate_type: "workspace".into(),
|
||||
aggregate_id: workspace_id.into(),
|
||||
event_type: format!("{command_name}_requested"),
|
||||
event_version: 1,
|
||||
payload_json: format!(
|
||||
"{{\"request_id\":\"{}\",\"trace_id\":\"{}\",\"command_id\":\"{}\",\"command_name\":\"{}\"}}",
|
||||
context.request_id, context.trace_id, command_id, command_name
|
||||
),
|
||||
command_log_id: format!("clog_{command_id}"),
|
||||
actor_type: context.actor_type.clone(),
|
||||
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
|
||||
status: EventStatus::Pending,
|
||||
trace_id: context.trace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
command_id: command_id.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_write_pipeline<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<WritePipelineResult> {
|
||||
let mutation = build_write_request(context, command)?;
|
||||
let workspace_id = mutation
|
||||
.workspace_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "workspace_unknown".into());
|
||||
let command_log =
|
||||
build_command_log_record(&command.command_id, &command.name, context, &workspace_id);
|
||||
let domain_event =
|
||||
build_domain_event_record(&command.command_id, &command.name, context, &workspace_id);
|
||||
let result = CommandResult {
|
||||
ok: true,
|
||||
command_id: command.command_id.clone(),
|
||||
event_ids: vec![domain_event.event_id.clone()],
|
||||
affected_objects: vec![AffectedObject {
|
||||
object_type: "workspace".into(),
|
||||
object_id: workspace_id.clone(),
|
||||
}],
|
||||
revision: Some(1),
|
||||
warnings: vec![],
|
||||
};
|
||||
Ok(WritePipelineResult {
|
||||
mutation,
|
||||
command_log,
|
||||
domain_event,
|
||||
result,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":4704921907237318175,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/lix/.rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: x86_64-unknown-linux-gnu\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""}},"successes":{}}
|
||||
@@ -0,0 +1,3 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
e053498f67f46e4d
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":17672942494452627365,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-40e6f74f7ef1a9aa/dep-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
31ec3f0d1a2e5472
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":1722584277633009122,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-bd799d0a25dcc494/dep-test-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
4e803ee3d8e7c709
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":8731458305071235362,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-cdff95eec220868e/dep-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
44b9888b46dbed5c
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":1722584277633009122,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-0fdc11ecf89ad5aa/dep-test-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
69002a3ff9918d3d
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":17672942494452627365,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-4592bf3d65d89282/dep-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
49c72387560f483c
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":8731458305071235362,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-f5d7351438cd4f82/dep-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
a1e984aa5f972227
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":2075902485319387119,"profile":17672942494452627365,"path":3721623360069338606,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/event-log-563618231cacaf7e/dep-lib-event_log","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
8f57032f1a7da02a
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":2075902485319387119,"profile":8731458305071235362,"path":3721623360069338606,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/event-log-d9b5c77d292421e8/dep-lib-event_log","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
bf54d8ba9752e161
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":1159665536100219803,"profile":17672942494452627365,"path":12914430766979976756,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552],[16479709356797637466,"event_log",false,2819982753825876385]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/index-fts-e444a40571811be1/dep-lib-index_fts","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+1
@@ -0,0 +1 @@
|
||||
d0e3930355d176f1
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":8731458305071235362,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4343738704907716425],[12060949818064432287,"core_domain",false,704786785418248270],[16479709356797637466,"event_log",false,3071592497278048143]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-45166d07753c36d7/dep-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+1
@@ -0,0 +1 @@
|
||||
b564adb2c82f1811
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":1722584277633009122,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4343738704907716425],[12060949818064432287,"core_domain",false,704786785418248270],[16479709356797637466,"event_log",false,3071592497278048143]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-5bf1d06211b95150/dep-test-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+1
@@ -0,0 +1 @@
|
||||
8dd9c30bdd3eb769
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":17672942494452627365,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4435361707722408041],[12060949818064432287,"core_domain",false,5579665713981379552],[16479709356797637466,"event_log",false,2819982753825876385]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-cd4527dde6c0c1c6/dep-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
@@ -0,0 +1,10 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-40e6f74f7ef1a9aa.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-40e6f74f7ef1a9aa.rmeta: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
crates/core-domain/src/lib.rs:
|
||||
crates/core-domain/src/audit.rs:
|
||||
crates/core-domain/src/entities.rs:
|
||||
crates/core-domain/src/ids.rs:
|
||||
crates/core-domain/src/time.rs:
|
||||
crates/core-domain/src/types.rs:
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-bd799d0a25dcc494.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-bd799d0a25dcc494: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
crates/core-domain/src/lib.rs:
|
||||
crates/core-domain/src/audit.rs:
|
||||
crates/core-domain/src/entities.rs:
|
||||
crates/core-domain/src/ids.rs:
|
||||
crates/core-domain/src/time.rs:
|
||||
crates/core-domain/src/types.rs:
|
||||
@@ -0,0 +1,12 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-cdff95eec220868e.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-cdff95eec220868e.rlib: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-cdff95eec220868e.rmeta: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
|
||||
|
||||
crates/core-domain/src/lib.rs:
|
||||
crates/core-domain/src/audit.rs:
|
||||
crates/core-domain/src/entities.rs:
|
||||
crates/core-domain/src/ids.rs:
|
||||
crates/core-domain/src/time.rs:
|
||||
crates/core-domain/src/types.rs:
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-0fdc11ecf89ad5aa.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-0fdc11ecf89ad5aa: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
crates/core-protocol/src/lib.rs:
|
||||
crates/core-protocol/src/command.rs:
|
||||
crates/core-protocol/src/common.rs:
|
||||
crates/core-protocol/src/governance.rs:
|
||||
crates/core-protocol/src/query.rs:
|
||||
crates/core-protocol/src/tool.rs:
|
||||
@@ -0,0 +1,10 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-4592bf3d65d89282.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-4592bf3d65d89282.rmeta: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
crates/core-protocol/src/lib.rs:
|
||||
crates/core-protocol/src/command.rs:
|
||||
crates/core-protocol/src/common.rs:
|
||||
crates/core-protocol/src/governance.rs:
|
||||
crates/core-protocol/src/query.rs:
|
||||
crates/core-protocol/src/tool.rs:
|
||||
@@ -0,0 +1,12 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-f5d7351438cd4f82.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-f5d7351438cd4f82.rlib: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-f5d7351438cd4f82.rmeta: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
|
||||
|
||||
crates/core-protocol/src/lib.rs:
|
||||
crates/core-protocol/src/command.rs:
|
||||
crates/core-protocol/src/common.rs:
|
||||
crates/core-protocol/src/governance.rs:
|
||||
crates/core-protocol/src/query.rs:
|
||||
crates/core-protocol/src/tool.rs:
|
||||
@@ -0,0 +1,8 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/event_log-563618231cacaf7e.d: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-563618231cacaf7e.rmeta: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
|
||||
|
||||
crates/event-log/src/lib.rs:
|
||||
crates/event-log/src/command_log.rs:
|
||||
crates/event-log/src/domain_event.rs:
|
||||
crates/event-log/src/rules.rs:
|
||||
@@ -0,0 +1,10 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/event_log-d9b5c77d292421e8.d: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-d9b5c77d292421e8.rlib: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-d9b5c77d292421e8.rmeta: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
|
||||
|
||||
crates/event-log/src/lib.rs:
|
||||
crates/event-log/src/command_log.rs:
|
||||
crates/event-log/src/domain_event.rs:
|
||||
crates/event-log/src/rules.rs:
|
||||
@@ -0,0 +1,5 @@
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/index_fts-e444a40571811be1.d: crates/index-fts/src/lib.rs
|
||||
|
||||
/mnt/Data1T/mnote/rust/target/debug/deps/libindex_fts-e444a40571811be1.rmeta: crates/index-fts/src/lib.rs
|
||||
|
||||
crates/index-fts/src/lib.rs:
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user