跳转至

ClientRuntime 与 LocalStore 契约

依赖:shared.md。SDK 独占连接、同步、游标、pending、REBUILD 与事件去重状态机;所有宿主只驱动这一份运行时。

pub trait LocalStore: Send + Sync {
    async fn persist_pending(
        &self,
        request: SendMessage,
    ) -> Result<(), ClientRuntimeError>;

    async fn apply_mailbox_page(
        &self,
        page: MailboxPage,
        cursor: MailboxCursor,
    ) -> Result<Vec<MailboxEntry>, ClientRuntimeError>;

    async fn resolve_pending(
        &self,
        committed: CommittedMessage,
        content: MessageContent,
    ) -> Result<(), ClientRuntimeError>;

    async fn pending(&self) -> Result<Vec<SendMessage>, ClientRuntimeError>;

    async fn apply_history(
        &self,
        page: HistoryPage,
    ) -> Result<Vec<MessageRecord>, ClientRuntimeError>;

    async fn cursor(&self) -> Result<Option<MailboxCursor>, ClientRuntimeError>;

    async fn reset_for_rebuild(&self) -> Result<(), ClientRuntimeError>;
}

pub trait ClientRuntime: Send + Sync {
    async fn connect(
        &self,
        request: ConnectRequest,
    ) -> Result<(), ClientRuntimeError>;

    async fn send_message(
        &self,
        request: SendMessage,
    ) -> Result<(), ClientRuntimeError>;

    async fn apply_mailbox_batch(
        &self,
        page: MailboxPage,
    ) -> Result<(), ClientRuntimeError>;

    async fn pull_history(
        &self,
        query: HistoryQuery,
    ) -> Result<HistoryPage, ClientRuntimeError>;

    async fn pull_members(
        &self,
        request: MemberPageRequest,
    ) -> Result<MemberPage, ClientRuntimeError>;

    async fn begin_rebuild(
        &self,
        trim_watermark: MailboxSeq,
    ) -> Result<RebuildPlan, ClientRuntimeError>;

    async fn finish_rebuild(
        &self,
        cursor: MailboxCursor,
    ) -> Result<(), ClientRuntimeError>;

    async fn next_event(&self) -> Result<ClientEvent, ClientRuntimeError>;
}

前置条件

  • 宿主创建 runtime 后必须显式调用一次 connect;未连接的网络命令返回 ConnectRequired,不得隐式使用默认账户连接。
  • 除测试显式选择 InMemoryForTest 外,LocalStore 必须是稳定的持久化位置。游标须一同保存 lane、epoch 和最后已应用序号。
  • apply_mailbox_batch 只接受完整事件组;begin_rebuild 只由 CursorExpired 路径触发。

后置条件

  • send_message 先将 pending 原子写入本地库,再允许网络发送;ACK 丢失后重启仍可对账。
  • 邮箱批次中的消息、去重记录、会话投影和游标必须在同一逻辑事务中落库,游标只能在批次完整应用后推进;实时 Push 不得推进游标。
  • 重连时 runtime 先同步邮箱,以发送者自己的 ClientMessageId 对账 pending;命中则原位确认,未命中才以原 ID 重发。
  • resolve_pending 与历史写入保留 MessageType / custom_type;MessagesAdded 只包含实际新增的条目。
  • begin_rebuild 保留 pending 和已有本地消息,先取权威会话列表、逐会话补历史,再由 finish_rebuild 持久新游标并恢复 pending 对账。
  • ACK 与 PONG 超期使状态离开 Online 并进入重连;不会无限维持在线状态。

错误

  • CursorExpired 必须触发 REBUILD,不能以空邮箱页或推进游标降级处理。
  • 本地迁移失败或版本过新时,LocalStoreFailure 后的重置必须同时丢弃游标与可重建数据、保留 pending,并发出 LocalStoreReset 事件。