Skip to content

DSL SDK(@tmagic/editor/sdk) ​

不依赖 DOM / Vue 界面 / 画布的 DSL 操作 SDK,只负责对 DSL 做增删改查、历史撤销重做、依赖收集与合法性校验,可以直接在 Node 中运行(比如给 AI agent 调用的命令行工具使用)。

浏览器端的 editorService、historyService 等单例就是用同一个工厂创建的默认实例,因此 import { editorService } from '@tmagic/editor' 的用法不受影响。

ts
import { createDslSdk } from '@tmagic/editor/sdk';

快速开始 ​

ts
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 + 历史快照」:

ts
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校验器,见校验
ctxservice 之间共享的上下文
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
ts
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,与浏览器端一致:

ts
sdk.editor.usePlugin({
  beforeAdd(addNode, parent) {
    return [addNode, parent];
  },
});

Powered by 腾讯视频会员平台技术中心