DSL SDK(@tmagic/editor/sdk)
不依赖 DOM / Vue 界面 / 画布的 DSL 操作 SDK,只负责对 DSL 做增删改查、历史撤销重做、依赖收集与合法性校验,可以直接在 Node 中运行(比如给 AI agent 调用的命令行工具使用)。
浏览器端的 editorService、historyService 等单例就是用同一个工厂创建的默认实例,因此 import { editorService } from '@tmagic/editor' 的用法不受影响。
import { createDslSdk } from '@tmagic/editor/sdk';快速开始
import { createDslSdk } from '@tmagic/editor/sdk';
const sdk = createDslSdk({
// 初始 DSL,会被深拷贝,不会修改传入的对象
dsl: { type: 'app', id: 'app_1', items: [{ type: 'page', id: 'page_1', name: 'index', items: [] }] },
// 此前通过 sdk.serializeHistory() 导出的历史快照(可选)
// history,
// 组件列表:用来判断新增的组件类型是否合法
componentGroupList: [{ title: '基础', items: [{ text: '文本', type: 'text' }] }],
// 组件属性表单配置,key 是组件 type,值是该组件的表单配置(FormConfig,即表单项数组):用来校验属性是否合法
propsConfigs: {
text: [{ type: 'text', name: 'text', text: '文本', rules: [{ required: true, message: '文本不能为空' }] }],
},
// 组件被添加时的初始值,与 propsConfigs 的 key 一一对应
propsValues: { text: { text: '文本' } },
});
await sdk.ready; // 登记表(含异步的属性配置)全部就绪
// 没有选中节点时,默认添加到第一个页面;id 由 SDK 生成,后续操作使用返回值里的 id
const node = await sdk.editor.add({ type: 'text', text: 'hello' });
await sdk.editor.update({ id: node.id, text: 'world' });
await sdk.editor.undo();
// 属性不合法时(strict 模式)会抛出 DslValidationError,且不会修改 DSL
await sdk.editor.update({ id: node.id, text: '' }).catch((e) => console.log(e.errors)); // [{ ..., message: '文本 -> 文本不能为空' }]
const newDsl = sdk.getDsl(); // 深拷贝,已包含依赖收集结果
const newHistory = sdk.serializeHistory(); // 可 JSON.stringify,下次作为 history 传回TIP
propsConfigs 的格式与 Editor 的 propsConfigs 相同。实际项目中通常直接使用 runtime 构建出来的 magicPresetConfigs / magicPresetValues(参考快速开始),在 Node 中加载构建产物后传入即可。
无状态使用
每次调用传入「DSL + 历史快照」,操作完成后导出新的「DSL + 历史快照」:
const sdk = createDslSdk({ dsl: readJson('dsl.json'), history: readJson('history.json') });
await sdk.editor.update({ id: 'button_1', text: 'new' });
writeJson('dsl.json', sdk.getDsl());
writeJson('history.json', sdk.serializeHistory());TIP
serializeHistory()导出的快照可以直接JSON.stringify;浏览器端historyService.serialize()默认保留Map,适合写入 IndexedDB。- Node 下默认的
parseDSL只解析 JSON,不会执行代码;如果历史里包含函数 / 正则,请自行传入parseDSL。
createDslSdk(options)
每次调用返回一组相互连线、互相独立的 service,多个实例互不影响。
options
| 参数 | 说明 | 默认值 |
|---|---|---|
dsl | 初始 DSL | - |
history | 历史快照 | - |
validateMode | 校验模式,见校验 | 'strict' |
autoWiring | 是否自动绑定依赖收集连线 | true |
reactivity | 响应式实现:reactive / shallowReactive / toRaw / nextTick,默认使用 Vue 的实现,可覆盖 | Vue |
storage | 存储适配器(localStorage 天然满足),用于复制粘贴、代码块草稿 | 内存实现 MemoryStorage |
parseDSL | 把序列化字符串还原成值 | 只解析 JSON |
historyPersistence | 历史持久化适配器 { get, set }(浏览器端对应 IndexedDB),不提供时 saveToIndexedDB / restoreFromIndexedDB 会 reject | - |
collectWorker | 依赖收集 worker 适配器,不提供则在主线程收集 | - |
copyText / notify | 复制到剪贴板、提示信息,不提供则不做任何事 | - |
componentGroupList / propsConfigs / propsValues / eventMethodList / datasourceList / datasourceConfigs / datasourceValues / datasourceEventMethodList / disabledCodeBlock / disabledDataSource / disabledPageFragment / disabledMultiSelect / alwaysMultiSelect / canDropIn | 登记表,与 Editor props 同名项一一对应 | - |
返回值
| 属性 | 说明 |
|---|---|
editor / history / props / dataSource / codeBlock / dep / storage / events / componentList | 各 service 实例,方法与浏览器端同名 service 一致 |
validate | 校验器,见校验 |
ctx | service 之间共享的上下文 |
ready | 登记表全部就绪后 resolve |
getDsl() | 导出当前 DSL(深拷贝) |
setDsl(dsl) | 整体替换 DSL,会写入历史记录,返回的 Promise 在自动选中节点完成后 resolve |
serializeHistory() | 导出可 JSON 序列化的历史快照 |
bindWiring() | 绑定依赖收集连线(autoWiring: false 时手动调用),返回解绑函数。绑定时如果 DSL 已经设置,会立即补做一次依赖收集 |
destroy() | 销毁 |
校验
validateMode 决定修改 DSL 时如何处理不合法的内容:
strict:修改前校验,不通过抛出DslValidationError(errors里是全部问题),DSL 不会被修改,适合给 agent 调用,agent 可以根据errors修正后重试。Node 下默认。record:不抛错,把校验结果写进editor.state.invalidNodeIds。off:不做额外校验。浏览器端默认实例使用这个模式(属性面板自己负责校验)。
可以通过 sdk.ctx.validateMode = 'off' 在运行时切换。
校验内容:
| 类别 | 内容 |
|---|---|
结构(kind: 'structure') | 组件类型是否在组件列表中(page / page-fragment / container 内置合法,组件列表为空时不校验)、父节点是否是容器、canDropIn、是否禁用页面片、id 是否重复、不允许修改节点类型、数据源类型是否可用 |
属性(kind: 'props') | 用 @tmagic/form/headless 的 validateForm 校验节点 / 数据源的属性表单配置,包含 typeMatch |
import { DslValidationError } from '@tmagic/editor/sdk';
try {
await sdk.editor.add({ type: 'unknown' });
} catch (e) {
if (e instanceof DslValidationError) {
console.log(e.errors); // [{ target, id, path, kind, message, suggestion }]
}
}
// 主动校验
await sdk.validate.node('text_1');
await sdk.validate.dataSource('ds_1');
await sdk.validate.dsl();WARNING
- 数据源的
add/update是同步方法,只做同步的结构校验(数据源类型是否可用);属性校验请用sdk.validate.dataSource(id)。 - SDK 内只注册了
@tmagic/form/headless的内置字段,编辑器自定义字段(data-source-select、code-select等)的typeMatch依赖编辑器的单例 service,不在 SDK 校验范围内;需要时可在浏览器环境下通过@tmagic/editor/headless的editorFields注册。
依赖收集
autoWiring 为 true 时,节点、数据源、代码块发生变化会同步收集依赖,并把结果写回 DSL 的 dataSourceDeps / dataSourceCondDeps / dataSourceMethodDeps,所以操作返回时导出的 DSL 依赖信息已经是最新的。
不需要依赖收集(只做 DSL 增删改)时可以传 autoWiring: false,之后需要时再调用 sdk.bindWiring();注意关闭期间不会同步数据源 / 代码块与依赖目标,也不会自动选中页面。
插件
所有 service 都支持 usePlugin,与浏览器端一致:
sdk.editor.usePlugin({
beforeAdd(addNode, parent) {
return [addNode, parent];
},
});