# 超级美眉内核 **Repository Path**: qiuwenwu91/mm_kernel ## Basic Information - **Project Name**: 超级美眉内核 - **Description**: 超级美眉内核,一种全新服务端开发框架,更简易的web、游戏、ai、iot开发,更适合AI的开发框架,能让AI快速建立工作流(workflow),实现高效、可重用业务逻辑。 - **Primary Language**: NodeJS - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-26 - **Last Updated**: 2026-09-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mm_kernel mm 微内核的**架构层**:把 `mm_kernel_core` 的能力机制与 `mm_kernel_ipc` 的消息平面装配成一套可直接使用的微内核,并提供隔离策略与承载适配器。宿主只需 `const { Kernel } = require('mm_kernel')`。 - **一切皆模块**:MySQL、HTTP、订单、支付都是模块,宿主只负责组装 - **能力(ability)为中心**:模块之间只通过能力名协作,不 `require` 彼此 - **双通信平面**:能力平面同步 RPC(`ability.get('db').query()`),消息平面异步投递(`event.publish` / `event.subscribe`),两者不合并 - **隔离可插拔**:`in-process` / `worker` / `process` 三级;未注册的级别**显式报错**,不静默降级 - **自动清理**:经 `resource` / `event` 登记的资源,卸载时自动释放 - **热更新**:`reload(模块名)` 即可替换实现,服务不重启 - **分层装载**:模块在 `package.json` 顶层声明 `group`(`infra` / `adapter` / `gateway` / `domain` / `app`)决定加载层级与顺序(由 `mm_infra_module` 提供) - 不引入第三方依赖,只依赖同仓库的 `mm_kernel_core` 与 `mm_kernel_ipc`;Node.js >= 18 --- ## 目录 - [安装](#安装) - [快速开始](#快速开始) - [架构](#架构) - [核心 API](#核心-api) - [模块开发](#模块开发) - [模块声明(package.json)](#模块声明packagejson) - [隔离级别](#隔离级别) - [进阶能力](#进阶能力) - [错误处理](#错误处理) - [示例与脚本](#示例与脚本) - [参与开发](#参与开发) - [License](#license) --- ## 安装 本包在仓库内以相对路径链接使用,**未发布到 npm**: ```json { "dependencies": { "mm_kernel": "file:../kernel/mm_kernel" } } ``` `mm_kernel` 的自身依赖同样是指向同仓库的路径: ```json { "dependencies": { "mm_kernel_core": "../mm_kernel_core", "mm_kernel_ipc": "../mm_kernel_ipc" } } ``` 要求 Node.js >= 18(使用 `worker_threads`、`AsyncLocalStorage`、`structuredClone` 语义)。 内核对进程内的单例有要求:`mm_kernel_core` 在进程中必须只有一份,否则上下文(ALS)与经 `mm_kernel_core/internal` 拼装的扩展包会绑定到不同实例。 --- ## 快速开始 ### 1. 宿主(唯一可以自由 `require` 的地方) ```js // server/index.js const http = require('http'); const { Kernel } = require('mm_kernel'); const { loadAll } = require('mm_infra_module'); async function main() { const kernel = new Kernel(); // 内核不读配置文件,配置由宿主注册为能力 kernel.registry.add('config', require('./config.json')); await kernel.start(); // 分层装载由 mm_infra_module 提供:按各模块声明的 group 分层,同层内按传入顺序加载 const { order } = await loadAll(kernel, [ 'user-mod', 'order-mod', 'db-mysql', 'web-express' ]); console.info(`loaded: ${order.join(' -> ')}`); const web = kernel.registry.get('web'); http.createServer(web.handler).listen(8080); } main().catch(console.error); ``` ### 2. 模块(只依赖 SDK) ```js // order-mod/index.js const { ability } = require('mm_kernel_sdk'); async function init(ctx) { const db = ability.get('db'); // 通过能力名拿依赖,不 require db-mysql const web = ability.get('web'); ability.add('order.query', { get: (id) => db.query('SELECT * FROM orders WHERE id=?', [id]), list: (query) => db.query('SELECT * FROM orders WHERE user_id=?', [query.user_id]) }); web.post('/orders', async (req) => { // 请求处理发生在加载生命周期之外,用 init(ctx) 传入的 ctx ctx.event.emit('order.created', req.body); return req.body; }); } module.exports = { init }; ``` ```json { "name": "order-mod", "version": "1.0.0", "main": "index.js", "keywords": ["mm_mod"], "group": "domain", "dependencies": { "mm_kernel_sdk": "^1.0.0" } } ``` ### 3. 消息平面 能力平面之外,内核另有一条异步消息平面,按主题投递,投递带背压与 FIFO 顺序保证(队列与邮箱由 `mm_kernel_ipc` 提供): ```js const kernel = new Kernel(); await kernel.start(); const reader = kernel.createContext('reader'); const writer = kernel.createContext('writer'); const cancel = reader.event.subscribe('sensor.reading', (payload) => { console.info('收到读数', payload); }); await writer.event.publish('sensor.reading', { value: 42 }); cancel(); // 隔离侧的事件上行同样落到这条平面上 const external = kernel.createContext('external'); await kernel.publishAs('mod-worker', 'sensor.reading', { value: 43 }); ``` 消息平面默认启用,`new Kernel({ messaging: false })` 可整体关闭;关闭后模块拿到的 `event.publish` / `event.subscribe` 是**显式抛错的占位实现**,不会静默丢消息。 --- ## 架构 `mm_kernel` 只做装配,不含机制实现。机制分属两个零耦合的包: | 包 | 角色 | 内容 | |---|---|---| | `mm_kernel_core` | 能力机制层 | 注册表、加载器、模块上下文、能力代理、契约校验、策略、调用上下文与埋点、隔离缝 | | `mm_kernel_ipc` | 通信扩展 | 消息平面(`lib/message/`)+ 线程承载(`lib/transport/`) | | `mm_kernel` | 架构装配层 | `Kernel`、隔离决策、承载适配器(worker / process)、双平面接线、隔离侧入口 | 装配关系: ``` 宿主 ──> Kernel ──┬─> Registry ────> 能力平面(同步调用、契约校验、策略) ├─> Loader ──────> 隔离缝 ──> WorkerTransport / ProcessTransport └─> TransportRegistry └─> Runtime(消息平面,mm_kernel_ipc)──> ModuleParticipant(每模块一个身份) ``` 周边包(各自独立,内核不感知它们): | 包 | 提供 | 使用者 | |---|---|---| | `mm_kernel_sdk` | `ability` / `resource` / `event` / `config` / `trace` / `scope` 六个命名空间 | 模块 | | `mm_kernel_contracts` | 能力的 JSON 契约(OpenAI function calling 风格) | 模块、模型工具调用 | | `mm_kernel_testing` | `mockContext` / `withMockContext` | 模块单测 | | `mm_kernel_scope` | `createScope` / `Scope`(能力可见性分区) | 宿主 | | `mm_infra_module` | `Catalog`(模块清单的发现、检索、安装、卸载编排)/ `loadAll`(按 `group` 分层装载)/ `doctor`(声明体检) | 宿主 | 依赖方向只有一条:周边包指向内核,内核不指向任何周边包。`mm_kernel` 对 `mm_kernel_core` / `mm_kernel_ipc` 是**实现依赖**(不是 peer):core 提供机制,ipc 提供通信,本包把它们接起来。 --- ## 核心 API ### `require('mm_kernel')` 导出面 | 分组 | 导出 | |---|---| | 架构层 | `Kernel` / `decideIsolation` / `DEFAULT_ISOLATION` / `createMessagePlane` / `ModuleParticipant` / `ModuleHost` / `HOST_ID` / `WorkerTransport` / `ProcessTransport` / `WORKER_ENTRY` / `CHILD_ENTRY` / `createChildWorker` / `createProcessPort` | | 通信层 | re-export `mm_kernel_ipc` 全部导出(`Runtime` / `Participant` / `Mailbox` / `Bus` / `PendingRequests` / `IpcPort` / `WorkerHost` / `WorkerRuntime` / `FRAME_KINDS` / `createFrame` 等) | | 错误族 | re-export `mm_kernel_core` 的 `KernelError` 系(见 [错误处理](#错误处理))与 `CODES` | | 内部件 | `internal`,即 `mm_kernel_core/internal`,仅供官方扩展包与其测试使用,不属公开契约 | 这样 worker / 子进程入口脚本可以经**单一入口**取到全部所需类型。 ### `new Kernel(config?)` | 选项 | 类型 | 默认值 | 说明 | |---|---|---|---| | `default_isolation` | `string \| null` | `null` | 模块未声明 `isolation` 时的默认级别;仍为空则用 `in-process` | | `ready_timeout` | `number` | `30000` | 等待隔离端就绪的毫秒上限 | | `on_conflict` | `string` | `'replace'` | 同名能力冲突策略:`replace` / `warn` / `throw` | | `wrap_in_process` | `boolean` | `false` | 是否把进程内能力也包成动态代理,使 `reload` 对已持有句柄的消费方立即生效 | | `policy` | `Function \| null` | `null` | 策略函数,见 [策略(Policy)](#策略policy) | | `contract` | `object \| null` | `null` | 契约选项,见 [契约与调用校验](#契约与调用校验) | | `messaging` | `object \| false` | `{}` | 消息平面配置(`queue_limit` / `byte_limit` / `request_timeout_ms` / `pending_limit`);传 `false` 关闭消息平面 | | `transports` | `object[]` | worker + process | 隔离承载适配器;显式给出时只装给定的 | 属性: | 属性 | 类型 | 说明 | |---|---|---| | `config` | `object` | 归一化后的内核配置 | | `registry` | `Registry` | 能力注册表,宿主注册能力的入口 | | `transports` | `TransportRegistry` | 隔离承载适配器注册表:`use` / `has` / `resolve` / `list` | | `loader` | `Loader` | 模块加载器,属内核实现细节,不在公开契约内 | | `runtime` | `Runtime \| null` | 消息平面运行时,`messaging: false` 时为 `null` | | `started` | `boolean` | 是否已启动 | | `stopped` | `boolean` | 是否已停止;停止后需重新 `start` 才能加载模块 | `config` / `registry` / `transports` / `loader` / `runtime` 五个引用由 `Object.defineProperty` 锁定为只读,避免被整体替换后内核与外部视图不一致。 方法: | 方法 | 签名 | 说明 | |---|---|---| | `start()` | `async () => void` | 启动内核 | | `stop(options?)` | `async ({ drain?, dispose_timeout? }) => void` | 置停止标志 → 收敛在途加载 → `drain` 等待在途隔离调用 → 逆序卸载全部模块 → 排空消息平面 | | `load(name, options?)` | `async (name, { inject? }) => any` | 加载模块;隔离模块返回 `null`;内核已停止时抛 `KernelError` | | `unload(name, options?)` | `async (name, { dispose_timeout? }) => void` | 卸载模块并清理其能力与资源 | | `reload(name)` | `async (name) => any` | 卸载后重新加载,实现热更新 | | `decideIsolation(manifest)` | `(manifest) => string` | 按「模块声明 → 宿主默认 → `in-process`」决定隔离级别 | | `createContext(name, options?)` | `(name, { forward_event?, scope? }) => object` | 创建模块上下文,并把该模块的消息面接到消息平面 | | `publishAs(name, topic, payload, to?)` | `async (...) => void` | 以指定模块身份发布主题消息;供承载适配器代隔离模块投递 | | `reportError(err)` | `(err) => void` | 上报非致命错误到注册表的 `error` 事件(`phase: 'transport'`) | | `handleTransportCrash(name, reason)` | `(name, reason) => void` | 回收崩溃隔离端的能力与记录 | | `setTracer(tracer?)` | `(tracer) => void` | 注入宿主 tracer,传空恢复空实现 | | `modules()` | `() => string[]` | 列出已加载的模块名 | | `has(name)` | `(name) => boolean` | 判断模块是否已加载 | `load` 的 `inject` 选项用于给隔离模块开放宿主能力(能力名数组),仅 `worker` / `process` 生效;不在清单内的能力名一律按「不存在」处理,不泄露宿主能力清单。 > 内核不提供 `loadAll`:分层装载由 `mm_infra_module` 的 `loadAll(kernel, names, options?)` 提供。 > 内核也不提供 `createScope`:`createContext` 只保留 `scope` 挂载点,创建作用域由 `mm_kernel_scope` 提供 `createScope(kernel, name, { parent?, meta? }) => Scope`。 ### `Registry`(`kernel.registry`) | 方法 | 说明 | |---|---| | `add(name, impl, meta?)` | 注册能力;`meta` 可含 `isolation` / `transport` / `endpoint` / `module` / `permissions` / `description` / `schema` / `effect` / `visibility` / `tags` / `version` | | `get(name)` | 取能力实现,不存在时抛 `AbilityNotFound` | | `getRecord(name)` | 取能力完整记录(隔离级别、模块、契约等),不存在时抛 `AbilityNotFound` | | `has(name)` / `del(name, module?)` / `list(filter?)` | 判断 / 删除 / 列清单;`filter` 支持 `visibility` / `module` / `effect` / `tag` | | `bindContract(name, record)` | 按注册表配置解析并绑定契约,作用域注册能力时复用同一逻辑 | | `on(event, fn)` / `off(event, fn)` / `emit(event, payload)` | 事件总线 | 能力记录的默认值:`isolation: 'in-process'`、`visibility: 'internal'`、`tags: []`、`permissions: []`,其余未给出的字段为 `null`。 宿主注册能力的典型写法: ```js kernel.registry.add('config', config); kernel.registry.add('settings', settings); kernel.registry.add('admin', { reload: (name) => kernel.reload(name), unload: (name) => kernel.unload(name), load: (name) => kernel.load(name) }); ``` ### 双通信平面 两条平面**刻意不合并**:一个要同步拿返回值的调用走能力平面,一个不想等、可能跨越多个消费者的事件走消息平面。 #### 能力平面(同步 RPC) - 模块在 `init` 中 `ability.add('order.query', impl)` 注册,用 `ability.get('order.query')` 消费 - 消费方拿到的是**按名解析的代理**,不是实现引用,因此 `reload` 后自动指向新实现 - 契约校验、策略校验、调用上下文与埋点都发生在这条路径上 - 跨隔离端的调用由承载适配器把方法调用编成帧,超时由代理兜底 #### 消息平面(异步投递) 由 `createMessagePlane` 装配,参与者**按需注册**——模块第一次发布或订阅时才登记进消息平面,未使用消息面的模块不产生任何参与者与队列开销。 ```js const { createMessagePlane } = require('mm_kernel'); // Kernel 内部即这样装配,宿主一般无需直接调用 const { runtime, createPort, ensureParticipant } = createMessagePlane({ config: { queue_limit: 1000, byte_limit: 1024 * 1024, request_timeout_ms: 30000, pending_limit: 1000 }, onError: (error) => console.error(error) }); ``` | 概念 | 类型 | 说明 | |---|---|---| | `Runtime` | `mm_kernel_ipc` | 消息平面运行时:寻址表、邮箱背压、请求-应答、停机排空 | | `ModuleParticipant` | 本包 | 模块在消息平面上的身份,`id` 等于模块名;收 `publish` 帧并按 topic 分发给本模块订阅者 | | `MessagePort` | 门面 | 由 `createPort(模块名)` 产出,只有 `publish(topic, payload, opts?)` 与 `subscribe(topic, handler, opts?)` 两个方法 | 投递语义: - `publish(topic, payload)` 缺省投给 `topic:<主题>` 的订阅者;给 `opts.to` 时按参与者 id 直投 - `subscribe` 返回取消函数,可安全重复调用;`opts.once` 命中一次后自动退订 - 单个订阅者抛错只上报,不中断其余订阅者 - 载荷需**可结构化克隆** 模块内以 `ctx.event` 使用同一条平面: | 上下文方法 | 说明 | |---|---| | `event.publish(topic, payload, opts?)` | 投递一条主题消息 | | `event.subscribe(topic, handler, opts?)` | 订阅主题,返回取消函数 | | `event.on(event, fn)` / `event.off(event, fn)` / `event.emit(event, payload)` | 进程内事件总线(`registry` 的事件),非主题消息 | `forward_event` 选项可把 `event.emit` 也转发出去;`mm_kernel` 的 `ModuleHost` 正是用它把隔离侧事件抬升到宿主事件总线。承载适配器在隔离模块调用 `event.publish` 时代为投递——**隔离侧没有总线,主题投递必须由宿主以该模块身份完成**,这就是 `Kernel.publishAs` 的用途。 ### 承载适配器(`TransportAdapter`) 隔离缝由 `mm_kernel_core` 定义、由本包注入实现。注册进 `kernel.transports` 的对象需满足: | 成员 | 必需 | 说明 | |---|---|---| | `isolation` | 是 | 适配的隔离级别,注册表的键 | | `start(context)` | 是 | 拉起隔离端并等就绪,返回 `{ endpoint, abilities }`;`context` 含 `name` / `entry_path` / `manifest` / `inject` / `kernel` | | `stop(record)` | 是 | 停止隔离端并回收资源,需幂等 | | `createProxy(record, ability_name)` | 是 | 为该端点上的某个能力创建调用代理 | | `pendingCount(record)` | 否 | 上报在途调用数,供 `kernel.stop({ drain })` 判断能否安全关闭 | 形状不合法的适配器在 `transports.use()` 时即被拒绝;模块声明了未注册的级别时 `Loader` 抛 `TRANSPORT_NOT_REGISTERED`,**不静默降级**为 in-process。 本包提供两个实现: | 类 | `isolation` | 实现 | 入口脚本 | |---|---|---|---| | `WorkerTransport` | `worker` | 一个模块 = 一个 worker = 一个隔离端,`worker_threads` 承载 | `WORKER_ENTRY` → `src/worker_entry.js` | | `ProcessTransport` | `process` | 继承 `WorkerTransport` 的**全部**逻辑,只覆盖入口与隔离端创建,`child_process.fork` 承载 | `CHILD_ENTRY` → `src/child_entry.js` | 两者共用同一套帧协议、反向调用、事件上报、超时与停机协商,差别只在隔离端:`worker` 走 `workerData` + structuredClone,`process` 走环境变量 + 子进程 IPC(JSON 序列化,`undefined` 会被丢弃、`Date` 会退化为字符串)。`process_port.js` 提供的 `createChildWorker` / `createProcessPort` 就是吸收 `child_process` 与 `MessagePort` 形状差异的两个小适配器。 `WorkerTransport` 选项:`inflight_limit`(默认 1000)、`ready_timeout_ms`(默认 10000)、`stop_timeout_ms`(默认 5000)。单次能力调用的超时取模块声明的 `timeout`,缺省 30000。 ### 隔离侧(`ModuleHost`) 隔离端**不需要完整内核**——没有 `Loader`、没有策略、没有全局契约表(契约已由宿主侧代理在转发前校验过),只需要一个本地注册表 + 一个模块上下文: | 成员 | 说明 | |---|---| | `id` / `module_name` | 参与者 id,等于模块名;`WorkerHost` 据此上报 READY | | `registry` | 本地能力表:模块自身能力 + 注入的宿主能力代理 | | `abilities` | 本模块对外提供的能力名,随 READY 帧上报 | | `_attach(runtime)` | 由 `WorkerHost` 注入宿主通道并建立模块上下文 | | `onStart()` | 注入宿主能力代理 → `require` 模块 → 执行 `init` → 收集能力名 | | `onStop()` | 调模块 `dispose`,再回收上下文登记的资源与能力 | | `_handle(msg)` | 宿主调用入口,在「模块上下文 + 调用上下文」中执行一次能力方法 | `HOST_ID = '@host'` 是隔离侧对宿主的保留地址:隔离侧只能寻址它,`event`(事件上报)、`publish`(主题投递)、`host-call`(反向调用宿主能力)三类上行帧都由承载适配器在宿主侧处理。不在 `inject` 白名单内的反向调用返回 `AbilityNotFound`,与本地行为一致。 已知边界:隔离侧**尚不支持主题订阅**(`event.subscribe` 抛 `INVALID_STATE`),事件上行与主题发布可用。 ### `decideIsolation(manifest, default_isolation?)` 隔离级别决策是**策略**而非机制,因此放在架构层:替换本文件的规则不需要动内核一行代码。 ```js const { decideIsolation, DEFAULT_ISOLATION } = require('mm_kernel'); // DEFAULT_ISOLATION === 'in-process' decideIsolation({ isolation: 'worker' }); // 'worker'(模块声明优先) decideIsolation({}, 'process'); // 'process'(宿主默认) decideIsolation({}); // 'in-process'(兜底) ``` --- ## 模块开发 ### 平台模块 vs 业务模块 | 类型 | 可以 `require` | 例子 | 职责 | |---|---|---|---| | 平台模块 | 任何东西(`fs`、`mysql2`、`express`) | `db-mysql`、`web-express` | 把底层库封装为能力 | | 业务模块 | `mm_kernel_sdk`、纯函数库、自己的文件 | `order-mod`、`user-mod` | 只通过能力名拿依赖 | 业务模块**不要**直接 `require`:内置 IO(`fs` / `http` / `net` / `child_process` / `worker_threads`)、平台库(`mysql2` / `redis` / `express`)、其他业务模块。 ### 生命周期 内核通过 `require.resolve(模块名)` 定位入口(也接受绝对路径),并向上查找最近的 `package.json` 作为模块声明。 | 导出 | 时机 | 说明 | |---|---|---| | `init(ctx)` | `load` 时调用一次,可返回 Promise | 注册能力、登记路由与资源;`ctx` 供生命周期外使用 | | `dispose()` | `unload` 时调用 | 需要显式收尾时使用;已登记到 `ctx` 的资源会自动清理 | 卸载顺序:`exports.dispose()` → `ctx._dispose()`(逆序清理 disposables、注销本模块注册的能力)→ 清理 `require.cache`(仅模块自身包内文件,不误伤共享依赖)。`dispose` 抛错或超时都不阻断后续清理,异常上报到 `error` 事件。 ### 资源自动清理 ```js resource.add('db-pool', pool); // 命中 close / end / dispose / destroy resource.add('timer', { dispose: () => clearInterval(timer) }); ``` 未通过 `resource` 登记的句柄,内核无法回收,责任自负。 ### 上下文(`ctx`) | 字段 | 说明 | |---|---| | `module_name` | 本模块名 | | `ability` | `add` / `get` / `has` / `del` / `list` / `call` / `stream` / `current` / `ref` | | `resource` | `add` / `del` | | `event` | `on` / `off` / `emit` / `publish` / `subscribe` | | `scope` | 挂载点,未绑定时为 `null` | | `_dispose()` | 逆序回收该上下文登记的全部资源与能力 | SDK(`mm_kernel_sdk`)只在模块的 `require` 阶段与 `init` 阶段可用——依靠 `AsyncLocalStorage` 承载上下文,并发的加载/调用不会串台。生命周期之外请使用 `init(ctx)` 传入的 `ctx`。 --- ## 模块声明(package.json) ```json { "name": "order-mod", "version": "1.0.0", "main": "index.js", "keywords": ["mm_mod"], "group": "domain", "isolation": "worker", "timeout": 30000 } ``` 声明字段全部位于 `package.json` **顶层**,不存在 `kernel` 容器,也不需要声明任何内核归属字段: | 顶层字段 | 适用 | 说明 | |---|---|---| | `keywords` | 全部 | 含 `mm_mod` 即被认定为 mm 系统可用模块;发现与安装校验只认它,包名与 npm scope 不参与判定 | | `group` | 全部 | `infra` / `adapter` / `gateway` / `domain` / `app`,决定加载层级与顺序;缺省或非法归 `unknown` 并排最后 | | `isolation` | 全部 | `in-process` / `worker` / `process`,缺省取宿主 `default_isolation` | | `timeout` | 隔离模块 | 单次能力调用的毫秒上限,默认 30000 | 内核自身只消费 `isolation` 与 `timeout`(`group` 由 `mm_infra_module` 消费)。模块之间只通过能力名协作,**不声明** `requires` / `provides`:能力是否可用一律以 `kernel.registry` 的注册结果为事实,加载失败会逆序回滚本次新加载的模块。 --- ## 隔离级别 模块声明 `isolation` 优先,其次宿主 `default_isolation`,默认 `in-process`。 | 级别 | 值 | 实现 | 适用 | |---|---|---|---| | L0 | `in-process` | 直接返回真实对象,零开销 | 可信模块 | | L1 | `worker` | `worker_threads` + 帧协议 | 不可信 / 阻塞型模块 | | L2 | `process` | `child_process.fork` + 帧协议 | 需要强隔离或独立进程资源 | 调用方代码对隔离级别无感: ```js const db = ability.get('db'); await db.query('SELECT 1'); // L0 是函数调用,L1/L2 走 IPC ``` 隔离侧的注意事项: - 参数与返回值必须**可序列化**;函数、类实例、Symbol、Stream、循环引用不支持 - `process` 级别额外受子进程 IPC 的 JSON 序列化限制(`undefined` 丢失、`Date` 退化为字符串) - 调用带超时;端点退出或崩溃时,所有在途调用以错误结束,能力被回收,并发出 `module:crashed` 事件 - 隔离模块可以通过 `load(name, { inject: ['config'] })` 反向调用宿主能力 - `load()` 对隔离模块返回 `null`,宿主通过 `ability.get` 使用其能力 ```js kernel.registry.on('module:crashed', ({ module, isolation, reason }) => { console.error(`模块崩溃: ${module} (${isolation}) ${reason}`); }); ``` `mm_kernel` 自带一个演示脚本,覆盖跨线程调用、反向注入、事件上行与消息平面: ```bash npm run demo ``` --- ## 进阶能力 ### 契约与调用校验 契约是**可选的**:声明契约的能力在注册期做结构自检、在调用期做入参校验,`returns` 声明了 `type` 时还做返回值校验;未声明契约的能力完全不参与校验,也不产生开销。 契约载体与 `mm_kernel_contracts` 的 JSON 结构一致,即**能力自带契约时,`meta.schema` 就是该能力契约集合**(`{ 方法名: 契约 }`): ```js ability.add('cache', impl, { schema: { get: require('mm_kernel_contracts').cache.get, set: require('mm_kernel_contracts').cache.set } }); ``` 契约选项配置在 `new Kernel({ contract })` 上,也可直接传给 `new Registry({ contract })`: | 选项 | 类型 | 默认值 | 说明 | |---|---|---|---| | `register` | `string` | `'strict'` | 注册期契约自检模式:`strict` 抛 `ContractError` / `warn` 告警 / `off` 跳过 | | `call` | `string` | `'warn'` | 调用期校验模式:`strict` 抛 `ContractError` / `warn` 告警 / `off` 跳过;同时作用于入参校验与返回值校验 | | `table` | `object` | `{}` | 全局契约表,形如 `{ 能力名: { 方法名: 契约 } }`,能力未自带契约时按能力名回退查表 | ```js const kernel = new Kernel({ contract: { register: 'strict', call: 'warn', table: require('mm_kernel_contracts') } }); ``` **参数顺序**以契约的 `parameters.properties` 键顺序为准,因此键名必须是合法标识符——否则 JS 引擎会重排数字样式的键,静默改变参数顺序,注册期自检会直接报错。 调用期入参校验只做三件事:实参个数不超过声明、必填参数不为 `undefined`、实参第一层类型与声明匹配;嵌套结构不递归校验。校验发生在代理转发之前,因此生效边界是**按名解析的调用路径**:只有 `ability.get` / `ability.ref` 返回的对象才带校验。模块若自行持有实现引用并直接调用,则不经过校验——内核不代理自己没声明契约的能力。 ### 策略(Policy) 在 `ability.add` / `get` / `del`、`event.emit` 四个鉴权点统一校验,未注入策略时全放行: ```js const kernel = new Kernel({ policy: ({ action, module, ability, meta }) => { if (action === 'event.emit' && ability.startsWith('internal:')) return false; if (action === 'ability.get' && ability === 'db' && module === 'untrusted-mod') return false; return true; } }); ``` 策略返回 `false` 或 thenable 视为拒绝,拒绝时抛 `PermissionDenied`。策略是**同步判断**,不要在策略里做 IO。 ### 追踪(Trace) 内核只在关键路径(`kernel.load`、`ability.call`)开启 span,落地方式由宿主决定;未设置 tracer 时全程空实现: ```js kernel.setTracer({ start: (name, meta) => { const span = tracer.startSpan(name, { attributes: meta }); return { set: (k, v) => span.setAttribute(k, v), end: (err) => span.end(err) }; } }); ``` ### 稳定能力句柄(ref) ```js const db = ability.ref('db'); // 每次属性访问实时解析 await kernel.reload('db-mysql'); await db.query('SELECT 1'); // 自动指向新实现 ``` 能力未注册时,访问句柄属性抛 `AbilityNotFound`。 ### 事件 - 模块内 `event.on` 注册的监听随卸载自动取消 - 隔离模块内 `event.emit` 会转发到宿主事件总线(经 `forward_event`) - 注册表内置事件:`add:<能力名>`、`del:<能力名>`、`error`;模块崩溃事件 `module:crashed` 由 `Loader` 在 `handleCrash` 中发出 - 单个监听器抛错不影响其他监听器,异常统一上报到 `error` 事件 --- ## 错误处理 全部错误继承 `KernelError`,从 `require('mm_kernel')`(或 `require('mm_kernel_core')`)统一导出,便于集中捕获: | 错误 | 触发场景 | |---|---| | `KernelError` | 基类 | | `MasError` | 消息平面 / 通信层错误,携带错误码 | | `AbilityNotFound` | 能力不存在 | | `AbilityConflict` | `on_conflict: 'throw'` 且同名能力已注册 | | `ModuleLoadError` | 模块解析 / require / init 失败 | | `PermissionDenied` | 策略拒绝 | | `ScopeDisposed` | 作用域已释放 | | `AbortedError` | 调用被中止 | | `ContractError` | 契约自检不通过,或调用期入参 / 返回值不符合契约 | 每个错误都可携带 `CODES` 中的错误码(`CODES` 随错误族一并导出),用于跨线程边界还原错误语义。 ```js const { KernelError, ModuleLoadError, CODES } = require('mm_kernel'); const { loadAll } = require('mm_infra_module'); try { await loadAll(kernel, mods); } catch (error) { if (error instanceof ModuleLoadError) { console.error(`模块加载失败: ${error.module}`); } else if (error instanceof KernelError) { console.error(error.message); } throw error; } ``` --- ## 示例与脚本 本包内的 `example/demo.js` 是架构层最小演示,直接加载 `test/fixtures/mod-worker`(声明了 worker 隔离的模块),演示四个能力:能力经线程边界调用、隔离模块反向调用宿主能力、隔离模块的事件上行、消息平面的主题发布订阅。 完整组合示例在 `mm_system/examples/`(`web-express` + `db-mysql` + `order-mod` + `user-mod`),宿主演示四包协同:`mm_kernel` 建内核、`mm_kernel_sdk` 供模块开发、`mm_infra_module` 编排清单并按层级装载、`mm_kernel_scope` 划分作用域。 | 脚本 | 说明 | |---|---| | `npm run demo` | 运行 `example/demo.js` | | `npm test` | 集成测试(`jest.integration.config.js`) | | `npm run check` | ESLint 检查 | | `npm run lint` | ESLint 检查并自动修复 | | `npm run format` | Prettier 格式化 | --- ## 参与开发 ```bash cd kernel/mm_kernel npm install npm run check && npm test ``` 代码约定:CommonJS(不用 ESM / TS)、2 空格缩进、单引号、`module.exports` 导出、一个类一个文件、JS + JSDoc 表达类型;新增功能走新接口或新子路径,不改动既有核心接口。 `src/` 布局: | 文件 | 职责 | |---|---| | `kernel.js` | `Kernel` 装配类 | | `isolation.js` | 隔离级别决策(策略层) | | `messaging.js` | 消息平面装配与 `MessagePort` 门面 | | `module_participant.js` | 模块在消息平面上的参与者 | | `module_host.js` | 隔离侧的模块宿主 | | `worker_entry.js` / `child_entry.js` | 隔离侧入口脚本 | | `transport_worker.js` / `transport_process.js` | 两种承载适配器 | | `process_port.js` | `child_process` ↔ `MessagePort` 形状适配 | --- ## License [MIT](LICENSE) © qww