文档/TuffTransport API
通用开发

TuffTransport API

TuffTransport API

概述

TuffTransport 是 Tuff 插件的下一代 IPC 通信系统,提供类型安全、高性能的消息传递,内置批量处理和流式传输支持。

TuffTransport 是当前插件开发唯一支持的 IPC 入口。旧 Channel API 仅作为历史迁移参考保留。

为什么选择 TuffTransport?

特性传统 ChannelTuffTransport
类型安全❌ 字符串事件名✅ 编译时类型检查
自动补全❌ 无 IDE 支持✅ 完整 IntelliSense
批量处理❌ 手动实现✅ 自动请求合并
流式传输❌ 不支持✅ MessagePort 流
错误处理❌ 通用错误✅ 结构化错误类型

介绍

快速开始

EXAMPLE.TS
import { useTuffTransport, CoreBoxEvents, StorageEvents } from '@talex-touch/utils/transport'

const transport = useTuffTransport()

// 发送类型安全的请求
await transport.send(CoreBoxEvents.ui.hide)

// 带参数的请求
const result = await transport.send(CoreBoxEvents.search.query, {
  query: { text: 'hello' }
})

// 批量请求(自动合并)
const [theme, lang] = await Promise.all([
  transport.send(StorageEvents.app.get, { key: 'theme' }),
  transport.send(StorageEvents.app.get, { key: 'language' }),
])

核心概念

TuffEvent

TuffTransport 的每次通信都使用 TuffEvent —— 一个在编译时编码请求/响应类型的类型安全事件定义。

EXAMPLE.TS
// ❌ 已退役:字符串事件名,无类型安全
channel.send('core-box:search:query', { text: 'hello' })

// ✅ TuffTransport:类型安全,自动补全
transport.send(CoreBoxEvents.search.query, { query: { text: 'hello' } })
//                                          ↑ TypeScript 强制正确的参数类型

Event Builder

使用 defineEvent 构建器创建自定义事件:

EXAMPLE.TS
import { defineEvent } from '@talex-touch/utils/transport'

// 定义类型安全的事件
const MyPluginEvents = {
  data: {
    fetch: defineEvent('my-plugin')
      .module('data')
      .event('fetch')
      .define<{ id: string }, { name: string; value: number }>()
  }
}

// 使用 - 完全类型安全!
const result = await transport.send(MyPluginEvents.data.fetch, { id: '123' })
console.log(result.name, result.value) // ✅ 类型安全访问

API 参考

useTuffTransport()

在插件渲染进程中获取 transport 实例。

EXAMPLE.TS
import { useTuffTransport } from '@talex-touch/utils/transport'

const transport = useTuffTransport()

transport.send(event, payload?, options?)

发送请求并等待响应。

参数:

  • event - TuffEvent 实例(必填)
  • payload - 匹配事件请求类型的参数
  • options - 发送选项

选项:

选项类型默认值说明
immediatebooleanfalse跳过批量,立即发送
timeoutnumber10000请求超时(毫秒)

示例:

EXAMPLE.TS
// 简单请求
await transport.send(CoreBoxEvents.ui.hide)

// 带参数的请求
const result = await transport.send(StorageEvents.app.get, { key: 'theme' })

// 立即发送(跳过批量)
await transport.send(StorageEvents.app.set, 
  { key: 'urgent', value: true },
  { immediate: true }
)

// 自定义超时
await transport.send(SlowEvent, data, { timeout: 30000 })

transport.stream(event, payload, options)

通过 MessagePort 发起流式请求。

EXAMPLE.TS
const controller = await transport.stream(
  CoreBoxEvents.search.query,
  { query: { text: 'hello' } },
  {
    onData: (result) => {
      console.log('收到数据:', result)
    },
    onEnd: () => {
      console.log('流完成')
    },
    onError: (err) => {
      console.error('流错误:', err)
    }
  }
)

// 需要时取消流
controller.cancel()

MessagePort 升级与回退

  • stream/on 在允许的通道上会自动升级 MessagePort;不可用时回退到 channel。
  • 运行时开关:TALEX_TRANSPORT_PORT_CHANNELS(逗号分隔事件名),置空可禁用。默认包含: ClipboardEvents.changeAppEvents.fileIndex.progressCoreBoxEvents.search.updateCoreBoxEvents.search.endCoreBoxEvents.search.noResults
  • 手动控制:options.port = false 强制走 channel;或配置 options.port.timeoutMs/options.port.scope
EXAMPLE.TS
// 自定义端口超时
const controller = await transport.stream(
  AppEvents.fileIndex.progress,
  undefined,
  {
    onData: (data) => console.log(data),
    port: { timeoutMs: 800 }
  }
)
EXAMPLE.TS
// 高级用法:主动升级端口(仅在需要时使用)
const handle = await transport.openPort({
  channel: CoreBoxEvents.search.update.toEventName(),
  scope: 'window'
})
handle?.port.postMessage({ type: 'data', payload: { hello: 'world' } })

