RaisFastRaisFast
Plugins

WASM 插件

用 Rust、Go、C 或 Zig 编译为 WebAssembly 的高性能插件,通过 WIT 接口实现强类型。

WASM 插件以接近原生的速度运行,并具有强沙箱隔离。可以使用任何能编译为 WebAssembly 的语言。

为什么选 WASM?

脚本插件(JS/Lua/Rhai)WASM 插件
性能良好接近原生
语言仅 JS / Lua / RhaiRust、Go、C、Zig 等
类型动态强类型(WIT 接口)
并发Per-request(无状态)实例池(可有状态)
适合快速集成、Hook重计算、数据处理

快速开始(Rust)

1. 创建项目

cargo new --lib my-plugin
cd my-plugin

2. 添加依赖

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.36"

3. 编写插件

下载 plugin.wit 接口文件,然后:

wit_bindgen::generate!({
    path: "../plugin-wit/plugin.wit",
    world: "plugin-world",
});

use exports::raisfast::plugin_wit::plugin_hooks::*;

struct Plugin;

impl Guest for Plugin {
    fn on_post_creating(input: PostInput) -> Option<PostInput> {
        let mut post = input;
        if post.slug.is_none() {
            post.slug = Some(post.title.to_lowercase().replace(' ', "-"));
        }
        Some(post)
    }

    fn on_post_created(output: PostOutput) {
        host_api::log("info", &format!("published: {}", output.slug));
        let key = format!("cache/posts/{}.json", output.slug);
        host_api::vfs_write(&key, "{}");
    }

    fn on_cron_tick(payload: Option<String>) {
        if let Some(p) = payload {
            host_api::log("info", &format!("cron fired: {p}"));
        }
    }
}

export_plugin_hooks!(Plugin);

4. 编译

cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/my_plugin.wasm plugin.wasm

5. Manifest

[plugin]
id = "com.example.my-plugin"
name = "My WASM Plugin"
version = "0.1.0"
runtime = "wasm"
entry = "plugin.wasm"

[hooks.on-post-creating]
priority = 10

[hooks.on-post-created]
priority = 10

[[cron]]
label = "定时任务"
job_type = "my_task"
cron_expr = "0 */6 * * *"

6. 部署

extensions/plugins/my-plugin/
├── manifest.toml
└── plugin.wasm

重启服务或等待热重载自动加载。

WIT 接口

WASM 插件实现 plugin-hooks 接口,导入 host-api

Host API(导入)

插件可以调用的函数:

interface host-api {
    log: func(level: string, msg: string);
    get-config: func(key: string) -> option<string>;
    http-get: func(url: string) -> option<string>;
    http-post: func(url: string, body: string) -> option<string>;
    get-data: func(key: string) -> option<string>;
    set-data: func(key: string, value: string) -> bool;
    get-post: func(slug: string) -> option<string>;
    db-query: func(sql: string, params: option<string>) -> string;
    db-execute: func(sql: string, params: option<string>) -> string;
    db-begin: func() -> string;
    db-commit: func() -> string;
    db-rollback: func() -> string;
    vfs-read: func(path: string) -> option<string>;
    vfs-write: func(path: string, content: string) -> bool;
    vfs-delete: func(path: string) -> bool;
    vfs-exists: func(path: string) -> bool;
    vfs-list: func(path: string) -> option<string>;
    vfs-stat: func(path: string) -> option<string>;
    emit-event: func(event-type: string, data: string) -> string;
}

Plugin Hooks(导出)

插件可以导出的函数:

interface plugin-hooks {
    // Filter — 返回 Some 修改数据,返回 None 跳过
    on-post-creating: func(input: post-input) -> option<post-input>;
    on-post-updating: func(input: post-input) -> option<post-input>;
    on-comment-creating: func(input: comment-input) -> option<comment-input>;
    on-content-creating: func(input: content-event) -> option<content-event>;
    on-content-updating: func(input: content-event) -> option<content-event>;
    render-markdown: func(input: string) -> option<string>;
    filter-html: func(input: string) -> option<string>;

    // Action — 返回值被忽略
    on-post-created: func(output: post-output);
    on-post-updated: func(output: post-output);
    on-post-deleted: func(id: string);
    on-comment-created: func(input: comment-input);
    on-content-created: func(input: content-event);
    on-content-updated: func(input: content-event);
    on-content-deleted: func(content-type: string, id: string);
    on-login: func(user-id: string);
    on-cron-tick: func(payload: option<string>);
}

WIT 类型

record post-input {
    title: string,
    content: string,
    slug: option<string>,
    excerpt: option<string>,
    category-id: option<string>,
    tag-ids: option<list<string>>,
    status: option<string>,
    cover-image: option<string>,
}

record post-output {
    id: string,
    title: string,
    slug: string,
    content: string,
    excerpt: option<string>,
    status: string,
    created-by: string,
    updated-by: option<string>,
    category-id: option<string>,
    view-count: s64,
    created-at: string,
    updated-at: string,
    published-at: option<string>,
}

record comment-input {
    content: string,
    nickname: option<string>,
    email: option<string>,
    parent-id: option<string>,
}

record content-event {
    content-type: string,
    data: string,
    id: option<string>,
}

并发模型

与 JS/Lua(per-request,无状态)不同,WASM 使用实例池

  • N 个预编译实例(通过 PLUGIN_WASM_POOL_SIZE 配置,默认 4)
  • 轮询分发处理并发请求
  • 实例可复用 — 可以在调用之间保持状态
  • 实例间完全隔离

当前限制

WIT 接口目前仅提供原始 SQL(db-querydb-execute)。以下在 JS/Lua/Rhai 中可用的函数尚未暴露给 WASM:

  • 高级 DB API:dbInsertdbFetchOnedbFetchAlldbUpdatedbDeletedbCountdbIncrementdbSumdbGroupBy
  • newId(UUID v7 生成)
  • dbPh(SQL 占位符)

可使用 db-querydb-execute 的原始 SQL 作为替代方案。

使用其他语言

任何能编译为 wasm32-unknown-unknown 并支持 Component Model 的语言都可以:

语言Bindgen 工具
Rustwit-bindgen
Gowit-bindgen-go
Cwit-bindgen-c
Zig社区工具

WIT 接口文件是唯一的契约 — 只要你的语言能实现它,就可以用。

On this page