继承接口面
本页汇总每个插件天然可见、但不属于某个 Tether 产品能力的框架接口:Cordis 核心 Context、 生命周期事件,以及 dsh 中由 loader / hmr / timer 混入的成员。它也是 TS 声明合并与 C# Type-keyed seam 的对照表。
对照基准是 dsh dsh-v0.1.1-rc.1 的
Inherited Cordis API。
该上游页面由 vendored Cordis、loader、hmr 和 timer 源码生成;本页记录当前 C# 实际公开面,不伪造同名成员。
对应的固定源码包括
events.ts、
loader/index.ts、
hmr/index.ts 与
timer/index.ts。
Inherited ctx members
| dsh / TypeScript | Tether / C# | 状态与边界 |
|---|---|---|
ctx.on / ctx.once | ctx.On / ctx.Once | 对应。C# 返回 IDisposable,且通过 Context 注册时随当前 scope 回收。 |
ctx.emit / parallel / serial / bail / waterfall | EmitAsync / ParallelAsync / SerialAsync / BailAsync / WaterfallAsync | 对应。C# listener 统一允许异步,普通异常向调用方传播。 |
ctx.plugin / ctx.inject | ctx.Plugin / ctx.Inject | 对应。返回 PluginHandle,使用 AwaitSettledAsync() 等待稳定。 |
ctx.effect | ctx.Effect / ctx.Scope.OnDispose | 近似。C# 登记 disposer,不运行并收集一个 effect body。 |
ctx.get / set / provide | Get<T> / SetAsync<T> / Provide<T> | 对应。键为 Type;修改受 exact providing scope 约束。 |
ctx.accessor / ctx.mixin | 无 | TS-only。C# 不动态扩展 Context 属性。 |
ctx.extend | ctx.Fork() | 近似。只派生 effect scope,不继承任意 metadata。 |
ctx.isolate / ctx.intercept | 无 | TS-only。当前没有 label isolation 或 intercept config。 |
ctx.root | ctx.Root | 对应。 |
ctx.scope | ctx.Scope | 对应。C# 类型为 EffectScope。 |
ctx.fiber | ctx.Scope + 挂载方的 PluginHandle | 职责拆分。 |
ctx.registry | ctx.Registry | 近似。Type-keyed store + activation graph。 |
ctx.reflect | 无 | TS-only。显式泛型 API 取代 proxy reflection。 |
ctx.events | ctx.Events | 对应。整棵 Context 树共享。 |
ctx.logger | 无 Cordis 核心成员 | TS-only。日志由宿主 / 产品 seam 显式提供。 |
ctx.timer | 无 ambient service | TS-only。使用 .NET timer,并把取消 / dispose 登记到当前 scope。 |
ctx.interval / timeout / throttle / debounce | 无 Cordis helper | TS-only。没有全局混入;按需要使用 PeriodicTimer、Timer 或产品层封装。 |
ctx.loader | 显式 CompositionLoader / MountedComposition | 职责拆分。Loader 不是 Context 属性。 |
ctx.hmr | 显式 HotReloadWatcher | 职责拆分。HMR watcher 不是 Context 属性。 |
各核心成员的完整签名见 Context、Events、 Fiber 与 Plugin Registry。
C# Context 默认公开面
下面是插件收到的 Context 上可直接调用的类别级清单;泛型 overload 在所属参考页逐项列出。
| 类别 | 成员 |
|---|---|
| 根与 scope | Root、IsRoot、Registry、Events、Scope、IsReady |
| 应用生命周期 | StartAsync()(仅根)、DisposeAsync() |
| 服务读取 | Get<T>()、Require<T>()、Has<T>() |
| 服务变更 | Provide<T>() / Provide(Type, ...)、SetAsync<T>()、RemoveAsync<T>() |
| effect | Effect(...)、OnDispose(...) |
| 事件 | On(...)、OnWaterfall(...)、Once(...)、Off(...)、五种派发方法 |
| scope | Fork() |
| 插件 | Plugin(...)、Plugin<TPlugin>()、Inject(...) |
这些不是从多个服务动态 mixin 出来的代理属性,而是 Context 的真实公开成员。直接使用 ctx.Events.On(...)
或其它底层对象时,要重新承担生命周期所有权;插件日常代码应优先调用 Context 转发。
Inherited events:TS → C#
dsh inherited 页面列出的 core / loader / hmr 事件,与 C# 当前事件面并非一一同名:
| dsh event | C# 对应 | 状态与差异 |
|---|---|---|
internal/plugin | 无公开事件 | Context.Plugin() 直接返回句柄;核心不广播创建通知。 |
internal/status | 无公开事件 | 查询 PluginHandle.IsActive / IsDisposed、EffectScope.State,或等待 settlement。 |
internal/service | service(不是同一 hook) | dsh 项是 service binding interception;C# 项是服务发布、更新或移除后的 Type-keyed 收敛通知。 |
internal/update | 无 | 组合更新走显式 MountedComposition.ReplaceAsync();没有可拦截的核心 waterfall。 |
internal/get | 无 | Registry.Get 不派发读取 hook。 |
internal/set | 无 | SetAsync 执行 owner 校验与依赖收敛,不派发写入 waterfall。 |
internal/listener | 无 | 注册 listener 不广播内部事件。 |
internal/dispatch | 无 | 普通派发不先经过内部 interception event。 |
hmr/change | 无 Cordis event | HotReloadWatcher 内部消费文件变更;公开控制面是方法与状态。 |
hmr/reload | 无 Cordis event | 调用 ReloadAsync(file) / watcher 自动 reload;失败通过 Error 与 internal/error 报告。 |
exit | 无 Cordis core event | 进程宿主负责信号与 root Context disposal。 |
loader/config-update | 无事件 | 调用方渲染候选后显式 ReplaceAsync。 |
loader/entry-init | 无事件 | 插件行初始化属于组合挂载事务内部。 |
loader/partial-dispose | 无事件 | 句柄与组合通过 DisposeAsync / RequestDispose 显式回收。 |
loader/patch-context | 无事件 | C# patch 作用于 stable-id 组合行,不 patch Context proxy。 |
“无公开事件”表示当前 C# 调用方没有同层 hook;不应通过猜测字符串事件名来依赖内部实现。
C# 核心生命周期事件
C# 把插件作者确实需要的框架事件集中在 LifecycleEvents:
public static class LifecycleEvents
{
public const string Ready = "ready";
public const string Dispose = "dispose";
public const string Fork = "fork";
public const string Service = "service";
public const string Error = "internal/error";
}
| 常量 | 参数 | 生产时机与派发方式 |
|---|---|---|
Ready | 根 Context | 根首次 StartAsync() 时顺序等待;普通 listener 失败传播。 |
Dispose | 被回收的 Context | scope 回收期间派发;listener 失败计入 cleanup outcome。 |
Fork | 新 child Context | Fork() 成功后 contained 派发,不阻塞同步调用。 |
Service | 服务键 Type | 服务发布、实例更新或移除的依赖收敛中派发;失败参与聚合结果。 |
Error | Exception、source string | 仅显式 contained 通知的错误 sink;普通派发绝不自动路由到这里。 |
ctx.On<Type>(LifecycleEvents.Service, key =>
{
Console.WriteLine($"service changed: {key.FullName}");
});
ctx.On<Exception, string>(LifecycleEvents.Error, (error, source) =>
{
logger.LogError(error, "Contained Cordis failure from {Source}", source);
});
Loader、HMR 与 timer 为什么不混入 ctx
Loader
CompositionLoader 是显式对象:宿主提供 PluginCatalog,调用 Render* / Load*,
并持有返回的 MountedComposition。组合替换、回滚与 disposal 因此有明确 owner,不依赖 ambient ctx.loader。
HMR
HotReloadWatcher 显式接收 Context、源码目录和 ScriptCompiler。调用方持有 watcher,
可查询 IsRunning / MountedFiles,调用 StartAsync、ReloadAsync、UnmountAsync、DisposeAsync。
它还有 .NET Error event,同时把失败 contained 到 Cordis internal/error;没有 ctx.hmr 或 hmr 字符串事件协议。
Timer
C# 直接使用 BCL timer,并把它绑定到 scope:
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
ctx.Effect(timer.Dispose);
async Task RefreshLoopAsync()
{
try
{
while (await timer.WaitForNextTickAsync(ctx.Scope.Cancellation))
{
await RefreshAsync();
}
}
catch (OperationCanceledException) when (ctx.Scope.Cancellation.IsCancellationRequested)
{
}
}
若长期循环由 apply 启动,插件不能在 apply 中直接 await RefreshLoopAsync();通常启动后台任务、监听 scope cancellation,
并登记一个可等待 disposer 来 join 该任务。核心不提供 interval / timeout / throttle / debounce mixin。
在 C# 中扩展插件可见接口
TS 通过 declare module 扩展 Context / Events 接口,再依靠字符串键和 proxy 注入实现。C# 使用两条显式 seam。
服务:接口 Type
public interface IIndexService
{
ValueTask RebuildAsync(CancellationToken cancellationToken = default);
}
// provider
ctx.Provide<IIndexService>(new LocalIndexService());
// consumer
[Inject(typeof(IIndexService))]
public sealed class SearchPlugin : Plugin
{
protected override void Apply(Context ctx)
=> _ = ctx.Require<IIndexService>();
}
Service Definition 程序集只放接口;Provider 和 Consumer 都引用它。更换 preset 时只换实现,不换消费方引用。
事件:常量 + 泛型调用点
public static class IndexEvents
{
public const string Rebuilt = "index/rebuilt";
}
ctx.On<int>(IndexEvents.Rebuilt, documentCount => { /* ... */ });
await ctx.EmitAsync(IndexEvents.Rebuilt, 42);
事件名仍是运行时字符串,泛型只约束当前 listener 的参数形状;发布包应集中声明常量和约定,不在各处散写 literal。 需要编译期更强的契约时,优先把交互建模为 Service Definition 接口。