transport.on(event, handler)

注册事件处理器接收消息。

EXAMPLE.TS
const cleanup = transport.on(SomeEvent, (payload) => {
  console.log('收到:', payload)
  return { success: true } // 返回响应
})

// 完成时清理
onUnmounted(() => cleanup())

transport.flush()

强制发送所有待处理的批量请求。

EXAMPLE.TS
await transport.flush()

预定义事件

CoreBoxEvents

EXAMPLE.TS
import { CoreBoxEvents } from '@talex-touch/utils/transport'

// UI 控制
CoreBoxEvents.ui.show       // 显示 CoreBox
CoreBoxEvents.ui.hide       // 隐藏 CoreBox
CoreBoxEvents.ui.expand     // 展开/收起

// 搜索
CoreBoxEvents.search.query  // 执行搜索(流式)
CoreBoxEvents.search.cancel // 取消搜索

// 输入框
CoreBoxEvents.input.get     // 获取输入值
CoreBoxEvents.input.set     // 设置输入值
CoreBoxEvents.input.clear   // 清空输入

// 提供者
CoreBoxEvents.provider.deactivate    // 停用提供者
CoreBoxEvents.provider.getDetails    // 获取提供者详情(批量)

StorageEvents

EXAMPLE.TS
import { StorageEvents } from '@talex-touch/utils/transport'

// 应用存储(支持批量)
StorageEvents.app.get       // 获取值
StorageEvents.app.set       // 设置值
StorageEvents.app.delete    // 删除值

// 插件存储(支持批量)
StorageEvents.plugin.get    // 获取插件值
StorageEvents.plugin.set    // 设置插件值

PluginEvents

EXAMPLE.TS
import { PluginEvents } from '@talex-touch/utils/transport'

// 生命周期
PluginEvents.lifecycle.load     // 加载插件
PluginEvents.lifecycle.unload   // 卸载插件
PluginEvents.lifecycle.reload   // 重载插件

// 功能
PluginEvents.feature.trigger    // 触发功能

// 日志(批量)
PluginEvents.log.write          // 写入日志

BoxItemEvents

EXAMPLE.TS
import { BoxItemEvents } from '@talex-touch/utils/transport'

// CRUD(支持批量)
BoxItemEvents.crud.create   // 创建项
BoxItemEvents.crud.update   // 更新项
BoxItemEvents.crud.upsert   // 创建或更新
BoxItemEvents.crud.delete   // 删除项

// 批量操作
BoxItemEvents.batch.upsert  // 批量更新
BoxItemEvents.batch.delete  // 批量删除
BoxItemEvents.batch.clear   // 按来源清空

ClipboardEvents v0.9.0

EXAMPLE.TS
import { ClipboardEvents } from '@talex-touch/utils/transport'

// 监听(流式)
ClipboardEvents.change      // 订阅剪贴板变化

// 历史记录(支持批量)
ClipboardEvents.getHistory  // 分页查询历史
ClipboardEvents.getLatest   // 获取最新项

// 操作
ClipboardEvents.apply       // 应用到活动应用
ClipboardEvents.delete      // 删除历史项
ClipboardEvents.setFavorite // 切换收藏状态
ClipboardEvents.clearHistory // 清空历史
ClipboardEvents.getImageUrl // 获取历史图片原图地址
ClipboardEvents.write       // 写入系统剪贴板
ClipboardEvents.read        // 读取当前剪贴板快照
ClipboardEvents.readImage   // 读取剪贴板图片
ClipboardEvents.readFiles   // 读取剪贴板文件
ClipboardEvents.clear       // 清空当前剪贴板
ClipboardEvents.copyAndPaste // 复制并粘贴

示例:订阅剪贴板变化

EXAMPLE.TS
const controller = await transport.stream(
  ClipboardEvents.change,
  undefined,
  {
    onData: ({ latest, history }) => {
      console.log('latest:', latest?.type, latest?.value)
      console.log('history size:', history.length)
    }
  }
)

onUnmounted(() => controller.cancel())

示例:查询剪贴板历史

EXAMPLE.TS
const { items, total } = await transport.send(ClipboardEvents.getHistory, {
  page: 1,
  pageSize: 20,
  type: 'text',
  keyword: '搜索关键词'
})

console.log(items[0]?.value, total)

批量处理

TuffTransport 自动合并支持批量的事件请求:

EXAMPLE.TS
// 这 3 个请求自动合并为 1 次 IPC 调用
const [a, b, c] = await Promise.all([
  transport.send(StorageEvents.app.get, { key: 'a' }),
  transport.send(StorageEvents.app.get, { key: 'b' }),
  transport.send(StorageEvents.app.get, { key: 'c' }),
])
// 结果:单次 IPC 包含 3 个请求,性能提升 500%+

