@junsma/nivo
| Install | |
|---|---|
npm install @junsma/nivo |
|
| Latest Version: | 2.0.0 |
| Vue: | 3 |
| License: | ISC |
| Last Updated: | Oct 4, 2026 |
| Links: | Repository · npm |
nivo
一个基于 NestJS + Vue 3 + Vite 的企业级中后台全栈应用框架(npm 包名
@junsma/nivo)。核心思路是:用一份 JSON Schema 描述业务,前端自动渲染出表格 / 搜索栏 / 表单 / 详情面板,后端按目录约定自动挂载接口与页面——即 DSL 驱动的低代码开发范式;同时内置一套可上线的 Agent Runtime(LangGraph Agent Loop / MCP / RAG / 记忆 / 护栏 / Trace / SSE 流式)。
特性
- Schema DSL 驱动 CRUD —— 一份
schema配置 + 若干xxxOption节点,同时驱动表格、搜索栏、表单、详情面板 - model + project 继承 —— 一份 model 模板 + 多个 project 覆盖,按
key智能合并,多项目复用 - 双进程架构 —— Vite Dev Server(:9002)出脚本与 HMR,Nest(:8080)读页面、渲染
.tpl、供接口 - 统一请求治理 —— 签名(445)/ 参数校验(442)/ 项目头(446)/ 异常兜底(50000)全局管道
- 业务扩展点 —— 业务 Nest Module 动态扫描注入 + 前端
$business*别名替换,业务无需改框架代码 - Agent Runtime —— LangGraph Agent 循环、多模型网关、Skill / Tool / MCP / RAG / 记忆 / 护栏 / Trace / Eval,见Agent Runtime
环境要求
- Node.js ≥ 20.19(或 22.12+)
- 下文命令以 Windows CMD 为例
快速开始
1. 安装框架仓
cd /d D:\path\to\nivo
npm install
npm run build :: 编译 Nest → dist/;index.js 依赖 dist/main.js
若本机 nest CLI 执行后无输出,可用
npx tsc -p tsconfig.build.json等价编译。
开发改 TypeScript 时可开监听编译:
npm run build:watch
2. 在业务项目中安装
在 业务仓 根目录:
npm install @junsma/nivo
未发布到 npm 时,可在业务仓 package.json 用 Git 等方式声明依赖后执行 npm install。
3. 本地联调(npm link)
改框架源码时,让业务仓指向本机 nivo 目录:
:: 1. 框架仓:安装依赖并编译,再注册全局 link
cd /d D:\path\to\nivo
npm install
npm run build :: 或另开终端 npm run build:watch
npm link
:: 2. 业务仓
cd /d D:\path\to\nivo-demo
npm link @junsma/nivo
改完 app/**/*.ts 后需重新 npm run build(或保持 build:watch),业务仓重启 Nest 进程后生效。
取消 link:
cd /d D:\path\to\nivo-demo
npm unlink @junsma/nivo
npm install
cd /d D:\path\to\nivo
npm unlink :: 可选
4. 双终端启动(开发)
在业务仓开两个终端:
:: 终端 A:Vite Dev Server,出脚本 / HMR
npm run build:dev
:: → http://127.0.0.1:9002
:: 终端 B:Nest 读页面、渲染 .tpl
npm run dev
:: → http://127.0.0.1:8080
5. 生产构建
npm run build:prod :: 产物落 app/public/dist/prod,并写 entry.*.tpl
npm run prod :: Nest 8080 读 /dist/prod/
服务端启动
const { serverStart } = require('@junsma/nivo');
// 启动 nivo 服务
const app = serverStart({
name: 'nivo-demo', // 项目名
homePage: '/view/project-list', // 兜底重定向路径
});
前端构建
const { frontendBuild } = require('@junsma/nivo');
// local → Vite Dev Server :9002;production → Vite build → app/public/dist/prod
frontendBuild(process.env._ENV);
环境变量 _ENV 取值:local / beta / production。
架构总览
index.js # 对外入口
├─ frontendBuild(env) ──► app/vite/dev.js | prod.js ──► vite.base.js
│ │ # 扫描两棵 entry.*.js 树(框架 app/pages + 业务 cwd/app/pages)
│ │ # merge 后进 rollupOptions.input
│ └─ nivo-tpl-plugin # dev:写 9002 绝对 URL 的 entry.*.tpl
│ # prod:写构建产物路径的 .tpl + /dist/prod 静态资源
└─ serverStart(options) ──► dist/main.js bootstrap()
├─ NestFactory.create(AppModule.register(options))
│ ├─ ConfigModule.register(options) # 框架默认 → 业务 default → 业务 {local,beta,prod} 深合并
│ └─ NivoModule.register() # Extend / Project / View / Ai
│ # + 扫描业务 dist/modules/**/*.module.js
│ # + Fallback 兜底路由(最后注册)
├─ 全局管道四件套(见下表)
└─ app.listen(8080) # 可用 process.env.PORT 覆盖
全局请求管道
| 组件 | 规则 | 失败返回 |
|---|---|---|
| ApiSignGuard | 所有 /api/* 必须带头 s_t(毫秒时间戳,10 分钟有效)+ s_sign = md5(signKey_s_t);apiSignVerify.whiteList 可豁免 |
{success:false, code:445} |
| 参数校验管道 | DTO / 参数按 ajv 风格校验 | {code:442, message:'request validate fail: ...'} |
| ProjKeyInterceptor | /api/proj/* 路由要求头 proj_key,注入 request.projKey |
{code:446, message:'proj_key not found'} |
| AllExceptionsFilter | 一切异常转 HTTP 200 + 业务码;template not found 302 回 homePage |
{code:50000} |
内置路由
| 路由 | 说明 |
|---|---|
GET /api/project |
按 proj_key 获取项目配置 |
GET /api/project/list |
项目列表(可传 proj_key 限定同模型) |
GET /api/project/model_list |
所有模型与项目的结构化数据 |
GET /view/:page |
渲染前端页面(nunjucks 渲染 .tpl) |
目录导览
| 路径 | 说明 |
|---|---|
app/main.ts |
Nest bootstrap 入口,编译到 dist/main.js |
app/app.module.ts |
根 Module:ConfigModule + NivoModule.register() |
app/nivo.module.ts |
框架 Module 聚合:Extend / Project / View / Ai / 业务扫描 / Fallback |
app/common/ |
Guard / Pipe / Filter / Interceptor / 静态兜底 |
app/config/ |
配置 Module(深合并 + a.b.c 点路径取值) |
app/extend/ |
Database(knex)/ Logger(log4js)Provider |
app/modules/ |
框架内置 Module(project、view) |
app/ai/ |
Agent Runtime(见Agent Runtime) |
app/pages/ |
前端页面入口与组件(Vue 3) |
app/vite/ |
Vite 构建器(dev / prod) |
app/public/ |
框架静态资源 + 前端构建产物 dist |
app/view/ |
.tpl 模板 |
app/types/ |
类型契约(express Request.projKey 增强) |
config/ |
框架默认配置 |
model/ |
model 加载器(index.js) |
dist/ |
Nest 编译产物 |
index.js |
对外入口:serverStart / frontendBuild |
nest.js |
Nest 公共导出(BaseController、ConfigService 等) |
test/ |
框架回归测试 |
数据模型:model + project
model/ 目录下(除加载器 index.js 外)的每个模型文件描述一套业务模板,index.js 自动扫描并支持深继承:
model/
└── buiness/ # 按业务域组织,目录名任意
├── model.js # 模型模板(通用结构)
└── project/
├── taobao.js # 项目 A:覆盖 model
└── jd.js # 项目 B:覆盖 model
业务项目的 model / project 放在业务仓自身 process.cwd()/model 下,框架按 cwd/model/** 扫描并缓存。
继承合并规则
project 以 model 为底,深合并(数组按 key 递归合并):
- model 有、project 没有 → 保留 model 配置
- model 有、project 有 → 用 project 递归覆盖
- model 没有、project 有 → 追加新增配置
model 配置结构
{
mode: 'dashboard', // 模版类型,不同模版类型对应不一样的模版数据结构
name: '', // 名称
desc: '', // 描述
icon: '', // icon
homePage: '', // 首页(项目配置)
// 头部菜单
menu: [{
key: '', // 菜单唯一描述,
name: '', // 菜单名称
menuType: '', // 枚举值:group / module
// 当 menuType == group 时,可填
subMenu: [{
// 可递归 menuItem
}, ...],
// 当 menuType == module 时,可填
moduleType: '', // 枚举值:sider/iframe/custom/schema
// 当 moduleType == sider 时
siderConfig: {
menu: [{
// 可递归 menuItem(除 moduleType === sider)
}, ...]
},
// 当 moduleType == iframe 时
iframeConfig: {
path: '', // iframe 路径
},
// 当 moduleType == custom 时
customConfig: {
path: '', // 自定义路由路径
},
// 当 moduleType == schema 时
schemaConfig: {
api: '', // 数据源API(遵循 RESTFUL 规范)
schema: { // 板块数据结构
type: 'object',
properties: {
key: {
...schema, // 标准 schema 配置
type: '', // 字段类型
label: '', // 字段的中文名
// 字段在 table 中的相关配置
tableOption: {
...elTableColumnConfig, // 标准 el-table-column 配置
toFixed: 0, // 保留小数点后几位
visible: true, // 默认为 true(false 时,表示不在表单中显示)
},
// 字段在 search-bar 中的相关配置
searchOption: {
...eleComponentConfig, // 标准 el-component-column 配置
comType: '', // 配置组件类型 input/select/.....
default: '', // 默认值
// comType === 'select'
enumList: [], // 下拉框可选项
// comType === 'dynamicSelect'
api: ''
},
// 字段在不同动态 component 中的相关配置,前缀对应 componentConfig 中的键值
// 如:componentConfig.createForm,这里对应 createFormOption
// 字段在 createForm 中相关配置
createFormOption: {
...eleComponentConfig, // 标准 el-component 配置
comType: '', // 控件类型 input/selct/input-number
visible: true, // 是否展示 (true/false),默认为 true
disabled: false, // 是否禁用 (true/false),默认为 false
default: '', // 默认值
// comType === 'select' 时生效
enumList: [] // 枚举列表
},
// 字段在 editForm 表单中的相关配置
editFormOption: {
...eleComponentConfig, // 标准 el-component 配置
comType: '', // 控件类型 input/selct/input-number
visible: true, // 是否展示 (true/false),默认为 true
disabled: false, // 是否禁用 (true/false),默认为 false
default: '', // 默认值
// comType === 'select' 时生效
enumList: [] // 枚举列表
},
detailPanelOption: {
...eleComponentConfig // 标准 el-component 配置
}
},
...
},
required: [], // 标记哪些字段是必填项
},
// table 相关配置
tableConfig: {
headerButtons: [{
label: '', // 按钮中文名
eventKey: '', // 按钮事件名
// 按钮事件具体配置
eventOption: {
// 当 eventKey === 'showComponent'
comName: '' // 组件名称
},
...elButtonConfig // 标准 el-button 配置
}, ...],
rowButtons: [{
label: '', // 按钮中文名
eventKey: '', // 按钮事件名
eventOption: {
// 当 eventKey === 'showComponent'
comName: '' // 组件名称
// 当 eventKey === 'remove'
params: {
// paramKey = 参数的键值
// rowValueKey = 参数值, 格式为 schema::tableKey,到 table 中找相应的字段
paramKey: rowValueKey
}
}, // 按钮事件具体配置
...elButtonConfig // 标准 el-button 配置
}, ...]
},
// search-bar 相关配置
searchConfig: {},
// 动态组件 相关配置
componentConfig: {
// create-form 表单相关配置
createForm: {
title: '', // 表单标题
saveBtnText: '', // 保存按钮文案
},
// edit-form 表单相关配置
editForm: {
mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
title: '', // 表单标题
saveBtnText: '', // 保存按钮文案
},
// detail-panel 相关配置
detailPanel: {
mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
title: '', // 表单标题
}
// ...支持用户动态扩展
}
}
}, ...]
}
字段选项前缀规则
字段某个 xxxOption 节点的前缀,决定它出现在哪个动态组件里(渲染时按 ${comName}Option 提取;schema 顶层 required 数组会映射为对应控件的必填校验):
| 前缀 | 组件 |
|---|---|
tableOption |
表格列 |
searchOption |
搜索栏 |
createFormOption |
新增表单 |
editFormOption |
编辑表单 |
detailPanelOption |
详情面板 |
按钮事件
eventKey: "showComponent"+eventOption.comName→ 调起对应动态组件eventKey: "remove"+eventOption.params→ 删除确认(参数值schema::表格字段表示运行时取该行对应字段值)- 动态组件保存 / 删除成功后,通过
command事件通知表格刷新列表
Agent Runtime(app/ai/)
框架内置一层可上线的 Agent Runtime:框架提供运行时、协议与扩展点,业务语义(查什么数据、接什么语料、接哪个模型)全部由业务仓注入,不把业务逻辑写进框架。
| 能力 | 说明 |
|---|---|
| Agent Loop | LangGraph 编排 plan → execute → observe 三节点循环,支持多轮 tool call 与委派 |
| 多模型网关 | OpenAI 兼容 Provider(baseURL / apiKey / model 走配置,改一行换厂商);未内置的模型可实现 LlmProvider 注册进注册表 |
| Skill / Tool / Agent | 业务产物 dist/skills/*.js、dist/agents/*.js 按约定放置,框架启动时扫描注册;Agent 是纯声明,执行统一走 Orchestrator |
| 流式 | POST /api/ai/agent/run/stream 以 SSE 增量推送 step,done 事件与 JSON 端点同构(同样含 runId / output / steps) |
| MCP | 内置 JSON-RPC 客户端:业务 config 声明 MCP server(stdio 的 command + args 或远程 url),外部进程工具与内置 Skill 同形态 |
| RAG | 分块 / 向量化 / 检索管线;向量存储走 Store 接口(文件 / Qdrant 等可插拔),检索命中片段在 steps 中可见 |
| Memory | 会话 Thread + Checkpoint,追问自动带上文 |
| Guardrail | 输入输出拦截,拦截记录写入 Trace |
| Trace | runId 级步骤回放:plan、tool 参数与结果、Guardrail 拦截、RAG 命中片段 |
| Eval | 断言库,产出 pass/fail 报告 |
HTTP 接口
| 路由 | 说明 |
|---|---|
GET /api/ai/tool/list |
已注册 Tool |
GET /api/ai/agent/list |
已注册 Agent(纯声明) |
GET /api/ai/agent/graph |
Agent Loop 图结构 |
GET /api/ai/agent/config |
Agent 配置 |
GET /api/ai/trace/:runId |
按 runId 回放执行步骤 |
POST /api/ai/agent/run |
执行 Agent,返回 { runId, output, steps } |
POST /api/ai/agent/run/stream |
SSE 流式执行,done 与 JSON 端点同构 |
/api/ai/* 与业务 API 同等对待:走登录 + 验签,不进白名单。
接入方式
- 业务
config配置ai.llm(OpenAI 兼容:baseURL/apiKey/model),即可对话与执行 Agent - 业务把 Skill / Agent 文件按目录约定构建到
dist/skills/、dist/agents/,框架启动时扫描注册 - Trace / 记忆 / 向量一律走 Store 接口持久化,框架本身无状态(重启零残留);未注入 Store 时默认文件存储落在业务仓
.nivo/目录(trace/、memory/、vector/) - MCP server 在业务 config 中声明,外部进程工具在 steps 中与内置 Skill 同形态可见
前端
多入口工程
Vite 多入口:自动扫描框架 app/pages/**/entry.*.js 与业务 cwd/app/pages/**/entry.*.js 两棵树,merge 后进 rollupOptions.input。
| 环境 | 行为 |
|---|---|
| local | Vite Dev Server :9002,HMR 热更新,.tpl 实时落地 |
| production | 构建产物落 app/public/dist/prod,nivo-tpl-plugin 同步写 entry.*.tpl 供 Nest 渲染 |
页面模板注入运行时数据(projKey / env / name / options),前端从 window.* 全局读取;dashboard 页内路由支持 iframe / schema / sider / custom 四类模块与动态子组件。
别名体系
$nivo*(指向框架内部,业务页面可直接 import):
| 别名 | 指向 |
|---|---|
$nivoPages |
框架页面根目录 |
$nivoCommon |
框架请求层(curl 等) |
$nivoCurl / $nivoUtils |
请求封装 / 工具函数 |
$nivoWidgets |
通用组件(schema-table / schema-form / schema-search-bar / header / sider) |
$nivoStore |
Pinia store(menu / project) |
$nivoBoot |
Vue 启动器(ElementPlus + Pinia + Router) |
$business*(业务扩展位):业务文件存在则指向业务文件,不存在则回退框架的空实现(blank.js),不影响框架运行——业务无需改框架代码即可替换对应模块:
| 别名 | 业务放置文件 |
|---|---|
$businessDashboardRouterConfig |
app/pages/dashboard/router.js |
$businessComponentConfig |
app/pages/dashboard/complex-view/schema-view/components/components-config.js |
$businessFormItemConfig |
app/pages/widgets/schema-form/form-item-config.js |
$businessSearchItemConfig |
app/pages/widgets/schema-search-bar/search-item-config.js |
$bussinessHeaderConfig |
app/pages/widgets/header-container/header-config.js |
前端请求层
业务请求统一收敛到 app/pages/common/curl.js:
- 自动携带签名头(
s_t+s_sign),对/api/proj/*自动附加proj_key(取自模板注入的全局) - 按错误码(442 / 445 / 446 / 50000)统一提示,恒定 resolve
{ success, data | code, ... }——调用方只需判断res.success - schema-table 发起新列表请求前会取消上一次请求,丢弃过期响应,避免快速切页时旧数据覆盖新数据
扩展点一览
| 扩展点 | 业务放置文件 | 能力 |
|---|---|---|
| 自定义页面 | app/pages/entry.xxx.js |
新增页面,/view/xxx 访问 |
| dashboard 路由 | app/pages/dashboard/router.js |
追加路由($businessDashboardRouterConfig) |
| schema-view 动态组件 | .../schema-view/components/components-config.js |
新增动态子组件(create-form / edit-form / detail-panel 同级) |
| schema-form 控件 | app/pages/widgets/schema-form/form-item-config.js |
新增表单控件 |
| schema-search-bar 控件 | app/pages/widgets/schema-search-bar/search-item-config.js |
新增搜索控件 |
| 顶部栏配置 | app/pages/widgets/header-container/header-config.js |
覆盖 header 配置($bussinessHeaderConfig) |
| 服务端业务模块 | dist/modules/**/*.module.js |
业务 Nest Module 启动时动态注入(Fallback 兜底最后注册) |
自定义页面扩展
- 在
app/pages/目录下写入口 entry.xxx.js
dashboard / schema-view 动态组件扩展
- 在
app/pages/dashboard/complex-view/schema-view/components下写组件 - 配置到
components-config.js
schema-form / schema-search-bar 控件扩展
- 在对应
complex-view下写控件 - 配置到对应
form-item-config.js/search-item-config.js
测试与质量
npm test :: mocha 接口回归(会启动独立 Nest 实例,需先停掉占用 8080 的服务)
npm run lint :: ESLint(Vue + JS 规则)
License
Version History
| Version | Released | License |
|---|---|---|
| 2.0.0 | ISC | |
| 1.3.0 | ISC | |
| 1.2.0 | ISC | |
| 1.1.2 | ISC | |
| 1.1.1 | ISC | |
| 1.0.4 | ISC | |
| 1.0.3 | ISC |