Files
erp/plans/flickering-tinkering-pebble-agent-a409e20167941384b.md
iven 841766b168
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

27 KiB
Raw Blame History

CRM WASM 插件架构审查报告

审查人:后端架构师 日期2026-04-15 审查对象CRM 客户管理 WASM 插件设计方案


0. 审查前提:当前系统实际状态

经过对代码库的详细审查,我确认以下事实作为审查基础:

  • WIT 接口 (crates/erp-plugin/wit/plugin.wit)9 个 Host API 函数db-insert/db-query/db-update/db-delete/event-publish/config-get/log-write/current-user/check-permission
  • 动态表结构 (crates/erp-plugin/src/dynamic_table.rs):每张表只有 id, tenant_id, data(JSONB), created_at, updated_at, created_by, updated_by, deleted_at, version 这 9 列。所有业务字段存储在 data JSONB 列中。
  • 索引策略:只为 requiredunique 字段创建 GIN 索引(data->>'field_name'),没有复合索引能力。
  • 查询能力 (data_service.rs)list() 方法只支持按 tenant_id 过滤 + 分页,不支持任何业务字段过滤PluginDataListParams 只有 page, page_size, search 三个参数。
  • 前端 CRUD (PluginCRUDPage.tsx):纯粹的表单驱动,无过滤、无关联实体展示、无自定义操作。
  • 设计规格文档 (docs/superpowers/specs/2026-04-13-wasm-plugin-system-design.md) 附录 D 中规划了 db-aggregate 接口,但实际 WIT 文件中并未实现
  • 设计规格文档规划了 render-page / handle-action 插件导出函数,但实际 WIT 中也没有

结论:当前系统是一个 MVP 级别的插件原型,距离支撑 CRM 这种多实体、多关系的业务插件还有显著差距。


1. JSONB 动态表能否支撑 CRM 数据模型?

1.1 结论:基本可行,但需要大幅增强查询能力

JSONB 存储本身不是问题。PostgreSQL 的 JSONB 在索引支持下,单字段等值查询和范围查询性能完全可以接受(毫秒级)。关键问题在于当前系统的查询能力几乎为零

1.2 关系查询的实现路径

对于 CRM 中"A 的所有子客户"这类关系查询:

方案 A利用 JSONB 索引的等值过滤(推荐)

parent_id 存储在 data JSONB 中。当前系统已有为 required 字段创建索引的逻辑:

-- 动态表已有的索引模式
CREATE INDEX idx_xxx_parent_id ON plugin_crm_customer (data->>'parent_id') WHERE deleted_at IS NULL;

查询"A 的直接子节点"只需:

SELECT * FROM plugin_crm_customer
WHERE tenant_id = $1 AND data->>'parent_id' = $2 AND deleted_at IS NULL;

这在有索引的情况下性能完全没问题10 万条记录也能在 5ms 内返回。

方案 B递归查询查询所有子孙节点需要 db-query 增强

当前的 db_query Host API 是预填充模式——Host 在执行 WASM 前就把查询结果塞进 query_results HashMap。WASM 内部只是读缓存。这意味着插件无法控制 SQL 的构建过程

递归查询需要 PostgreSQL 的 WITH RECURSIVE CTE

WITH RECURSIVE descendants AS (
    SELECT id, data, 0 as depth FROM plugin_crm_customer
    WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
    UNION ALL
    SELECT c.id, c.data, d.depth + 1 FROM plugin_crm_customer c
    JOIN descendants d ON c.data->>'parent_id' = d.id::text
    WHERE c.tenant_id = $2 AND c.deleted_at IS NULL AND d.depth < 10
)
SELECT * FROM descendants ORDER BY depth;

当前系统无法执行这种查询。 需要 Host API 层面支持。

