App — 应用容器
App 是 Aalis 的顶层容器,负责初始化核心子系统、管理插件生命周期和启动消息路由。指令、权限、工具等能力由插件提供。
源码: packages/core/src/orchestration/app.ts
构造函数
const app = new App(options: AppOptions);
// 推荐:
const app = createApp(options: AppOptions);core 不感知"文件系统 / 进程 / 终端"等任何 I/O 概念——core 自身不读取任何 YAML 文件。 配置由宿主从任意来源(文件/URL/远端)加载好,作为快照传进 config;文件读写、watch、 重启、插件发现等 I/O 全部通过 provider 注入。
AppOptions 中只有 config 是必填,其余皆可选:
| 字段 | 类型 | 说明 |
|---|---|---|
config | AalisConfig | ConfigManager | 必填。配置快照(如 { name, logLevel, plugins }),或已构造的 ConfigManager |
configProvider | ConfigProvider | 配置持久化与外部变更监听;缺省=只读内存模式 |
dataDir | string | 业务数据目录(plugin 用作相对路径基准) |
pluginLoader | PluginLoader | 插件加载器;缺省=autoLoadPlugins() 为 no-op,须手动 app.plugin(mod) |
restartStrategy | RestartStrategy | 重启策略;缺省=restart() 抛错 |
events | EventBus | 自定义事件总线 |
services | ServiceContainer | 自定义服务容器 |
hooks | HookRegistry | 自定义钩子注册表 |
contributions | ContributionRegistry | 自定义贡献点注册表 |
logHub | LogHub | 自定义日志通道;缺省=LogHub.default(进程级共享) |
logger | Logger | 自定义 Logger 实现;缺省=DefaultLogger(写入 logHub) |
devMode | boolean | 传给根 Context,决定 provide 是否跑一致性校验;默认 true |
构造时:
- 将
config(快照或现成ConfigManager)规范为ConfigManager - 初始化 events / services / hooks / contributions / logger 及根 Context(注入或自建)
- 创建
PluginManager,并provide('app', this)/provide('plugins', …) - 应用配置中已有的服务偏好(
preferService)
关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
ctx | Context | 根执行上下文 |
plugins | PluginManager | 插件管理器 |
logger | Logger | 日志器 |
events | EventBus | 事件总线 |
services | ServiceContainer | 服务容器 |
hooks | HookRegistry | 钩子注册表 |
contributions | ContributionRegistry | 贡献点注册表 |
核心方法
app.start()
- 发出
app:starting事件 - 发出
app:ready事件(sticky) - 发出
app:started事件(sticky)
每一步都等前一个事件的监听器全部返回后才推进。配置外部变更的热重载编排属宿主政策,由宿主自行 app.ctx.config.watch(cb) 接管,start() 不做。
app.stop()
ctx.config.unwatch()停止监听配置变更- 发出
app:stopping事件(知会用;清理一律走ctx.onDispose) plugins.idle():等在飞的 recompute 排干,否则下一步的 shutdown 请求会被排队、拓扑逆序落空plugins.stopAll():按拓扑逆序disposeAsync所有 active 插件(消费者先关、提供者后关,逐项等待其ctx.onDispose的异步清理完成——落盘/关连接真正结束才轮到下一个)- 清空 sticky 缓存(
app:ready/app:started) disposeAsync根 Context(同样等待异步清理)
单个异步清理项的等待上限由 AppOptions.disposeTimeoutMs 控制(默认 5000ms;0=不设限):超时放弃该项、继续后续清理并 warn 点名,防网络类关闭卡死停机。
app.plugin(module, config?, instanceId?)
注册单个插件,返回值同 plugins.register(false = 重名或未声明 reusable 的多实例)。instanceId 缺省用 module.name。配置合并优先级:代码传入 > 配置文件 > 宿主派生默认值(默认值经 AppOptions.pluginDefaults 注入,core 不认识任何配置词汇;缺省注入 = 无默认值)。三层是逐层深合并:同一路径上双方都是纯对象则递归合并,否则后者整体覆盖;数组与非纯对象(Date / Map / 类实例)是原子值,只覆盖不逐元素合并。所以配置文件里只写了嵌套组中的一个键(只写 server.port),同组其余默认值(server.host)在插件首次 apply 时依然在位。
app.autoLoadPlugins()
通过注入的 pluginLoader 自动发现并注册所有插件;未注入 loader 时为 no-op。流程: discover() 发现插件 → 逐个 load() 并 app.plugin(mod) 注册 → 扫描配置中的多实例条目 (name:suffix,要求模块声明 reusable)。
app.rescanPlugins()
重新扫描插件源,加载新发现的插件(已注册的跳过),返回新加载的插件名列表。优先调用 pluginLoader.reload(desc) 做热重载,未实现时退化为 load(desc);未注入 loader 时返回 []。
app.saveConfig()
委托给 configProvider 持久化当前配置,返回 Promise<void>:Promise 兑现时保存已完成,provider 失败以拒绝传出,调用方应 await;无 provider 时立即完成。并发保存的先后与外部编辑的合并不在此契约内。
app.restart()
委托给注入的 restartStrategy:清空 sticky 缓存 → 发出 app:restarting 事件 → 调用 strategy.restart({ stop })(stop / restart 时序由策略决定)。未注入 restartStrategy 时抛错, 自身不保存任何数据、也不直接 spawn 进程。
基础指令
App 本身不注册指令。基础指令由插件提供,例如 @aalis/plugin-commands 提供 /help、/status、/clear、/shutdown、/restart,@aalis/plugin-authority 提供 /authority、/level、/auto。
| 指令 | 可见性 | 说明 |
|---|---|---|
/help | public | 列出所有已注册指令 |
/status | public | 显示系统状态(服务可用性、工具数、指令数) |
/clear [--type/-t <type>] | public | 清空当前会话指定类型记忆 |
/clear list | public | 列出可清理的记忆类型 |
/clear all [--type/-t <type>] | restricted | 全局清空指定类型记忆 |
/shutdown | restricted | 关闭应用 |
/restart | restricted | 重启应用 |
/authority [target] | public | 查看自己或指定用户的权限等级(owner 显示 ∞) |
/level <target> <level> | restricted | owner 给某外部身份设置权限等级(整数;0 默认,负数封禁;仅 owner 可用,防自授) |
/auto [<分钟>|on|off] | restricted | owner 临时免 dangerous 二次确认(仅 owner 本人) |
配置同步(宿主政策,不在 core)
默认值回填、按 configSchema 裁剪未知字段、配置外部变更的热重载编排均属宿主政策, 由 @aalis/runtime 的 config-sync 模块提供(syncPluginDefaults / installConfigHotReload, startAalis 默认接线;configSync.trimUnknownFields=false 可保留未知字段)。 core 只持有机制:配置快照 get/set、config.watch 透传、updateConfig。 不经 runtime 的嵌入式宿主需要时用这些公开 API 自行编排。