批量配置

定义自定义事件时配置批量:

EXAMPLE.TS
const MyEvent = defineEvent('my-plugin')
  .module('data')
  .event('fetch')
  .define<Request, Response>({
    batch: {
      enabled: true,
      windowMs: 50,        // 收集 50ms
      maxSize: 20,         // 每批最多 20 个请求
      mergeStrategy: 'dedupe' // 'queue' | 'dedupe' | 'latest'
    }
  })

合并策略:

  • queue - 所有请求按顺序处理
  • dedupe - 相同参数的请求共享响应
  • latest - 相同 key 只保留最新请求

流式传输

对于大数据或持续数据,使用 MessagePort 流式传输:

EXAMPLE.TS
const MyStreamEvent = defineEvent('my-plugin')
  .module('data')
  .event('stream')
  .define<{ filter: string }, AsyncIterable<DataItem>>({
    stream: {
      enabled: true,
      bufferSize: 100
    }
  })

// 消费流
const controller = await transport.stream(
  MyStreamEvent,
  { filter: 'active' },
  {
    onData: (item) => items.push(item),
    onEnd: () => console.log('完成'),
    onError: (err) => console.error(err)
  }
)

// 需要时取消
onUnmounted(() => controller.cancel())

错误处理

TuffTransport 提供结构化错误:

EXAMPLE.TS
import { TuffTransportError, TuffTransportErrorCode } from '@talex-touch/utils/transport'

try {
  await transport.send(SomeEvent, data)
} catch (err) {
  if (err instanceof TuffTransportError) {
    switch (err.code) {
      case TuffTransportErrorCode.TIMEOUT:
        console.log('请求超时')
        break
      case TuffTransportErrorCode.INVALID_EVENT:
        console.log('无效事件 - 请使用 defineEvent()')
        break
      case TuffTransportErrorCode.UNKNOWN_EVENT:
        console.log('未注册处理器')
        break
    }
  }
}

错误码:

错误码说明
INVALID_EVENT不是有效的 TuffEvent
UNKNOWN_EVENT未注册处理器
TIMEOUT请求超时
STREAM_CANCELLED流被取消
BATCH_FAILED批量请求失败
SERIALIZE_FAILED参数序列化失败

从传统 Channel 迁移

之前(传统方式)

EXAMPLE.TS
import { useChannel } from '@talex-touch/utils/plugin/sdk'

const channel = useChannel()
const result = await channel.send('core-box:search:query', { text: 'hello' })

之后(TuffTransport)

EXAMPLE.TS
import { useTuffTransport, CoreBoxEvents } from '@talex-touch/utils/transport'

const transport = useTuffTransport()
const result = await transport.send(CoreBoxEvents.search.query, { query: { text: 'hello' } })

硬切迁移

不要让新插件代码继续停留在 Channel 路径。请在同一次变更中把字符串事件发送替换为 typed TuffTransport 事件:

EXAMPLE.TS
await transport.send(StorageEvents.app.get, { key: 'theme' })

技术原理

  • 事件通过 defineEvent().module().event().define() 构建类型约束,编译期校验请求/响应。
  • 批量处理在客户端聚合请求,按策略合并并在主进程侧拆分。
  • 流式传输基于 MessagePort,适用于持续数据推送场景。

最佳实践

  1. 始终使用 TuffEvent - 不要传字符串给 transport.send()
  2. 定义自定义事件 - 使用 defineEvent() 创建插件特定的通信
  3. 利用批量处理 - 使用 Promise.all() 发起多个请求
  4. 清理处理器 - 在 onUnmounted() 中调用清理函数
  5. 处理错误 - 检查 TuffTransportErrorCode 进行特定处理
  6. 使用流式传输 - 用于大数据或实时更新

类型定义

EXAMPLE.TS
interface ITuffTransport {
  send<TReq, TRes>(
    event: TuffEvent<TReq, TRes>,
    payload: TReq,
    options?: SendOptions
  ): Promise<TRes>

  stream<TReq, TChunk>(
    event: TuffEvent<TReq, AsyncIterable<TChunk>>,
    payload: TReq,
    options: StreamOptions<TChunk>
  ): Promise<StreamController>

  on<TReq, TRes>(
    event: TuffEvent<TReq, TRes>,
    handler: (payload: TReq) => TRes | Promise<TRes>
  ): () => void

  flush(): Promise<void>
  destroy(): void
}

interface SendOptions {
  immediate?: boolean
  timeout?: number
}

interface StreamOptions<T> {
  onData: (chunk: T) => void
  onError?: (error: Error) => void
  onEnd?: () => void
}

interface StreamController {
  cancel(): void
  readonly cancelled: boolean
  readonly streamId: string
}