1.3 具体建议

  1. 为外键字段显式声明索引:在 PluginField 中新增 indexed: bool 属性,或者引入 references 属性自动创建索引。CRM 中 contact.customer_idcommunication.customer_idcustomer_tag.customer_idcustomer_relationship.from_customer_idcustomer_relationship.to_customer_id 这些外键字段必须有索引。

  2. 对 unique 约束的处理需要修正:当前 customer.code 声明为 unique但索引只过滤了 deleted_at IS NULL。如果软删除后重建同名 code唯一索引会冲突。需要在索引中加入排除已删除记录的逻辑或使用 partial unique index

    CREATE UNIQUE INDEX idx_xxx_code_uniq ON plugin_crm_customer (data->>'code') WHERE deleted_at IS NULL;
    

    当前代码已经使用了 WHERE deleted_at IS NULL,这一点做得正确。但要确认 sanitize_identifier 生成的索引名不会因为表名过长而截断。


2. Host API 限制:跨实体查询

2.1 问题诊断

这是 CRM 插件面临的最严重的架构缺陷。CRM 的核心场景全部涉及跨实体:

  • 客户列表 + 联系人数量:需要 JOIN 或子查询
  • 客户详情页展示联系人列表:需要按 customer_id 过滤 contact
  • 沟通记录按客户筛选:需要按 customer_id 过滤 communication
  • 标签筛选客户:需要按 tag_name 在 customer_tag 中查,再反查 customer

当前 db_query 的实现(host.rs 第 99-109 行)只是从预填充的 HashMap 中取缓存:

fn db_query(&mut self, entity: String, _filter: Vec<u8>, _pagination: Vec<u8>) -> Result<Vec<u8>, String> {
    self.query_results.get(&entity).cloned()
        .ok_or_else(|| format!("实体 '{}' 的查询结果未预填充", entity))
}

_filter_pagination 参数被完全忽略了。

2.2 解决方案:引入结构化查询 API

不是加 JOIN那会让 Host API 变成 SQL 构建器),而是引入两级查询增强

第一级(必须实现):单实体过滤查询

在 Host API 中新增 db-query-filtered,或在现有 db_query 中实际处理 filter 参数:

/// 结构化过滤查询
db-query: func(entity: string, filter: list<u8>, pagination: list<u8>) -> result<list<u8>, string>;

filter 的 JSON 结构:

{
  "conditions": [
    {"field": "customer_id", "op": "eq", "value": "uuid-here"},
    {"field": "status", "op": "in", "value": ["active", "vip"]},
    {"field": "occurred_at", "op": "gte", "value": "2026-01-01"}
  ],
  "order_by": [{"field": "created_at", "dir": "desc"}],
  "search": "关键词"
}

Host 层将其安全转换为参数化 SQL

SELECT id, data, created_at, updated_at, version
FROM plugin_crm_contact
WHERE tenant_id = $1 AND deleted_at IS NULL
  AND data->>'customer_id' = $2
ORDER BY created_at DESC
LIMIT $3 OFFSET $4

这样 CRM 插件就能实现"查某个客户的所有联系人"、"查某个客户的所有沟通记录"。

第二级(必须实现):聚合查询

设计规格中规划了 db-aggregate 但未实现。CRM 需要它来显示统计信息:

db-aggregate: func(entity: string, query: list<u8>) -> result<list<u8>, string>;

query 结构:

{
  "group_by": ["data->>'status'"],
  "aggregates": [
    {"alias": "count", "func": "count", "field": "id"},
    {"alias": "total_value", "func": "sum", "field": "data->>'amount'"}
  ],
  "filter": {"field": "data->>'level'", "op": "eq", "value": "vip"}
}

第三级(推荐实现):跨实体关联查询

不要实现通用 JOIN太复杂且安全风险高而是实现一种受限的"关联加载"模式:

/// 按外键值批量查询(一次加载多个关联记录)
db-query-batch: func(entity: string, field: string, ids: list<string>) -> result<list<u8>, string>;

