Bruno 架构解析:Monorepo 布局、请求执行管线与 QuickJS 脚本沙箱
Bruno 是一个以"文件即 API 集合"为核心理念的开源 API 客户端(Postman/Insomnia 的轻量替代)。本文基于仓库内的架构参考文档 .claude/reference/architecture.md,结合各包的实际源码与 package.json,完整梳理 Bruno 的 npm workspaces monorepo 布局、跨进程请求执行管线(渲染进程 → Electron 主进程 → axios/gRPC/WebSocket)、bruno-js 的双模脚本沙箱(QuickJS / Node VM)、.bru/.yml 双文件格式系统,以及关键依赖的版本约束。读完后,你将能够准确定位任意功能所属的包、判断编辑某个包后是否需要重新构建、并理解一次请求从发起到断言执行的完整调用链。
一、Monorepo 结构:17 个 workspace 的分工
Bruno 使用 npm workspaces 组织代码,根 package.json 的 workspaces 字段声明了 packages/ 下的 16 个包目录(含 bruno-sqlite),另有仓库根级的 tests/(Playwright e2e 测试)与 playwright/(测试夹具与辅助函数)。
各包的定位与 npm 包名如下:
| 目录 | npm 包名 | 职责 |
|---|---|---|
packages/bruno-app/ |
@usebruno/app |
React 渲染进程(rsbuild、Redux Toolkit、styled-components) |
packages/bruno-electron/ |
bruno(未加 scope,其余均为 @usebruno/*) |
Electron 主进程,网络请求的实际执行者(electron-builder 打包) |
packages/bruno-js/ |
— | 用户脚本沙箱(QuickJS + node:vm) |
packages/bruno-common/ |
@usebruno/common |
共享工具,依赖 DAG 的底座(rollup → dist/cjs+dist/esm) |
packages/bruno-converters/ |
@usebruno/converters |
导入/导出(Postman、Insomnia、OpenAPI 等) |
packages/bruno-requests/ |
@usebruno/requests |
HTTP/gRPC/WS 请求的共享构件(rollup) |
packages/bruno-filestore/ |
@usebruno/filestore |
.bru/.yml 序列化(rollup + tsc --emitDeclarationOnly) |
packages/bruno-query/ |
@usebruno/query |
JSONPath 风格查询引擎(rollup) |
packages/bruno-lang/ |
— | Bru DSL 语法:v1(arcsecond,遗留)+ v2(ohm-js,当前) |
packages/bruno-schema/ |
@usebruno/schema |
Yup 运行时校验(collections/requests) |
packages/bruno-schema-types/ |
@usebruno/schema-types |
仅 TypeScript 类型定义(tsc,types-only) |
packages/bruno-graphql-docs/ |
@usebruno/graphql-docs |
GraphQL 文档生成器(rollup) |
packages/bruno-toml/ |
— | @iarna/toml 的薄封装(当前未在任何活跃数据路径上使用) |
packages/bruno-cli/ |
— | 命令行执行器,直接从 src/ 运行 |
packages/bruno-tests/ |
— | 测试服务器(express:HTTP/HTTPS/proxy/GraphQL) |
packages/bruno-docs/ |
@usebruno/docs |
占位 stub(仅 package.json + readme,无 src/) |
补充:当前仓库的 workspaces 中还新增了
packages/bruno-sqlite(@usebruno/sqlite),已被bruno-app与bruno-electron作为依赖引入,承担本地 SQLite 持久化相关能力,架构文档的包列表略早于该包的纳入。
构建工具差异:哪些包需要重新构建
每个包的构建工具不同,这直接决定"编辑后要不要重跑构建":
- rollup(产出
dist/cjs+dist/esm):bruno-common、bruno-converters、bruno-requests、bruno-query、bruno-graphql-docs;其中 bruno-filestore 额外执行tsc --emitDeclarationOnly(已核实其 build 脚本为rollup -c && tsc --emitDeclarationOnly -p tsconfig.build.json)。 - 仅 tsc:bruno-schema-types(build 脚本为
tsc -p tsconfig.json)。 - rsbuild:bruno-app。
- 无构建步骤(直接消费
src/):bruno-js、bruno-lang、bruno-schema、bruno-toml、bruno-cli、bruno-electron、bruno-tests、bruno-docs。
因此编辑后必须重新构建的 7 个包(因为它们产出 dist/)是:bruno-common、bruno-requests、bruno-filestore、bruno-converters、bruno-query、bruno-graphql-docs、bruno-schema-types。编辑 bruno-js / bruno-lang / bruno-schema / bruno-toml 则无需重建——这些包以源码形式被直接消费。
依赖方向与所有权边界
内部 @usebruno/* 依赖 DAG 的所有权护栏(bruno-common 保持浏览器安全、bruno-js 不依赖 Electron API、bruno-schema 与 bruno-schema-types 的分工、禁止向上依赖)定义在自动附加的规则文件 .claude/rules/architecture.md 中,区域级规则(.claude/rules/electron-ipc.md、.claude/rules/redux-store.md、.claude/rules/dsl-changes.md)则深入各主题细节。架构参考文档本身定位是"地图",具体约束以上述规则文件为准。
二、请求执行管线:从渲染进程到断言
一次"发送请求"动作的完整链路(以 Electron 应用为准):
- 渲染进程发起 IPC:渲染进程调用
ipcMain.handle('send-http-request', …),处理器位于 bruno-electron/src/ipc/network/index.js。 - 变量插值:
ipc/network/interpolate-vars.js调用@usebruno/common的interpolate完成{{variable}}替换。 - Pre-request 脚本:通过
ScriptRuntime执行(bruno-js/src/runtime/script-runtime.js)。 - 构建并发送请求:HTTP 走 axios,另有 gRPC(
@grpc/grpc-js)与 WebSocket 客户端。认证拦截器(如addDigestInterceptor、applyOAuth1ToRequest)、cookie 处理、scripting.buildScriptedEntry、grpc/ws 辅助等构件来自@usebruno/requests。需要注意:bruno-requests 是"请求构件库"而非编排器,真正的编排发生在ipc/network目录下(prepare-request.js、axios-instance.js、prepare-grpc-request.js、ws-event-handlers.js等文件共同完成这一层)。 - 响应后处理:变量提取由
VarsRuntime负责,测试由TestRuntime负责,断言由AssertRuntime负责——三者与ScriptRuntime是彼此独立的 runtime,均位于 bruno-js/src/runtime/(该目录现有script-runtime.js、test-runtime.js、assert-runtime.js、vars-runtime.js、scripted-entries.js五个文件)。 - 结果回传:结果以数据对象形式返回渲染进程,失败时对象上携带
error字段——handler 不 reject Promise,这是 Electron IPC 的错误约定(详见.claude/rules/electron-ipc.md)。
ipc/network 目录本身就是这条管线的源码级索引:
packages/bruno-electron/src/ipc/network/
index.js # send-http-request 等 IPC handler 总入口
interpolate-vars.js # 第 2 步:变量插值
prepare-request.js # 第 4 步:请求准备
axios-instance.js # HTTP 客户端封装
prepare-grpc-request.js # gRPC 分支
ws-event-handlers.js # WebSocket 分支
grpc-event-handlers.js # gRPC 事件处理
awsv4auth-helper.js # AWS v4 签名认证
三、脚本沙箱(bruno-js):QuickJS 与 Node VM 双模
用户脚本(pre-request / post-response / tests)在两种沙箱中执行:
- QuickJS(默认,"safe" 模式):基于
quickjs-emscripten的 WebAssembly 沙箱,实现位于 bruno-js/src/sandbox/quickjs/。用户脚本被包裹在异步闭包中,setTimeout被替换为基于bru.sleep的异步实现,从而在纯 QuickJS 环境中提供计时语义; - Node VM("developer" 模式):
node:vm的runInContext,位于 bruno-js/src/sandbox/node-vm/,可访问更完整的 Node 能力。
运行时的选择是集中式的。getJsSandboxRuntime 读取集合的 securityConfig.jsSandboxMode 配置完成映射:'safe'(默认)→ QuickJS,'developer' → Node VM。主进程侧的实现(bruno-electron/src/ipc/network/index.js)为:
const getJsSandboxRuntime = (collection) => {
const securityConfig = get(collection, 'securityConfig', {});
if (securityConfig.jsSandboxMode === 'developer') {
return 'nodevm';
}
// default runtime is `quickjs`
return 'quickjs';
};
CLI 侧(bruno-cli/src/commands/run.js)也实现了同名函数,保证命令行执行与桌面应用行为一致。架构文档明确要求:新增沙箱模式逻辑必须走 getJsSandboxRuntime,不要重复推导映射关系。
两种沙箱的脚本包裹前缀与行号偏移量统一维护在 bruno-js/src/utils/sandbox.js:NODEVM_SCRIPT_WRAPPER_OFFSET / QUICKJS_SCRIPT_WRAPPER_OFFSET 由前缀字符串的换行数计算得出,供错误格式化工具把 VM 报告的行号映射回 .bru/.yml 源码行。
脚本上下文中可用的全局对象(由 script-runtime.js 注入):
bru、req,以及res(仅 post-response 阶段);test、expect(chai)、assert(chai);console。
四、文件系统设计:.bru 与 .yml 双格式
bruno-filestore是解析/序列化的中心入口(bruno-filestore/src/index.ts),向下委托给formats/bru与formats/yml两个格式化模块。- 磁盘上存在两种格式:
.bru(Bruno 自有 DSL)与.yml(OpenCollection YAML)。新创建的集合默认使用.yml(DEFAULT_COLLECTION_FORMAT = 'yml',该常量定义于bruno-app的utils/common/constants并被"新建集合/保存临时请求"等 UI 流程引用)。 - 当前
.bru语法是 v2(ohm-js),位于 bruno-lang/v2/src/,filestore 通过bruToJsonV2/jsonToBruV2使用它;v1(arcsecond,位于 bruno-lang/v1/src/)为遗留实现。 - 集合直接存放在文件系统上,由 Electron 主进程用 chokidar 监听变更。
任何对磁盘形状(on-disk shape)的变更,都需要遵循 .claude/rules/dsl-changes.md 中定义的流程。
五、应用侧:Redux Store 与 Providers
渲染进程的状态管理采用 Redux Toolkit。权威 slice 清单见 bruno-app/src/providers/ReduxStore/index.js 中的 reducer 映射(约 11 个 slice,其中 collections/ 目录是体量最大的一块)。中间件约定与副作用边界由 .claude/rules/redux-store.md 规定。
Provider 层(bruno-app/src/providers/)当前包含:App/、ReduxStore/、Theme/、Hotkeys/、Toaster/、PromptVariables/ 等目录,分别负责应用生命周期、全局状态、主题、快捷键、轻提示与交互式变量输入。
六、核心数据模型类型(bruno-schema-types)
核心类型集中在 bruno-schema-types/src/,包内只有类型定义(build 仅执行 tsc),供全仓库共享。理解 Bruno 数据模型必须先掌握以下事实:
- 请求是一个联合类型:
Request = HttpRequest | GrpcRequest | WebSocketRequest(requests/index.ts)。任何处理"一个请求"的代码都必须同时考虑这三种形态——这正是 Bruno 相比纯 HTTP 客户端在类型层面的关键约束。 - 其他中心类型:
Collection(collection/collection.ts)、Item(请求或文件夹,以type字段判别,collection/item.ts)、Auth(以mode为判别字段的联合类型,common/auth.ts)、KeyValue(headers/params/assertions 的通用键值对,common/key-value.ts)、Environment、Script。
运行时校验则由独立的 bruno-schema(Yup)承担——"类型定义"与"运行时校验"分属两个包,这一分工是所有权边界的一部分。
七、关键依赖版本:以当前 package.json 为准
架构文档特别提醒:多个核心依赖被钉在低于最新的大版本上,不要假设它们是最新 API。以下版本已逐一在当前仓库各 package.json 中核实(适用前提:以本次仓库快照为准):
前端(bruno-app,见 packages/bruno-app/package.json)
| 依赖 | 当前版本 | 注意 |
|---|---|---|
| react / react-dom | 19.0.0 |
— |
| @reduxjs/toolkit | ^1.8.0 |
v1,不是 v2 |
| react-redux | ^7.2.9 |
v7 |
| styled-components | ^5.3.3 |
不是 v6 |
| codemirror | 5.65.2 |
CodeMirror 5,不是 scoped 的 @codemirror/* |
| tailwindcss | ^3.4.1 |
v3 |
| @rsbuild/core | ^1.7.6 |
devDependency |
桌面端(bruno-electron,见 packages/bruno-electron/package.json)
| 依赖 | 当前版本 |
|---|---|
| electron | ~37.6.1(devDependency) |
| electron-builder | ^24.13.3(devDependency) |
| chokidar | ^3.5.3 |
| @grpc/grpc-js | ^1.14.4 |
| js-yaml | 4.3.1 |
| electron-store | ^8.1.0 |
ws 是 bruno-requests / bruno-tests 的依赖,不是 bruno-electron 的直接依赖。
解析层(bruno-lang):arcsecond ^5(v1,遗留)、ohm-js ^16.6(v2,当前);bruno-toml 封装 @iarna/toml 但当前未被使用。
根 package.json 的硬钉(overrides):package.json 中 rollup 被钉在 3.30.0,axios 被钉在 1.18.0(另有 tar、pbkdf2 等安全相关固定)。在叶子包中提升这些版本号不会生效——必须改根级 override。
TypeScript 版本不统一:bruno-common 为 ^5.8.3,bruno-schema-types 为 ^5.0.0,而 bruno-converters / bruno-filestore / bruno-graphql-docs / bruno-query / bruno-requests 均为 ^4.8.4;根目录没有任何 TS 依赖。跨包编写类型代码时不能假设全局一致的 TS 语言特性。
八、测试资产:验证架构事实的入口
理解这套架构后,仓库中的测试资产是最直接的验证入口:
- packages/bruno-js/tests/:沙箱生命周期、QuickJS 陷阱隔离(
quickjs-trap-containment.spec.js)、teardown、脚本化条目等单测; - packages/bruno-electron/tests/:IPC 层测试(
network/目录覆盖请求准备、mock、代理等); - tests/ + playwright.config.ts:Playwright e2e,按项目分组运行(
test:e2e、test:e2e:ssl、test:e2e:auth、test:e2e:mock-server等脚本见根 package.json); - packages/bruno-tests/:express 测试服务器,支撑 auth、redirects、sse、graphql 等端到端场景。
结语
Bruno 的架构可以概括为一条清晰的主轴:bruno-app(渲染)与 bruno-electron(主进程)构成 Electron 双进程外壳,ipc/network 是请求编排的唯一中枢,bruno-requests 提供可复用构件,bruno-js 的四个独立 runtime 分别承担 pre-request、变量提取、测试与断言,bruno-filestore + bruno-lang 守护 .bru/.yml 双格式,bruno-schema-types 则以 HttpRequest | GrpcRequest | WebSocketRequest 三态联合类型约束全仓库的请求处理代码。开发时牢记三条护栏:编辑 7 个产 dist/ 的包后必须重新构建、依赖升级要改根级 override 而非叶子包、所有沙箱模式逻辑必须收敛到 getJsSandboxRuntime。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00