应用与插件组合
用精确的插件实例绑定依赖,保持普通业务服务,并按应用需要组合能力。
本页描述已发布的 @lenso/core@0.2.0,源码审计基线固定为 549b987。组合应用时应使用实际安装包的 exports 与匹配的 lockfile。
应用是一张实例依赖图
Plugin<T> 声明一个服务实例:id、精确的 requires 引用、可选元数据,以及 setup(context)。definePlugin 返回这份声明,defineApp({ plugins }) 收集声明。两个函数都不会启动数据库、监听器或业务服务。
startApp 验证整张图,并先启动依赖,再启动消费者。plugins 数组是安装集合。每个被依赖的实例都必须出现在集合中;只安装消费者不会递归安装其依赖。
| 引用 | 含义 |
|---|---|
id | 为诊断和显式操作选择命名。不同实例需要不同、非空的 ID。 |
requires: [database] | 声明对这个确切 Plugin 对象的必需依赖。 |
context.get(database) / app.get(database) | 取得当前应用中属于这个对象的服务。 |
重新创建一个 ID 或服务类型相同的插件,不能满足原对象的依赖。在消费者工厂里创建数据库,再在应用里另建一个数据库,会出现 missing-dependency;若两个对象共用 ID 且都被安装,还会出现 duplicate-id。
从 Notes 的真实组合开始
现有 Notes 应用接受结构类型的数据库插件、对应的 session store 适配函数,以及业务所需的查询。PostgreSQL、Bun SQLite、Workers D1 保留各自的原生数据库类型;业务层依赖小接口 NotesQueries。
下面是 Notes 中的真实插件工厂。相关 import、服务函数和类型都在同一源码文件中:
export function createNotesPlugin<TDatabase>(options: { id: string; database: Plugin<TDatabase>; authentication: Plugin<NotesAuthentication>; queries(database: TDatabase): NotesQueries;}): Plugin<NotesService> { return definePlugin({ id: options.id, requires: [options.database, options.authentication], setup(context) { return createNotesService( options.queries(context.get(options.database)), context.get(options.authentication), ); }, });}应用工厂 只创建一次 authentication,把同一个对象传给 Notes,并返回 plugins: [database, authentication, notes]。宿主安装这组实例。CLI 与 Web 因而复用同一业务规则,数据库和业务插件无需强制依赖 Web。
增加第二个数据库时,先创建一次、分配不同 ID,把引用传给目标消费者,再把两个提供者都安装到应用中。Lenso 不按类型、命名惯例、创建顺序或“主数据库”标记自动选择提供者。
可选能力由工厂明确表达
requires 中的每条边都是必需依赖。支持可选提供者时,把工厂参数设为可选,只在参数存在时声明依赖,并在 setup 中明确处理缺省分支。未声明依赖的 context.get 会报错,不会等待提供者稍后出现。
消费者可以接受 Plugin<SmallServiceInterface>,允许应用替换提供者。替代实现必须满足服务行为,并由应用显式传入。这里没有全局容器或隐式 provider 选择。
Notes 也独立提供 createNotesService(queries, authentication)。插件组合负责初始化与资源归属,不必成为每个单元测试或业务函数的约束。
元数据本身不会执行行为
contributions 是声明式元数据,app.contributions(kind?) 负责收集。Core 不把它自动变成路由、命令或权限。CLI 操作需要独立显式声明。可选 Manage 把同一批操作的所选子集绑定到精确实例;声明能力本身不开放 CLI、MCP 或 HTTP 入口。可选 source: { file, export?, line?, column? } 为诊断提供声明位置;这些元数据不得包含 secret。
app.status() 返回安装实例的 ready 或 stopped。它表示这个应用的生命周期状态,不表示数据库可达、认证成功或持久 worker 健康。
在获取资源前定位装配错误
validatePlugins(plugins) 返回依赖优先的顺序,或抛出包含结构化诊断的 DiagnosticError。startApp 在配置预检与 setup 前调用它。
| 错误码 | 检查方向 |
|---|---|
missing-dependency | 安装的是否是原引用对象,而不是另一次工厂调用的结果? |
duplicate-id | 同一对象是否安装两次,或不同实例是否共用 ID? |
cyclic-dependency | 是否经过另一插件依赖回自身?可把共同能力移到更底层服务。 |
invalid-id / invalid-source | ID 是否非空,显式源码位置是否符合类型要求? |
在版本匹配、已准备好的 Notes checkout 中,lenso inspect --root examples/notes --json 可以查看声明图而不执行 setup,但仍会导入可信配置代码。获取资源前读生命周期与归属,增加来源读取器前读配置。