这解决了 CRM 中最常见的 N+1 查询问题。例如加载 20 个客户后,一次性查出所有关联的联系人:

// 请求:查 contact 表中 customer_id 在 [id1, id2, ...] 中的记录
{
  "entity": "contact",
  "field": "customer_id",
  "ids": ["uuid-1", "uuid-2", "..."]
}

Host 生成:

SELECT id, data, created_at, updated_at, version
FROM plugin_crm_contact
WHERE tenant_id = $1 AND deleted_at IS NULL
  AND data->>'customer_id' = ANY($2::text[])

2.3 关于"多次 API 调用"的问题

在 WASM 插件模型下,每次 API 调用都是在同一个 Store 内完成的,不涉及网络开销。多次 db_query 调用的成本是:

  • WASM 到 Host 的函数调用(纳秒级)
  • Host 从预填充缓存中读取(纳秒级)

所以即使需要多次调用,性能也不是问题。真正的问题是当前预填充机制不支持按业务字段过滤。只要实现了第一级的结构化过滤CRM 插件的跨实体查询就可以通过以下模式实现:

// 插件内伪代码
let contacts = db_query("contact", {"field": "customer_id", "op": "eq", "value": customer_id}, pagination);
let communications = db_query("communication", {"field": "customer_id", "op": "eq", "value": customer_id}, pagination);

前端也可以直接通过 REST API 调用:

GET /api/v1/plugins/crm/contact?filter={"field":"customer_id","op":"eq","value":"uuid"}&page=1&page_size=20

3. 关系图谱的可行性

3.1 结论:可行但有数据量上限

CRM 关系图谱的核心查询是给定一个客户找出它的所有直接关系parent_child/partner/supplier 等)。

查询模式

-- 查询 A 的所有关系
SELECT * FROM plugin_crm_customer_relationship
WHERE tenant_id = $1 AND deleted_at IS NULL
  AND (data->>'from_customer_id' = $2 OR data->>'to_customer_id' = $2);

这个查询在索引支持下完全可行。需要为 from_customer_idto_customer_id 都创建索引。

前端加载策略

"加载全量关系+客户来渲染图谱"这个方案在以下条件下可行:

  • 单租户客户量 < 5000
  • 关系记录 < 10000

超出这个量级,需要改为"按中心节点展开"的模式:

  1. 先加载中心客户的直接关系1 跳)
  2. 用户点击某个关联客户时加载该客户的直接关系2 跳)
  3. 前端用力导向图逐步渲染

3.2 建议

  1. 不要设计成全量加载。manifest 页面类型 graph 应支持配置 depth 参数(默认 1 跳,最大 3 跳),以及 center_entity_id 参数。

  2. 后端提供"关系网络"专用查询。不是新增 Host API而是在 data_handler 层增加一个通用的关系查询端点:

    GET /api/v1/plugins/{plugin_id}/{entity}/graph?center_id=uuid&depth=2&relationship_types=parent_child,partner
    

    这可以由基座的前端通用组件调用,不需要插件 WASM 参与。

  3. 前端 graph 组件选型:推荐 Ant Design 的 @ant-design/graph 或 G6。图谱渲染是纯前端能力不需要 WASM 介入。


4. parent_id 层级(递归查询)

4.1 问题分析

"查询某客户的所有子孙节点"在当前架构下确实是个难题。三种场景需要递归:

  1. 组织架构式展示:树形组件显示客户层级
  2. 汇总统计:某集团下所有子公司的总交易额
  3. 权限继承:父客户的负责人可以看到子客户

4.2 解决方案

方案 AHost 端提供递归查询 API推荐

新增 Host API

/// 递归查询树形结构的所有子孙节点
db-query-tree: func(entity: string, parent-field: string, root-id: string, max-depth: s32) -> result<list<u8>, string>;

Host 层使用 WITH RECURSIVE 实现:

