From 581fe34f63c89992fb7aa36ec73f2b1bc8bfe0e2 Mon Sep 17 00:00:00 2001 From: zhongqin Date: Sun, 3 May 2026 16:13:03 +0800 Subject: [PATCH] improve plugin sdk --- extensions/plugins/crm/sdk.d.ts | 78 ++++++------ extensions/plugins/ecommerce/sdk.d.ts | 78 ++++++------ extensions/plugins/first-ext/sdk.d.ts | 41 +++++++ extensions/plugins/forum/sdk.d.ts | 78 ++++++------ plugin-sdk/js/js_plugin_v1.js | 118 ++++++++++-------- plugin-sdk/js/js_plugin_v1.ts | 166 ++++++++++++++++++++++++++ plugin-sdk/js/tsconfig.json | 13 ++ plugin-sdk/lua/lua_plugin_v1.lua | 66 +++++----- scripts/gen-plugin-sdk.sh | 40 +++++++ src/constants.rs | 3 + src/plugins.rs | 24 ++-- src/plugins/engine_lua.rs | 6 +- src/plugins/host_common.rs | 8 +- src/plugins/js_host.rs | 25 ++-- src/plugins/lua_host.rs | 27 +++-- 15 files changed, 535 insertions(+), 236 deletions(-) create mode 100644 extensions/plugins/first-ext/sdk.d.ts create mode 100644 plugin-sdk/js/js_plugin_v1.ts create mode 100644 plugin-sdk/js/tsconfig.json create mode 100755 scripts/gen-plugin-sdk.sh diff --git a/extensions/plugins/crm/sdk.d.ts b/extensions/plugins/crm/sdk.d.ts index 67a06a76..ffcda484 100644 --- a/extensions/plugins/crm/sdk.d.ts +++ b/extensions/plugins/crm/sdk.d.ts @@ -1,37 +1,41 @@ -/** - * JS Plugin SDK v1 Type Declarations - */ -export const SDK_VERSION: string; - -export function dbQuery(sql: string, params?: unknown[]): Record[]; -export function dbExec(sql: string, params?: unknown[]): { error?: string; rows_affected?: number }; -export function dbBegin(): { ok: boolean }; -export function dbCommit(): { ok: boolean }; -export function dbRollback(): { ok: boolean }; - -export function httpGet(url: string): string | null; -export function httpGetJson(url: string): Record | null; -export function httpPost(url: string, body: Record | string): string | null; -export function httpPostJson(url: string, body: Record | string): Record | null; - -export function configGet(key: string): string | null; -export function storeGet(key: string): string | null; -export function storeSet(key: string, value: string): boolean; - -export function vfsRead(path: string): string | null; -export function vfsWrite(path: string, content: string): boolean; -export function vfsDelete(path: string): boolean; -export function vfsExists(path: string): boolean; -export function vfsList(path: string): string[] | null; - -export function ok(data: unknown): any; -export function fail(status: number, msg: string): { __plugin_error: boolean; __status: number; __message: string }; - -export function extractJson(input: any, field?: string): any; - -export function logInfo(msg: string): void; -export function logWarn(msg: string): void; -export function logError(msg: string): void; - -export function newId(): string; -export function eventEmit(type: string, data: string | Record): void; +export interface DbExecResult { + error?: string; + rows_affected?: number; +} +export interface PluginError { + __plugin_error: boolean; + __status: number; + __message: string; +} +export declare const SDK_VERSION: string; +export declare function dbQuery(sql: string, params?: unknown[]): Record[]; +export declare function dbExec(sql: string, params?: unknown[]): DbExecResult; +export declare function dbBegin(): { + ok: boolean; +}; +export declare function dbCommit(): { + ok: boolean; +}; +export declare function dbRollback(): { + ok: boolean; +}; +export declare function httpGet(url: string): string | null; +export declare function httpGetJson(url: string): Record | null; +export declare function httpPost(url: string, body: Record | string): string | null; +export declare function httpPostJson(url: string, body: Record | string): Record | null; +export declare function configGet(key: string): string | null; +export declare function storeGet(key: string): string | null; +export declare function storeSet(key: string, value: string): boolean; +export declare function vfsRead(path: string): string | null; +export declare function vfsWrite(path: string, content: string): boolean; +export declare function vfsDelete(path: string): boolean; +export declare function vfsExists(path: string): boolean; +export declare function vfsList(path: string): string[] | null; +export declare function ok(data: unknown): any; +export declare function fail(status: number, msg: string): PluginError; +export declare function extractJson(input: any, field?: string): any; +export declare function logInfo(msg: string): void; +export declare function logWarn(msg: string): void; +export declare function logError(msg: string): void; +export declare function newId(): string; +export declare function eventEmit(type: string, data: string | Record): void; diff --git a/extensions/plugins/ecommerce/sdk.d.ts b/extensions/plugins/ecommerce/sdk.d.ts index 67a06a76..ffcda484 100644 --- a/extensions/plugins/ecommerce/sdk.d.ts +++ b/extensions/plugins/ecommerce/sdk.d.ts @@ -1,37 +1,41 @@ -/** - * JS Plugin SDK v1 Type Declarations - */ -export const SDK_VERSION: string; - -export function dbQuery(sql: string, params?: unknown[]): Record[]; -export function dbExec(sql: string, params?: unknown[]): { error?: string; rows_affected?: number }; -export function dbBegin(): { ok: boolean }; -export function dbCommit(): { ok: boolean }; -export function dbRollback(): { ok: boolean }; - -export function httpGet(url: string): string | null; -export function httpGetJson(url: string): Record | null; -export function httpPost(url: string, body: Record | string): string | null; -export function httpPostJson(url: string, body: Record | string): Record | null; - -export function configGet(key: string): string | null; -export function storeGet(key: string): string | null; -export function storeSet(key: string, value: string): boolean; - -export function vfsRead(path: string): string | null; -export function vfsWrite(path: string, content: string): boolean; -export function vfsDelete(path: string): boolean; -export function vfsExists(path: string): boolean; -export function vfsList(path: string): string[] | null; - -export function ok(data: unknown): any; -export function fail(status: number, msg: string): { __plugin_error: boolean; __status: number; __message: string }; - -export function extractJson(input: any, field?: string): any; - -export function logInfo(msg: string): void; -export function logWarn(msg: string): void; -export function logError(msg: string): void; - -export function newId(): string; -export function eventEmit(type: string, data: string | Record): void; +export interface DbExecResult { + error?: string; + rows_affected?: number; +} +export interface PluginError { + __plugin_error: boolean; + __status: number; + __message: string; +} +export declare const SDK_VERSION: string; +export declare function dbQuery(sql: string, params?: unknown[]): Record[]; +export declare function dbExec(sql: string, params?: unknown[]): DbExecResult; +export declare function dbBegin(): { + ok: boolean; +}; +export declare function dbCommit(): { + ok: boolean; +}; +export declare function dbRollback(): { + ok: boolean; +}; +export declare function httpGet(url: string): string | null; +export declare function httpGetJson(url: string): Record | null; +export declare function httpPost(url: string, body: Record | string): string | null; +export declare function httpPostJson(url: string, body: Record | string): Record | null; +export declare function configGet(key: string): string | null; +export declare function storeGet(key: string): string | null; +export declare function storeSet(key: string, value: string): boolean; +export declare function vfsRead(path: string): string | null; +export declare function vfsWrite(path: string, content: string): boolean; +export declare function vfsDelete(path: string): boolean; +export declare function vfsExists(path: string): boolean; +export declare function vfsList(path: string): string[] | null; +export declare function ok(data: unknown): any; +export declare function fail(status: number, msg: string): PluginError; +export declare function extractJson(input: any, field?: string): any; +export declare function logInfo(msg: string): void; +export declare function logWarn(msg: string): void; +export declare function logError(msg: string): void; +export declare function newId(): string; +export declare function eventEmit(type: string, data: string | Record): void; diff --git a/extensions/plugins/first-ext/sdk.d.ts b/extensions/plugins/first-ext/sdk.d.ts new file mode 100644 index 00000000..ffcda484 --- /dev/null +++ b/extensions/plugins/first-ext/sdk.d.ts @@ -0,0 +1,41 @@ +export interface DbExecResult { + error?: string; + rows_affected?: number; +} +export interface PluginError { + __plugin_error: boolean; + __status: number; + __message: string; +} +export declare const SDK_VERSION: string; +export declare function dbQuery(sql: string, params?: unknown[]): Record[]; +export declare function dbExec(sql: string, params?: unknown[]): DbExecResult; +export declare function dbBegin(): { + ok: boolean; +}; +export declare function dbCommit(): { + ok: boolean; +}; +export declare function dbRollback(): { + ok: boolean; +}; +export declare function httpGet(url: string): string | null; +export declare function httpGetJson(url: string): Record | null; +export declare function httpPost(url: string, body: Record | string): string | null; +export declare function httpPostJson(url: string, body: Record | string): Record | null; +export declare function configGet(key: string): string | null; +export declare function storeGet(key: string): string | null; +export declare function storeSet(key: string, value: string): boolean; +export declare function vfsRead(path: string): string | null; +export declare function vfsWrite(path: string, content: string): boolean; +export declare function vfsDelete(path: string): boolean; +export declare function vfsExists(path: string): boolean; +export declare function vfsList(path: string): string[] | null; +export declare function ok(data: unknown): any; +export declare function fail(status: number, msg: string): PluginError; +export declare function extractJson(input: any, field?: string): any; +export declare function logInfo(msg: string): void; +export declare function logWarn(msg: string): void; +export declare function logError(msg: string): void; +export declare function newId(): string; +export declare function eventEmit(type: string, data: string | Record): void; diff --git a/extensions/plugins/forum/sdk.d.ts b/extensions/plugins/forum/sdk.d.ts index 67a06a76..ffcda484 100644 --- a/extensions/plugins/forum/sdk.d.ts +++ b/extensions/plugins/forum/sdk.d.ts @@ -1,37 +1,41 @@ -/** - * JS Plugin SDK v1 Type Declarations - */ -export const SDK_VERSION: string; - -export function dbQuery(sql: string, params?: unknown[]): Record[]; -export function dbExec(sql: string, params?: unknown[]): { error?: string; rows_affected?: number }; -export function dbBegin(): { ok: boolean }; -export function dbCommit(): { ok: boolean }; -export function dbRollback(): { ok: boolean }; - -export function httpGet(url: string): string | null; -export function httpGetJson(url: string): Record | null; -export function httpPost(url: string, body: Record | string): string | null; -export function httpPostJson(url: string, body: Record | string): Record | null; - -export function configGet(key: string): string | null; -export function storeGet(key: string): string | null; -export function storeSet(key: string, value: string): boolean; - -export function vfsRead(path: string): string | null; -export function vfsWrite(path: string, content: string): boolean; -export function vfsDelete(path: string): boolean; -export function vfsExists(path: string): boolean; -export function vfsList(path: string): string[] | null; - -export function ok(data: unknown): any; -export function fail(status: number, msg: string): { __plugin_error: boolean; __status: number; __message: string }; - -export function extractJson(input: any, field?: string): any; - -export function logInfo(msg: string): void; -export function logWarn(msg: string): void; -export function logError(msg: string): void; - -export function newId(): string; -export function eventEmit(type: string, data: string | Record): void; +export interface DbExecResult { + error?: string; + rows_affected?: number; +} +export interface PluginError { + __plugin_error: boolean; + __status: number; + __message: string; +} +export declare const SDK_VERSION: string; +export declare function dbQuery(sql: string, params?: unknown[]): Record[]; +export declare function dbExec(sql: string, params?: unknown[]): DbExecResult; +export declare function dbBegin(): { + ok: boolean; +}; +export declare function dbCommit(): { + ok: boolean; +}; +export declare function dbRollback(): { + ok: boolean; +}; +export declare function httpGet(url: string): string | null; +export declare function httpGetJson(url: string): Record | null; +export declare function httpPost(url: string, body: Record | string): string | null; +export declare function httpPostJson(url: string, body: Record | string): Record | null; +export declare function configGet(key: string): string | null; +export declare function storeGet(key: string): string | null; +export declare function storeSet(key: string, value: string): boolean; +export declare function vfsRead(path: string): string | null; +export declare function vfsWrite(path: string, content: string): boolean; +export declare function vfsDelete(path: string): boolean; +export declare function vfsExists(path: string): boolean; +export declare function vfsList(path: string): string[] | null; +export declare function ok(data: unknown): any; +export declare function fail(status: number, msg: string): PluginError; +export declare function extractJson(input: any, field?: string): any; +export declare function logInfo(msg: string): void; +export declare function logWarn(msg: string): void; +export declare function logError(msg: string): void; +export declare function newId(): string; +export declare function eventEmit(type: string, data: string | Record): void; diff --git a/plugin-sdk/js/js_plugin_v1.js b/plugin-sdk/js/js_plugin_v1.js index 42488c2c..7d48cf4f 100644 --- a/plugin-sdk/js/js_plugin_v1.js +++ b/plugin-sdk/js/js_plugin_v1.js @@ -1,103 +1,123 @@ +// JS Plugin SDK v1 — 唯一真相源 +// 由本文件生成 sdk.d.ts,分发到各插件目录 +// Host 函数签名必须与 src/plugins/js_host.rs register_host_functions() 保持一致 export const SDK_VERSION = "1.0.0"; - export function dbQuery(sql, params) { - const result = Host.dbQuery(sql, params ? JSON.stringify(params) : null); - if (!result) throw new Error("query returned no result"); - if (result.startsWith("error:")) throw new Error(result.slice(6)); + const result = RaisFastHost.dbQuery(sql, params ? JSON.stringify(params) : null); + if (!result) + throw new Error("query returned no result"); + if (result.startsWith("error:")) + throw new Error(result.slice(6)); return JSON.parse(result); } - export function dbExec(sql, params) { - const result = Host.dbExecute(sql, params ? JSON.stringify(params) : null); + const result = RaisFastHost.dbExecute(sql, params ? JSON.stringify(params) : null); return JSON.parse(result); } - export function dbBegin() { - const result = JSON.parse(Host.dbBegin()); - if (!result.ok) throw new Error("dbBegin failed"); + const result = JSON.parse(RaisFastHost.dbBegin()); + if (!result.ok) + throw new Error("dbBegin failed"); return result; } export function dbCommit() { - const result = JSON.parse(Host.dbCommit()); - if (!result.ok) throw new Error("dbCommit failed"); + const result = JSON.parse(RaisFastHost.dbCommit()); + if (!result.ok) + throw new Error("dbCommit failed"); return result; } export function dbRollback() { - return JSON.parse(Host.dbRollback()); + return JSON.parse(RaisFastHost.dbRollback()); } - export function httpGet(url) { - return Host.httpGet(url); + return RaisFastHost.httpGet(url) || null; } - export function httpGetJson(url) { - const result = Host.httpGet(url); - if (!result) return null; + const result = RaisFastHost.httpGet(url); + if (!result) + return null; return JSON.parse(result); } - export function httpPost(url, body) { const json = typeof body === "string" ? body : JSON.stringify(body); - return Host.httpPost(url, json); + return RaisFastHost.httpPost(url, json) || null; } - export function httpPostJson(url, body) { const json = typeof body === "string" ? body : JSON.stringify(body); - const result = Host.httpPost(url, json); - if (!result) return null; + const result = RaisFastHost.httpPost(url, json); + if (!result) + return null; return JSON.parse(result); } - -export function configGet(key) { return Host.getConfig(key); } - -export function storeGet(key) { return Host.getData(key); } -export function storeSet(key, value) { return Host.setData(key, value); } - -export function vfsRead(path) { return Host.fsRead(path); } -export function vfsWrite(path, content) { return Host.fsWrite(path, content); } -export function vfsDelete(path) { return Host.fsDelete(path); } -export function vfsExists(path) { return Host.fsExists(path); } +export function configGet(key) { + return RaisFastHost.getConfig(key); +} +export function storeGet(key) { + return RaisFastHost.getData(key); +} +export function storeSet(key, value) { + return RaisFastHost.setData(key, value); +} +export function vfsRead(path) { + return RaisFastHost.fsRead(path); +} +export function vfsWrite(path, content) { + return RaisFastHost.fsWrite(path, content); +} +export function vfsDelete(path) { + return RaisFastHost.fsDelete(path); +} +export function vfsExists(path) { + return RaisFastHost.fsExists(path) ?? false; +} export function vfsList(path) { - const result = Host.fsList(path); + const result = RaisFastHost.fsList(path); return result ? result.split(",") : null; } - export function ok(data) { return data; } - export function fail(status, msg) { return { __plugin_error: true, __status: status, __message: msg }; } - export function extractJson(input, field) { try { let parsed; if (typeof input === "string") { parsed = JSON.parse(input); - } else { + } + else { parsed = input; } - if (!field) return parsed; + if (!field) + return parsed; const parts = field.split("."); let val = parsed; for (const part of parts) { - if (val == null || typeof val !== "object") return null; + if (val == null || typeof val !== "object") + return null; val = val[part]; } if (typeof val === "string") { - try { return JSON.parse(val); } catch { return val; } + try { + return JSON.parse(val); + } + catch { + return val; + } } return val != null ? val : null; - } catch { return null; } + } + catch { + return null; + } +} +export function logInfo(msg) { RaisFastHost.log("info", msg); } +export function logWarn(msg) { RaisFastHost.log("warn", msg); } +export function logError(msg) { RaisFastHost.log("error", msg); } +export function newId() { + return RaisFastHost.newId(); } - -export function logInfo(msg) { Host.log("info", msg); } -export function logWarn(msg) { Host.log("warn", msg); } -export function logError(msg) { Host.log("error", msg); } - -export function newId() { return Host.newId(); } - export function eventEmit(type, data) { - return Host.emitEvent(type, typeof data === "string" ? data : JSON.stringify(data)); + RaisFastHost.emitEvent(type, typeof data === "string" ? data : JSON.stringify(data)); } diff --git a/plugin-sdk/js/js_plugin_v1.ts b/plugin-sdk/js/js_plugin_v1.ts new file mode 100644 index 00000000..69a206f4 --- /dev/null +++ b/plugin-sdk/js/js_plugin_v1.ts @@ -0,0 +1,166 @@ +// JS Plugin SDK v1 — 唯一真相源 +// 由本文件生成 sdk.d.ts,分发到各插件目录 +// Host 函数签名必须与 src/plugins/js_host.rs register_host_functions() 保持一致 + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +declare const RaisFastHost: { + log(level: string, msg: string): void; + getConfig(key: string): string | null; + httpGet(url: string): string; + httpPost(url: string, body: string): string; + getData(key: string): string | null; + setData(key: string, value: string): boolean; + getPost(slug: string): string | null; + dbQuery(sql: string, params: string | null): string; + dbExecute(sql: string, params: string | null): string; + dbBegin(): string; + dbCommit(): string; + dbRollback(): string; + fsRead(path: string): string | null; + fsWrite(path: string, content: string): boolean; + fsDelete(path: string): boolean; + fsExists(path: string): boolean | null; + fsList(path: string): string | null; + fsStat(path: string): string | null; + newId(): string; + emitEvent(eventType: string, data: string): string; +}; + +export interface DbExecResult { + error?: string; + rows_affected?: number; +} + +export interface PluginError { + __plugin_error: boolean; + __status: number; + __message: string; +} + +export const SDK_VERSION: string = "1.0.0"; + +export function dbQuery(sql: string, params?: unknown[]): Record[] { + const result = RaisFastHost.dbQuery(sql, params ? JSON.stringify(params) : null); + if (!result) throw new Error("query returned no result"); + if (result.startsWith("error:")) throw new Error(result.slice(6)); + return JSON.parse(result); +} + +export function dbExec(sql: string, params?: unknown[]): DbExecResult { + const result = RaisFastHost.dbExecute(sql, params ? JSON.stringify(params) : null); + return JSON.parse(result); +} + +export function dbBegin(): { ok: boolean } { + const result = JSON.parse(RaisFastHost.dbBegin()); + if (!result.ok) throw new Error("dbBegin failed"); + return result; +} + +export function dbCommit(): { ok: boolean } { + const result = JSON.parse(RaisFastHost.dbCommit()); + if (!result.ok) throw new Error("dbCommit failed"); + return result; +} + +export function dbRollback(): { ok: boolean } { + return JSON.parse(RaisFastHost.dbRollback()); +} + +export function httpGet(url: string): string | null { + return RaisFastHost.httpGet(url) || null; +} + +export function httpGetJson(url: string): Record | null { + const result = RaisFastHost.httpGet(url); + if (!result) return null; + return JSON.parse(result); +} + +export function httpPost(url: string, body: Record | string): string | null { + const json = typeof body === "string" ? body : JSON.stringify(body); + return RaisFastHost.httpPost(url, json) || null; +} + +export function httpPostJson(url: string, body: Record | string): Record | null { + const json = typeof body === "string" ? body : JSON.stringify(body); + const result = RaisFastHost.httpPost(url, json); + if (!result) return null; + return JSON.parse(result); +} + +export function configGet(key: string): string | null { + return RaisFastHost.getConfig(key); +} + +export function storeGet(key: string): string | null { + return RaisFastHost.getData(key); +} + +export function storeSet(key: string, value: string): boolean { + return RaisFastHost.setData(key, value); +} + +export function vfsRead(path: string): string | null { + return RaisFastHost.fsRead(path); +} + +export function vfsWrite(path: string, content: string): boolean { + return RaisFastHost.fsWrite(path, content); +} + +export function vfsDelete(path: string): boolean { + return RaisFastHost.fsDelete(path); +} + +export function vfsExists(path: string): boolean { + return RaisFastHost.fsExists(path) ?? false; +} + +export function vfsList(path: string): string[] | null { + const result = RaisFastHost.fsList(path); + return result ? result.split(",") : null; +} + +export function ok(data: unknown): any { + return data; +} + +export function fail(status: number, msg: string): PluginError { + return { __plugin_error: true, __status: status, __message: msg }; +} + +export function extractJson(input: any, field?: string): any { + try { + let parsed: any; + if (typeof input === "string") { + parsed = JSON.parse(input); + } else { + parsed = input; + } + if (!field) return parsed; + const parts = field.split("."); + let val: any = parsed; + for (const part of parts) { + if (val == null || typeof val !== "object") return null; + val = val[part]; + } + if (typeof val === "string") { + try { return JSON.parse(val); } catch { return val; } + } + return val != null ? val : null; + } catch { return null; } +} + +export function logInfo(msg: string): void { RaisFastHost.log("info", msg); } +export function logWarn(msg: string): void { RaisFastHost.log("warn", msg); } +export function logError(msg: string): void { RaisFastHost.log("error", msg); } + +export function newId(): string { + return RaisFastHost.newId(); +} + +export function eventEmit(type: string, data: string | Record): void { + RaisFastHost.emitEvent(type, typeof data === "string" ? data : JSON.stringify(data)); +} diff --git a/plugin-sdk/js/tsconfig.json b/plugin-sdk/js/tsconfig.json new file mode 100644 index 00000000..98a1bbef --- /dev/null +++ b/plugin-sdk/js/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "declaration": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "files": ["js_plugin_v1.ts"] +} diff --git a/plugin-sdk/lua/lua_plugin_v1.lua b/plugin-sdk/lua/lua_plugin_v1.lua index 181e11a8..541f2f00 100644 --- a/plugin-sdk/lua/lua_plugin_v1.lua +++ b/plugin-sdk/lua/lua_plugin_v1.lua @@ -2,70 +2,70 @@ local M = {} M.SDK_VERSION = "1.0.0" function M.dbQuery(sql, params) - local paramsJson = params and Host.jsonEncode(params) or nil - local result = Host.dbQuery(sql, paramsJson) + local paramsJson = params and RaisFastHost.jsonEncode(params) or nil + local result = RaisFastHost.dbQuery(sql, paramsJson) if not result then error("query returned no result") end if result:sub(1, 6) == "error:" then error(result:sub(7)) end - return Host.jsonDecode(result) + return RaisFastHost.jsonDecode(result) end function M.dbExec(sql, params) - local paramsJson = params and Host.jsonEncode(params) or nil - local result = Host.dbExecute(sql, paramsJson) - return Host.jsonDecode(result) + local paramsJson = params and RaisFastHost.jsonEncode(params) or nil + local result = RaisFastHost.dbExecute(sql, paramsJson) + return RaisFastHost.jsonDecode(result) end function M.dbBegin() - local result = Host.jsonDecode(Host.dbBegin()) + local result = RaisFastHost.jsonDecode(RaisFastHost.dbBegin()) if not result.ok then error("dbBegin failed") end return result end function M.dbCommit() - local result = Host.jsonDecode(Host.dbCommit()) + local result = RaisFastHost.jsonDecode(RaisFastHost.dbCommit()) if not result.ok then error("dbCommit failed") end return result end function M.dbRollback() - return Host.jsonDecode(Host.dbRollback()) + return RaisFastHost.jsonDecode(RaisFastHost.dbRollback()) end function M.httpGet(url) - return Host.httpGet(url) + return RaisFastHost.httpGet(url) end function M.httpGetJson(url) - local result = Host.httpGet(url) + local result = RaisFastHost.httpGet(url) if not result then return nil end - local ok, decoded = pcall(Host.jsonDecode, result) + local ok, decoded = pcall(RaisFastHost.jsonDecode, result) return ok and decoded or nil end function M.httpPost(url, body) - local jsonBody = type(body) == "string" and body or Host.jsonEncode(body) - return Host.httpPost(url, jsonBody) + local jsonBody = type(body) == "string" and body or RaisFastHost.jsonEncode(body) + return RaisFastHost.httpPost(url, jsonBody) end function M.httpPostJson(url, body) - local jsonBody = type(body) == "string" and body or Host.jsonEncode(body) - local result = Host.httpPost(url, jsonBody) + local jsonBody = type(body) == "string" and body or RaisFastHost.jsonEncode(body) + local result = RaisFastHost.httpPost(url, jsonBody) if not result then return nil end - local ok, decoded = pcall(Host.jsonDecode, result) + local ok, decoded = pcall(RaisFastHost.jsonDecode, result) return ok and decoded or nil end -function M.configGet(key) return Host.getConfig(key) end +function M.configGet(key) return RaisFastHost.getConfig(key) end -function M.storeGet(key) return Host.getData(key) end -function M.storeSet(key, value) return Host.setData(key, value) end +function M.storeGet(key) return RaisFastHost.getData(key) end +function M.storeSet(key, value) return RaisFastHost.setData(key, value) end -function M.vfsRead(path) return Host.fsRead(path) end -function M.vfsWrite(path, content) return Host.fsWrite(path, content) end -function M.vfsDelete(path) return Host.fsDelete(path) end -function M.vfsExists(path) return Host.fsExists(path) end +function M.vfsRead(path) return RaisFastHost.fsRead(path) end +function M.vfsWrite(path, content) return RaisFastHost.fsWrite(path, content) end +function M.vfsDelete(path) return RaisFastHost.fsDelete(path) end +function M.vfsExists(path) return RaisFastHost.fsExists(path) end function M.vfsList(path) - local result = Host.fsList(path) + local result = RaisFastHost.fsList(path) if not result then return nil end local list = {} for part in result:gmatch("[^,]+") do @@ -86,7 +86,7 @@ function M.extractJson(input, field) local ok, result = pcall(function() local parsed = input if type(input) == "string" then - parsed = Host.jsonDecode(input) + parsed = RaisFastHost.jsonDecode(input) end if not field or field == "" then return parsed end local val = parsed @@ -95,7 +95,7 @@ function M.extractJson(input, field) val = val[part] end if type(val) == "string" then - local decodeOk, decoded = pcall(Host.jsonDecode, val) + local decodeOk, decoded = pcall(RaisFastHost.jsonDecode, val) if decodeOk then return decoded end return val end @@ -104,15 +104,15 @@ function M.extractJson(input, field) return ok and result or nil end -function M.logInfo(msg) Host.log("info", msg) end -function M.logWarn(msg) Host.log("warn", msg) end -function M.logError(msg) Host.log("error", msg) end +function M.logInfo(msg) RaisFastHost.log("info", msg) end +function M.logWarn(msg) RaisFastHost.log("warn", msg) end +function M.logError(msg) RaisFastHost.log("error", msg) end -function M.newId() return Host.newId() end +function M.newId() return RaisFastHost.newId() end function M.eventEmit(eventType, data) - local dataStr = type(data) == "string" and data or Host.jsonEncode(data) - return Host.emitEvent(eventType, dataStr) + local dataStr = type(data) == "string" and data or RaisFastHost.jsonEncode(data) + return RaisFastHost.emitEvent(eventType, dataStr) end _sdk_module = M diff --git a/scripts/gen-plugin-sdk.sh b/scripts/gen-plugin-sdk.sh new file mode 100755 index 00000000..a81ce3f5 --- /dev/null +++ b/scripts/gen-plugin-sdk.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +TSC="${TSC:-}" +if [ -z "$TSC" ]; then + for candidate in \ + "$ROOT_DIR/frontend/web/node_modules/.bin/tsc" \ + "$ROOT_DIR/frontend/sdk/node_modules/.bin/tsc" \ + "$ROOT_DIR/node_modules/.bin/tsc"; do + if [ -x "$candidate" ]; then + TSC="$candidate" + break + fi + done +fi + +if [ -z "$TSC" ]; then + echo "error: tsc not found. Install typescript or set TSC=/path/to/tsc" + exit 1 +fi + +SDK_DIR="$ROOT_DIR/plugin-sdk/js" + +echo "Compiling js_plugin_v1.ts → .js + .d.ts ..." +rm -rf "$SDK_DIR/dist" +"$TSC" --project "$SDK_DIR/tsconfig.json" + +cp "$SDK_DIR/dist/js_plugin_v1.js" "$SDK_DIR/js_plugin_v1.js" +echo " → plugin-sdk/js/js_plugin_v1.js" + +for dir in "$ROOT_DIR"/extensions/plugins/*/; do + cp "$SDK_DIR/dist/js_plugin_v1.d.ts" "${dir}sdk.d.ts" + echo " → ${dir}sdk.d.ts" +done + +rm -rf "$SDK_DIR/dist" +echo "done" diff --git a/src/constants.rs b/src/constants.rs index bbf5b09a..bb54ed06 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -50,3 +50,6 @@ pub const CMS_ADMIN_PREFIX: &str = "/api/v1/admin/cms"; pub const CMS_ROUTE: &str = "/cms"; /// Content Type 管理路由段(相对 API_PREFIX) pub const CMS_ADMIN_ROUTE: &str = "/admin/cms"; + +/// 插件宿主函数全局对象名(JS/Lua) +pub const PLUGIN_HOST_GLOBAL: &str = "RaisFastHost"; diff --git a/src/plugins.rs b/src/plugins.rs index 85596115..3314de6c 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -2102,7 +2102,7 @@ priority = 10 r#" Plugin = { on_post_created = function(data) - Host.log("info", "post created") + RaisFastHost.log("info", "post created") end } "#, @@ -2187,7 +2187,7 @@ priority = 10 r#" Plugin = { on_post_creating = function(input) - local env = Host.getConfig("app.env") + local env = RaisFastHost.getConfig("app.env") if env then input.env = env end @@ -2412,11 +2412,11 @@ Plugin = { on_post_creating = function(input) local slug = input.slug or "" if slug ~= "" then - local exists = Host.fsExists("cache/" .. slug .. ".txt") + local exists = RaisFastHost.fsExists("cache/" .. slug .. ".txt") if exists then input.cache_hit = true end - local stat = Host.fsRead("stats.json") + local stat = RaisFastHost.fsRead("stats.json") if stat then input.stats = stat end @@ -2428,15 +2428,15 @@ Plugin = { local slug = input.slug or "" local title = input.title or "" if slug ~= "" then - Host.fsWrite("cache/" .. slug .. ".txt", title .. "|" .. (input.content or "")) - Host.fsWrite("stats.json", '{"writes":1}') + RaisFastHost.fsWrite("cache/" .. slug .. ".txt", title .. "|" .. (input.content or "")) + RaisFastHost.fsWrite("stats.json", '{"writes":1}') - local info = Host.fsStat("cache/" .. slug .. ".txt") + local info = RaisFastHost.fsStat("cache/" .. slug .. ".txt") if info then input.file_stat = info end - local entries = Host.fsList("cache") + local entries = RaisFastHost.fsList("cache") if entries then input.cache_files = table.concat(entries, ",") end @@ -2447,8 +2447,8 @@ Plugin = { on_post_deleted = function(input) local slug = input.slug or "" if slug ~= "" then - Host.fsDelete("cache/" .. slug .. ".txt") - local entries = Host.fsList("cache") + RaisFastHost.fsDelete("cache/" .. slug .. ".txt") + local entries = RaisFastHost.fsList("cache") if entries then input.remaining = table.concat(entries, ",") end @@ -2559,7 +2559,7 @@ Plugin.ping = function(input) end Plugin.count = function(input) - local result = Host.dbQuery("SELECT COUNT(*) as total FROM posts") + local result = RaisFastHost.dbQuery("SELECT COUNT(*) as total FROM posts") if result and result:sub(1, 6) ~= "error:" then return { status = 200, @@ -2646,7 +2646,7 @@ end ); std::fs::write(plugin_dir.join("plugin.toml"), manifest).unwrap(); - let lua_code = "Plugin = { on_cron_tick = function(data) Host.setData(\"last_job\", data.job_type or \"\") end }"; + let lua_code = "Plugin = { on_cron_tick = function(data) RaisFastHost.setData(\"last_job\", data.job_type or \"\") end }"; std::fs::write(plugin_dir.join("init.lua"), lua_code).unwrap(); let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); diff --git a/src/plugins/engine_lua.rs b/src/plugins/engine_lua.rs index f803e8af..b4e6d751 100644 --- a/src/plugins/engine_lua.rs +++ b/src/plugins/engine_lua.rs @@ -400,7 +400,7 @@ Plugin = { let code = r#" Plugin = { on_post_created = function(data) - Host.log("info", "post created: " .. tostring(data.id)) + RaisFastHost.log("info", "post created: " .. tostring(data.id)) end } "#; @@ -546,11 +546,11 @@ Plugin = { let code = r#" Plugin = { on_post_created = function(data) - local env = Host.getConfig("app.env") + local env = RaisFastHost.getConfig("app.env") if env ~= "test" then error("expected test, got: " .. tostring(env)) end - local unknown = Host.getConfig("nonexistent.key") + local unknown = RaisFastHost.getConfig("nonexistent.key") if unknown ~= nil then error("expected nil for unknown key") end diff --git a/src/plugins/host_common.rs b/src/plugins/host_common.rs index 818aef7a..cb5a86f6 100644 --- a/src/plugins/host_common.rs +++ b/src/plugins/host_common.rs @@ -4,6 +4,8 @@ //! 各引擎的 `register_host_functions` 只负责引擎特定的参数绑定, //! 所有权限校验、HTTP 请求、DB 查询均委托给 `HostContext` 方法。 +use crate::constants::PLUGIN_HOST_GLOBAL; + use std::sync::Arc; use crate::config::app::AppConfig; @@ -148,7 +150,7 @@ impl HostContext { pub fn get_data(&self, key: &str) -> Option { let Some(pool) = &self.pool else { tracing::debug!( - "[plugin:{}] Host.getData called by {} but no DB pool", + "[plugin:{}] {PLUGIN_HOST_GLOBAL}.getData called by {} but no DB pool", self.runtime_label, self.plugin_id ); @@ -171,7 +173,7 @@ impl HostContext { pub fn set_data(&self, key: &str, value: &str) -> bool { let Some(pool) = &self.pool else { tracing::debug!( - "[plugin:{}] Host.setData called by {} but no DB pool", + "[plugin:{}] {PLUGIN_HOST_GLOBAL}.setData called by {} but no DB pool", self.runtime_label, self.plugin_id ); @@ -196,7 +198,7 @@ impl HostContext { pub fn get_post(&self, slug: &str) -> Option { let Some(pool) = &self.pool else { tracing::debug!( - "[plugin:{}] Host.getPost called by {} but no DB pool", + "[plugin:{}] {PLUGIN_HOST_GLOBAL}.getPost called by {} but no DB pool", self.runtime_label, self.plugin_id ); diff --git a/src/plugins/js_host.rs b/src/plugins/js_host.rs index 77dc749b..ade9e406 100644 --- a/src/plugins/js_host.rs +++ b/src/plugins/js_host.rs @@ -1,13 +1,14 @@ //! JS 宿主函数 — 引擎绑定层 //! //! 仅负责将 [`HostContext`](super::host_common::HostContext) 的公共业务逻辑 -//! 绑定到 `QuickJS` 全局对象的 `Host` 属性上。 +//! 绑定到 `QuickJS` 全局对象的 `PLUGIN_HOST_GLOBAL` 属性上。 use std::sync::Arc; use rquickjs::{Function, Object}; use crate::config::app::AppConfig; +use crate::constants::PLUGIN_HOST_GLOBAL; use crate::db::Pool; use crate::plugins::Permissions; use crate::plugins::host_common::HostContext; @@ -148,7 +149,7 @@ pub fn register_host_functions( })?; host.set("emitEvent", emit_event_fn)?; - global.set("Host", host)?; + global.set(PLUGIN_HOST_GLOBAL, host)?; Ok(()) } @@ -173,7 +174,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let log_fn: Function = host.get("log").unwrap(); let _: () = log_fn.call(("info", "test")).unwrap(); @@ -200,7 +201,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_cfg_fn: Function = host.get("getConfig").unwrap(); let env: Option = get_cfg_fn.call(("app.env",)).unwrap(); @@ -224,7 +225,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let http_fn: Function = host.get("httpGet").unwrap(); let result: String = http_fn.call(("https://evil.com",)).unwrap(); @@ -245,7 +246,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let http_fn: Function = host.get("httpPost").unwrap(); let result: String = http_fn.call(("https://evil.com", "{}")).unwrap(); @@ -266,7 +267,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_data_fn: Function = host.get("getData").unwrap(); let result: Option = get_data_fn.call(("some.key",)).unwrap(); @@ -287,7 +288,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let set_data_fn: Function = host.get("setData").unwrap(); let result: bool = set_data_fn.call(("key", "val")).unwrap(); @@ -308,7 +309,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_post_fn: Function = host.get("getPost").unwrap(); let result: Option = get_post_fn.call(("some-slug",)).unwrap(); @@ -329,7 +330,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let db_fn: Function = host.get("dbQuery").unwrap(); let result: String = db_fn.call(("SELECT 1", rquickjs::Undefined)).unwrap(); @@ -350,7 +351,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); let db_fn: Function = host.get("dbQuery").unwrap(); let result: String = db_fn @@ -373,7 +374,7 @@ mod tests { .unwrap(); let global = ctx.globals(); - let host: Object = global.get("Host").unwrap(); + let host: Object = global.get(PLUGIN_HOST_GLOBAL).unwrap(); for name in [ "log", "getConfig", diff --git a/src/plugins/lua_host.rs b/src/plugins/lua_host.rs index 68c1f60b..6d15af1b 100644 --- a/src/plugins/lua_host.rs +++ b/src/plugins/lua_host.rs @@ -1,16 +1,17 @@ //! Lua 宿主函数 — 引擎绑定层 //! //! 仅负责将 [`HostContext`](super::host_common::HostContext) 的公共业务逻辑 -//! 绑定到 Lua 全局 table 的 `Host` 属性上。 +//! 绑定到 Lua 全局 table 的 `PLUGIN_HOST_GLOBAL` 属性上。 use std::sync::Arc; use mlua::{Lua, LuaSerdeExt}; use crate::config::app::AppConfig; +use crate::constants::PLUGIN_HOST_GLOBAL; use crate::db::Pool; -use crate::plugins::Permissions; use crate::plugins::host_common::HostContext; +use crate::plugins::Permissions; /// 注册宿主函数到 Lua 全局作用域。 pub fn register_host_functions( @@ -189,7 +190,7 @@ pub fn register_host_functions( })?; host.set("jsonDecode", json_decode_fn)?; - globals.set("Host", host)?; + globals.set(PLUGIN_HOST_GLOBAL, host)?; Ok(()) } @@ -217,7 +218,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let log_fn: mlua::Function = host.get("log").unwrap(); let _: () = log_fn.call(("info", "test")).unwrap(); @@ -238,7 +239,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_cfg_fn: mlua::Function = host.get("getConfig").unwrap(); let env: String = get_cfg_fn.call(("app.env",)).unwrap(); @@ -259,7 +260,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let http_fn: mlua::Function = host.get("httpGet").unwrap(); let result: String = http_fn.call(("https://evil.com",)).unwrap(); @@ -274,7 +275,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let http_fn: mlua::Function = host.get("httpPost").unwrap(); let result: String = http_fn.call(("https://evil.com", "{}")).unwrap(); @@ -289,7 +290,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_data_fn: mlua::Function = host.get("getData").unwrap(); let result: mlua::Value = get_data_fn.call(("some.key",)).unwrap(); @@ -304,7 +305,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let set_data_fn: mlua::Function = host.get("setData").unwrap(); let result: bool = set_data_fn.call(("key", "val")).unwrap(); @@ -319,7 +320,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let get_post_fn: mlua::Function = host.get("getPost").unwrap(); let result: mlua::Value = get_post_fn.call(("some-slug",)).unwrap(); @@ -334,7 +335,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let db_fn: mlua::Function = host.get("dbQuery").unwrap(); let result: String = db_fn.call(("SELECT 1",)).unwrap(); @@ -349,7 +350,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); let db_fn: mlua::Function = host.get("dbQuery").unwrap(); let result: String = db_fn.call(("DELETE FROM posts",)).unwrap(); @@ -364,7 +365,7 @@ mod tests { register_host_functions(&lua, config, "test-plugin".into(), perms, None, None).unwrap(); let globals = lua.globals(); - let host: mlua::Table = globals.get("Host").unwrap(); + let host: mlua::Table = globals.get(PLUGIN_HOST_GLOBAL).unwrap(); for name in [ "log", "getConfig",