TypeScript 模块集成
本文档说明宿主进程(Desktop、CLI 工具或其他调度进程)如何基于 dotcraft-wire 模块契约集成 TypeScript 外部社交渠道模块。
1. 概览
模块契约为宿主提供稳定的集成边界:
- 从
manifest读取模块元数据 - 通过
createModule(context)创建可运行实例 - 以机器可读方式观察生命周期状态与错误
- 基于
configDescriptors渲染配置界面 - 通过
moduleId做变体切换,同时保持channelName作为运行时逻辑身份
宿主不应依赖包内私有路径,也不应通过源码目录结构推断行为。
2. 加载模块
宿主只从包根导入。
typescript
import { configDescriptors, createModule, manifest } from "@dotcraft/channel-feishu";
import type { ModuleFactory, ModuleManifest } from "dotcraft-wire";
const moduleManifest: ModuleManifest = manifest;
const moduleFactory: ModuleFactory = createModule;
console.log(moduleManifest.moduleId);
console.log(configDescriptors.length);3. 模块发现
宿主可以通过“已知包列表”或“moduleId 配置映射”构建模块注册表。
推荐流程:
- 加载允许接入的包根。
- 读取每个包导出的
manifest。 - 按
moduleId建立索引。 - 可选按
channelName做分组展示。
选择键是 moduleId;运行时逻辑身份保持为 channelName。
4. 创建并启动模块实例
显式构造 WorkspaceContext 并传给模块工厂。
typescript
import { createModule, manifest } from "@dotcraft/channel-feishu";
import type { ModuleInstance, WorkspaceContext } from "dotcraft-wire";
const context: WorkspaceContext = {
workspaceRoot: "F:/workspace/demo",
craftPath: "F:/workspace/demo/.craft",
channelName: manifest.channelName,
moduleId: manifest.moduleId,
};
const instance: ModuleInstance = createModule(context);
await instance.start();启动输入由宿主明确提供,模块不应仅依赖当前工作目录来发现工作区信息。
5. 生命周期观察
建议宿主在 start() 之前注册状态回调。
typescript
import type { LifecycleStatus, ModuleError, ModuleInstance } from "dotcraft-wire";
function mapStatusToHostAction(status: LifecycleStatus, error?: ModuleError): string {
switch (status) {
case "configMissing":
return "提示用户创建模块配置";
case "configInvalid":
return `展示配置错误:${error?.message ?? "配置无效"}`;
case "starting":
return "展示连接中状态";
case "ready":
return "标记模块已就绪";
case "authRequired":
return "启动交互式认证流程";
case "authExpired":
return "提示认证过期并引导重新认证";
case "degraded":
return "展示降级告警";
case "stopped":
return "标记模块已停止";
}
}
function observeLifecycle(instance: ModuleInstance): void {
instance.onStatusChange((status, error) => {
const action = mapStatusToHostAction(status, error);
console.log(`[module-status] ${status} -> ${action}`);
});
}宿主可随时通过 instance.getStatus() 获取当前状态,通过 instance.getError() 获取最近结构化错误。
6. 渲染配置界面
若包导出了 configDescriptors,宿主可据此构建配置表单,无需解析包内私有 schema。
typescript
import { configDescriptors } from "@dotcraft/channel-weixin";
import type { ConfigDescriptor } from "dotcraft-wire";
type FormField = {
key: string;
label: string;
required: boolean;
inputType: "text" | "password" | "checkbox" | "number";
};
function toFormField(descriptor: ConfigDescriptor): FormField {
if (descriptor.dataKind === "secret") {
return { key: descriptor.key, label: descriptor.displayLabel, required: descriptor.required, inputType: "password" };
}
if (descriptor.dataKind === "boolean") {
return { key: descriptor.key, label: descriptor.displayLabel, required: descriptor.required, inputType: "checkbox" };
}
if (descriptor.dataKind === "number") {
return { key: descriptor.key, label: descriptor.displayLabel, required: descriptor.required, inputType: "number" };
}
return { key: descriptor.key, label: descriptor.displayLabel, required: descriptor.required, inputType: "text" };
}
const fields = configDescriptors.map(toFormField);
console.log(fields);宿主 UI 应重点遵循:
required:必填校验masked与dataKind: "secret":敏感字段掩码展示displayLabel/description:作为用户可读提示
7. 交互式初始化
交互式初始化需求应通过生命周期状态表达,而不是绑定某个固定 UI。
typescript
import type { ModuleInstance } from "dotcraft-wire";
function attachInteractiveSetupHandlers(instance: ModuleInstance): void {
instance.onStatusChange((status, error) => {
if (status === "authRequired") {
console.log("展示二维码路径或初始化引导");
return;
}
if (status === "authExpired") {
console.log("提示会话过期并触发重新认证流程");
return;
}
if (status === "configMissing" || status === "configInvalid") {
console.log(`需要处理配置问题:${error?.message ?? status}`);
}
});
}具体交互方式(Desktop 面板、CLI 提示、Dashboard 通知)由宿主决定;契约只要求状态是结构化、可识别的。
8. 停止模块
通过 await instance.stop() 停止模块,并将 stopped 视为该实例的终止状态。
推荐宿主行为:
- 禁用该实例的发送/工具调用入口。
- 将连接状态标记为离线。
- 保留最近结构化错误用于排障与审计。
9. 变体替换
变体替换允许宿主切换同一渠道族的实现,同时保持逻辑渠道身份不变。
选择模型:
- 通过
moduleId选择具体实现 - 通过
channelName保持运行时身份 - 默认配置命名仍按渠道约定(除非 manifest 明确声明不同)
示例:
- 标准版:
moduleId = "feishu-standard",channelName = "feishu" - 企业版:
moduleId = "feishu-enterprise",channelName = "feishu"
因此宿主只需切换 moduleId,无需重写集成模型。
10. 模块接入要求
第三方包满足以下导出即可被同一宿主模型加载:
manifestcreateModule- 可选
configDescriptors
建议接入清单:
- 实现
dotcraft-wire模块契约类型。 - 保持宿主只依赖包根导出。
- 提供机器可读生命周期与错误信号。
- 在模块边界内完成配置验证。
- 提供包级测试与一致性测试。
这样可保证一方、企业版与第三方模块都能在同一宿主边界下热插拔与替换。