WITH RECURSIVE tree AS (
    -- 锚点:根节点
    SELECT id, data, 0 as depth
    FROM plugin_crm_customer
    WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
    UNION ALL
    -- 递归:子节点
    SELECT c.id, c.data, t.depth + 1
    FROM plugin_crm_customer c
    JOIN tree t ON c.data->>'parent_id' = t.id::text
    WHERE c.tenant_id = $2 AND c.deleted_at IS NULL AND t.depth < $3
)
SELECT id, data, depth FROM tree ORDER BY depth;

方案 B物化路径denormalization

在 customer 的 data 中存储物化路径 path: "root_id/parent_id/self_id",然后可以用前缀匹配:

SELECT * FROM plugin_crm_customer
WHERE data->>'path' LIKE 'root_id/%' AND tenant_id = $1 AND deleted_at IS NULL;

这需要 GIN 索引支持 LIKE 前缀查询(pg_trgm 扩展或 text_pattern_ops)。

推荐方案 A。物化路径有数据一致性问题(移动节点时需要更新所有子节点的 path而递归 CTE 是 PostgreSQL 原生支持的,一次查询搞定。max_depth 参数防止无限递归。

4.3 对 tree 页面类型的建议

manifest 中 type: tree 的页面需要额外的配置:

[[ui.pages]]
route = "/crm/customer-tree"
entity = "customer"
type = "tree"
tree_config = { parent_field = "parent_id", label_field = "name", icon_field = "customer_type", max_depth = 5 }

基座前端根据 tree_config 渲染 Ant Design Tree 组件,使用 db-query-tree API 加载数据。


5. 缺失的关键能力

5.1 必须新增的 Host API

按优先级排序:

优先级 API 原因
P0 db-query-filtered(结构化过滤) 没有它 CRM 插件完全无法工作。当前 db_query 的 filter 参数被忽略。
P0 db-aggregate(聚合查询) CRM 仪表盘需要"按状态统计客户数"、"按级别统计客户数"等。设计规格中已规划但未实现。
P0 db-query-by-id(按 ID 查单条) 当前 db_query 不支持按 ID 查询。虽然 REST API 有 GET by ID但 WASM 内部没有。
P1 db-query-batch(批量外键查询) 解决 N+1 问题,关联实体加载。
P1 db-query-tree(递归树查询) 支持客户层级树、组织架构等树形结构。
P1 db-batch-insert(批量插入) 导入客户数据、批量创建联系人。
P2 db-exists(存在性检查) 检查 code 是否已存在,比 count 更高效。
P2 db-count(计数) 单独的计数查询,不返回数据。

5.2 必须增强的基座能力

能力 现状 需要
REST API 过滤 PluginDataListParams 只有 page/page_size/search 需要支持 filter 查询参数,支持 JSONB 字段的等值/范围/IN 查询
REST API 排序 固定 ORDER BY created_at DESC 需要支持 order_by 参数
REST API 关联加载 需要支持 ?include=contact,communication 参数,一次返回客户+联系人+沟通记录
Schema 唯一约束校验 只创建了索引,没有插入前校验 需要在 PluginDataService::create 中检查 unique 字段
实体间引用完整性 删除客户时应提示有关联的联系人/沟通记录。至少提供 GET /plugins/{id}/{entity}/{record_id}/references 端点

5.3 WASM 插件端需要的能力

设计规格中规划了 render-pagehandle-action,但 WIT 中未实现。对于 CRM 插件,以下场景需要这两个函数:

  • 客户 360 度视图:在同一个页面展示客户基本信息 + 联系人列表 + 最近沟通记录 + 标签 + 关系图谱。这不是一个简单的 CRUD 页面,需要 WASM 返回复合 UI 指令。
  • 自定义操作"转为正式客户"、"合并客户"、"批量分配"等操作需要 WASM 处理业务逻辑。

建议在 WIT 中新增:

interface plugin-api {
    init: func() -> result<_, string>;
    on-tenant-created: func(tenant-id: string) -> result<_, string>;
    handle-event: func(event-type: string, payload: list<u8>) -> result<_, string>;
    // 新增
    render-page: func(page-path: string, params: list<u8>) -> result<list<u8>, string>;
    handle-action: func(page-path: string, action: string, data: list<u8>) -> result<list<u8>, string>;
}

6. Manifest ui.pages 设计审查

6.1 当前类型覆盖度评估

类型 CRM 场景 是否覆盖 说明
crud 客户列表、联系人列表、沟通记录列表 覆盖 基本够用
tree 客户层级树 需增强 需要增加 tree_config 配置
graph 关系图谱 需增强 需要增加 graph_configrelationship_entity, center_id, depth
timeline 沟通记录时间线 新增 Ant Design Timeline 组件天然支持
tabs 客户 360 度视图 需增强 需要支持嵌套子页面
dashboard CRM 首页统计 缺失 需要 stat_cards + charts 配置
kanban 客户跟进阶段看板 缺失 按客户等级/跟进状态分列
detail 客户详情页 缺失 不是 CRUD是复合视图

6.2 建议的页面类型扩展

interface PluginPage {
  route: string;
  entity: string;
  display_name: string;
  icon?: string;
  menu_group?: string;

  // 页面类型扩展
  type: "crud" | "tree" | "graph" | "timeline" | "tabs" | "dashboard" | "detail" | "custom";

  // 类型专属配置
  crud_config?: CrudConfig;     // 列/过滤/排序/表单/操作
  tree_config?: TreeConfig;     // 父节点字段/标签字段/最大深度
  graph_config?: GraphConfig;   // 关系实体/关系类型/默认深度
  dashboard_config?: DashboardConfig; // 统计卡片/图表
  detail_config?: DetailConfig; // 关联实体/布局
  tabs_config?: TabsConfig;     // 子标签页列表
}

6.3 对 CRM 最关键的缺失detail 页面类型

CRM 最核心的交互不是列表 CRUD而是客户 360 度详情页

┌────────────────────────────────────────────────┐
│ 客户名称:某某集团              [编辑] [更多 ▾] │
│ 类型:企业 | 行业:制造业 | 级别VIP           │
├────────────────────────────────────────────────┤
│ [基本信息] [联系人] [沟通记录] [关系图谱] [标签] │
├────────────────────────────────────────────────┤
│ 联系人列表(联系人的 CRUD自动过滤当前客户   │
│ + 新增联系人                                   │
│ ┌─────┬──────┬──────┬───────┬────┐            │
│ │姓名 │职位   │电话   │邮箱    │操作│            │
│ └─────┴──────┴──────┴───────┴────┘            │
└────────────────────────────────────────────────┘

这种页面需要:

  1. 顶部展示主实体详情
  2. 底部 tabs 展示关联实体列表
  3. 关联实体自动按主实体 ID 过滤

建议 detail 类型配置:

[[ui.pages]]
route = "/crm/customer/:id"
entity = "customer"
type = "detail"
display_name = "客户详情"

[ui.pages.detail_config.header]
title_field = "name"
subtitles = [
  { field = "customer_type", label = "类型" },
  { field = "industry", label = "行业" },
  { field = "level", label = "级别", tag_colors = {vip = "gold", svip = "red"} }
]
actions = ["edit", "delete", { label = "转为正式客户", action = "activate", permission = "crm.customer.update" }]

[[ui.pages.detail_config.tabs]]
label = "联系人"
entity = "contact"
filter_field = "customer_id"
type = "crud"

[[ui.pages.detail_config.tabs]]
label = "沟通记录"
entity = "communication"
filter_field = "customer_id"
type = "timeline"

[[ui.pages.detail_config.tabs]]
label = "关系图谱"
entity = "customer_relationship"
type = "graph"
graph_config = { center_from = "from_customer_id", center_to = "to_customer_id" }

7. 权限模型审查

7.1 当前方案的 8 个权限码

根据 CRM 插件常见需求,推测 8 个权限码为:

  • crm.customer.list / crm.customer.create / crm.customer.update / crm.customer.delete
  • crm.contact.list / crm.contact.create / crm.contact.update / crm.contact.delete

7.2 不足之处

问题 1缺少沟通记录和关系图谱的权限

沟通记录communication和关系图谱customer_relationship是独立实体应该有独立的权限码。

问题 2缺少数据范围权限data scope

CRM 中最常见的权限需求不是"能不能看联系人",而是"能看到哪些客户的联系人"。典型的数据范围:

  • 本人:只看自己创建的客户
  • 本部门:看本部门所有人创建的客户
  • 本部门及下级:看本部门及子部门创建的客户
  • 全部:看所有客户

当前的 check_permission 只支持布尔型权限检查(有没有这个权限),不支持数据范围过滤。

问题 3级联权限问题

"联系人/沟通记录的访问是否应该依赖客户级别的权限?"——是的,应该依赖。如果用户看不到某个客户,就不应该看到该客户下的联系人和沟通记录。

7.3 建议的权限模型

权限码16 个)

crm.customer.list          # 查看客户列表
crm.customer.create        # 创建客户
crm.customer.update        # 编辑客户
crm.customer.delete        # 删除客户
crm.customer.export        # 导出客户数据

crm.contact.list           # 查看联系人
crm.contact.create         # 创建联系人
crm.contact.update         # 编辑联系人
crm.contact.delete         # 删除联系人

crm.communication.list     # 查看沟通记录
crm.communication.create   # 创建沟通记录
crm.communication.update   # 编辑沟通记录
crm.communication.delete   # 删除沟通记录

crm.relationship.view      # 查看关系图谱
crm.tag.manage             # 管理标签分类

crm.customer.all           # 数据范围:查看所有客户(默认只看自己的)

数据范围过滤的实现

这不是插件自身能解决的,需要基座支持。建议在 current_user API 返回的数据中包含数据范围信息,或者在 Host API 层增加数据范围过滤:

/// 扩展 current_user 返回值
current-user: func() -> result<list<u8>, string>;
// 返回结构增加 data_scope 字段
// { "id": "...", "tenant_id": "...", "data_scope": "all" | "department" | "department_tree" | "self" }

然后在 db_query 的过滤逻辑中自动注入 created_by 过滤。

关于级联权限:建议在 REST API 层处理。当查询联系人时,如果用户没有 crm.customer.all,则联系人查询自动 JOIN customer 表检查 created_by

SELECT c.* FROM plugin_crm_contact c
JOIN plugin_crm_customer cu ON c.data->>'customer_id' = cu.id::text
WHERE c.tenant_id = $1 AND c.deleted_at IS NULL
  AND (cu.data->>'created_by' = $2 OR cu.data->>'level' IN ('vip', 'svip'))
  -- VIP 客户所有人可见

这种过滤逻辑复杂且与业务强相关,建议在 WASM 插件的 handle_action 中实现,或者作为基座的通用数据范围机制。


8. 综合评估与实施建议

8.1 可行性评估

维度 评分 说明
JSONB 数据存储 8/10 PostgreSQL JSONB 足够支撑,索引机制需增强
单实体 CRUD 6/10 基本可用,但查询/排序/过滤能力严重不足
跨实体关联 3/10 当前完全不支持,是最需要补强的部分
树形层级 3/10 需要递归查询支持
关系图谱 5/10 存储可行,查询/渲染需要增强
权限模型 5/10 权限码够用,数据范围权限缺失
UI 配置驱动 4/10 缺少 detail/dashboard/kanban 类型

8.2 实施路径建议

Phase 1基座查询能力增强前置条件约 3-5 天)

  1. 实现 db_query 的 filter 参数解析和 SQL 构建
  2. 实现 db-aggregate Host API
  3. REST API 支持 filter / order_by 参数
  4. 为外键字段自动创建索引
  5. 实现 unique 约束的插入前校验

