@junsma/nivo

企业级中后台全栈框架(Nest + Vue3 + Vite),JSON Schema 驱动 CRUD,内置 Agent Runtime(MCP/RAG/流式)
1,353
Install
npm install @junsma/nivo
Latest Version:2.0.0
Vue:3
License:ISC
Last Updated:Oct 4, 2026
Links: Repository  ·  npm
Maintainer: junsma

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 动态组件扩展

  1. 在 app/pages/dashboard/complex-view/schema-view/components 下写组件
  2. 配置到 components-config.js

schema-form / schema-search-bar 控件扩展

  1. 在对应 complex-view 下写控件
  2. 配置到对应 form-item-config.js / search-item-config.js

测试与质量

npm test      :: mocha 接口回归(会启动独立 Nest 实例,需先停掉占用 8080 的服务)
npm run lint  :: ESLint(Vue + JS 规则)

License

ISC

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