Phase 2CRM 核心功能(约 5-7 天)

  1. 创建 CRM 插件 crate5 个实体)
  2. 实现客户 + 联系人 + 沟通记录的 CRUD
  3. 实现 type: detail 前端通用组件
  4. 实现客户详情页tabs 嵌套关联实体)

Phase 3高级功能约 3-5 天)

  1. 实现 db-query-tree 支持客户层级
  2. 实现关系图谱页面
  3. 实现数据范围权限过滤
  4. 实现 render-page / handle-action 支持自定义操作

8.3 风险提示

  1. 最大的风险不是技术,是 Host API 的演进方向。每增加一个 Host API 函数,就意味着所有已安装的插件都依赖这个接口。建议在 CRM 插件开发前先冻结 Host API v2包含上述增强然后所有插件基于 v2 开发。

  2. JSONB 动态表的性能天花板。单表数据量超过 100 万条时JSONB 的查询性能会显著下降(相比原生列)。如果 CRM 模块被高频使用长期应考虑将高频实体customer升级为原生列存储。

  3. 前端通用组件的复杂度。detail + graph + tree + timeline 这四种页面类型,每种都需要基座提供通用实现。这不是 CRM 独有的需求,而是所有行业插件的共同需求。建议将这些组件沉淀为基座的 PluginUI 组件库,而不是 CRM 插件的一部分。


附录Host API v2 建议的 WIT 定义

package erp:plugin;

interface host-api {
    // === 基础 CRUD ===
    db-insert: func(entity: string, data: list<u8>) -> result<list<u8>, string>;
    db-get-by-id: func(entity: string, id: string) -> result<list<u8>, string>;
    db-query: func(entity: string, filter: list<u8>, pagination: list<u8>) -> result<list<u8>, string>;
    db-update: func(entity: string, id: string, data: list<u8>, version: s64) -> result<list<u8>, string>;
    db-delete: func(entity: string, id: string) -> result<_, string>;
    db-exists: func(entity: string, filter: list<u8>) -> result<bool, string>;
    db-count: func(entity: string, filter: list<u8>) -> result<s64, string>;

    // === 批量操作 ===
    db-query-batch: func(entity: string, field: string, ids: list<string>) -> result<list<u8>, string>;
    db-batch-insert: func(entity: string, items: list<list<u8>>) -> result<list<list<u8>>, string>;

    // === 高级查询 ===
    db-aggregate: func(entity: string, query: list<u8>) -> result<list<u8>, string>;
    db-query-tree: func(entity: string, parent-field: string, root-id: string, max-depth: s32) -> result<list<u8>, string>;

    // === 事件总线 ===
    event-publish: func(event-type: string, payload: list<u8>) -> result<_, string>;

    // === 配置 ===
    config-get: func(key: string) -> result<list<u8>, string>;

    // === 日志 ===
    log-write: func(level: string, message: string);

    // === 用户/权限 ===
    current-user: func() -> result<list<u8>, string>;
    check-permission: func(permission: string) -> result<bool, string>;
}

interface plugin-api {
    init: func() -> result<_, string>;
    on-tenant-created: func(tenant-id: string) -> result<_, string>;
    on-tenant-deleted: func(tenant-id: string) -> result<_, string>;
    handle-event: func(event-type: string, payload: list<u8>) -> result<_, string>;
    render-page: func(page-path: string, params: list<u8>) -> result<list<u8>, string>;
    handle-action: func(page-path: string, action: string, data: list<u8>) -> result<list<u8>, string>;
}

world plugin-world {
    import host-api;
    export plugin-api;
}

共计 15 个 Host API 函数vs 当前 9 个6 个 Plugin API 函数vs 当前 3 个)。