npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

evaengine

v1.0.10

Published

EvaEngine for Node.js, a micro service development engine

Readme

EvaEngine for Node.js

NPM version CI codecov npm License

面向 Node.js 微服务的 Application Runtime:同一套引擎覆盖 HTTPCLI定时任务,并提供 DI、Provider、中间件、配置、缓存、鉴权辅助、实体(Sequelize)、异常体系与 Swagger 生成。

消费方(人与 agent): 本 README 即为完整对外说明。使用本包不需要阅读仓库内的 docs/

环境要求

  • Node.js ≥ 24
  • ESM("type": "module"
  • npm(或其它可从 npm registry 安装的客户端)

安装

npm install evaengine

可选脚手架:EvaSkeleton.js

导入方式(重要)

包的 default 导出 是一个 core 对象。顶层具名导出只有 defaultcore(同一对象)。

import eva from 'evaengine';
// 或:import { core as eva } from 'evaengine';

const {
  EvaEngine,
  Command,
  DI,
  Entities,
  express,
  wrapper,
  services,
  middlewares,
  providers,
  exceptions,
  swagger,
  utils,
  commands,       // 内置 CLI 命令
  Joi,
  sequelize
} = eva;

不要依赖 import { EvaEngine } from 'evaengine'——该具名导出不存在。


心智模型

new EvaEngine(meta, mode?)
  → 绑定 base 服务(env, config, logger, namespace, now, event_manager)
  → bootstrap()                 # web 服务 + 中间件 Provider
  → use(...) / registerCommands
  → run() | runHttps() | runCLI() | runCrontab() | runCommand()

| 模式 | 典型流程 | |------|----------| | web(默认) | bootstrap()use()run() / runHttps() | | cli | registerCommands()runCLI() / runCrontab() / runCommand() |

进程级事实(按每进程一个 Engine 规划):

  • DI全局容器。
  • EvaEngine.getApp()模块级 Express app 单例。
  • bootstrap() 注册 web 服务与中间件 Provider;CLI 路径在 getCLI / runCrontab 内注册 CLI 服务。
  • 内置 EventManager 仅进程内(不是消息队列)。

快速开始

Web

import eva from 'evaengine';

const { EvaEngine, DI, wrapper, exceptions } = eva;
const { UnauthorizedException } = exceptions;

const engine = new EvaEngine({
  projectRoot: process.cwd(),
  port: Number(process.env.PORT) || 3000
  // configPath、sourceRoot 可选
});

engine.bootstrap();

// 可选横切中间件(须在 bootstrap 之后)
engine.use(DI.get('trace')());
// engine.use(DI.get('session')());
// engine.use(DI.get('auth')());

engine.use('/health', (req, res) => {
  res.json({ ok: true });
});

engine.use('/me', wrapper(async (req, res) => {
  if (!req.auth?.uid) {
    throw new UnauthorizedException('Login required');
  }
  res.json({ uid: req.auth.uid });
}));

engine.run();

CLI

import eva from 'evaengine';
import * as UserCommands from './commands/user.js';

const { EvaEngine } = eva;

const engine = new EvaEngine({ projectRoot: process.cwd() }, 'cli');
engine.registerCommands(UserCommands);
await engine.runCLI();
// node app.js user:create --name=Ada

定时任务

import eva from 'evaengine';
import * as Jobs from './commands/jobs.js';

const { EvaEngine } = eva;

const engine = new EvaEngine({ projectRoot: process.cwd() }, 'cli');
engine.registerCommands([Jobs]);
// 六段 cron(含秒)等细节见 runCrontab 第三参数 useSeconds
engine.runCrontab('0/10 * * * * *', 'hello:world --id=EvaEngine');

内置 CLI 二进制

npx engine
npx engine make:entity
npx engine make:dbview
npx engine make:graphql
npx engine tramp:dump-config

配合 Spring Cloud Config(仅 bin):

  • SPRING_CONFIG_ENDPOINT(设置后启用)
  • SPRING_CONFIG_NAMESPRING_CONFIG_PROFILESSPRING_CONFIG_LABEL

推荐项目结构

project/
  package.json          # "type": "module"
  config/
    config.default.cjs
    config.development.cjs
    config.production.cjs
    config.local.development.cjs   # 本地覆盖,建议 gitignore
  src/
    app.js              # web 入口
    cli.js
    commands/
    entities/
    routes/
  test/

配置

配置目录为 {projectRoot}/config(可用构造参数 configPath 覆盖),按以下顺序合并:

  1. 引擎内置默认(随包提供)
  2. config.default.cjs
  3. config.<NODE_ENV>.cjs
  4. 可选 config.local.<NODE_ENV>.cjs(不存在则忽略)

配置文件使用 CommonJS .cjs(经 require 加载)。

// config/config.default.cjs
module.exports = {
  app: { name: 'my-service' },
  redis: { host: '127.0.0.1', port: 6379, lazyConnect: true },
  cache: { prefix: 'myapp', driver: 'redis' },
  token: {
    secret: process.env.TOKEN_SECRET || '',
    provider: undefined, // 设为 'kong' 时使用 Kong JWT 与对应 auth 中间件
    faker: { enable: false, key: 'eva', uid: 1 }
  },
  session: {
    secret: process.env.SESSION_SECRET || 'change-me',
    resave: true,
    saveUninitialized: true,
    cookie: { path: '/', httpOnly: true, secure: false, maxAge: 3600_000 }
  },
  db: {
    dialect: 'mysql',
    port: 3306,
    database: '',
    replication: {
      write: { host: '', username: '', password: '', pool: {} },
      read: []
    }
  }
};

运行时读取:

const config = DI.get('config');
config.get('redis.host');
config.get(); // 完整对象

环境变量

| 变量 | 作用 | |------|------| | NODE_ENV | 选择 config.<env>.cjs | | PORT | 常见应用端口(使用时传入构造参数) | | LOG_LEVEL | 覆盖日志级别 | | TZ | moment 默认时区(未设置时为 Asia/Shanghai) | | CLI_NAME | CLI 模式下 logger 标签 | | MAX_REQUEST_DEBUG_BODY | debug 中间件 body 限制 | | SEQUELIZE_REPLICATION_CONFIG_KEY | db 下 replication 配置的替代键名 | | SPRING_CONFIG_* | bin 远程配置(见上文) |


DI 与服务

DI.get('logger').info('hello');
DI.get('redis').getInstance();
DI.get('cache'); // 缓存门面
DI.get('jwt');
DI.get('http_client');
DI.get('rest_client');
DI.get('event_manager');
DI.get('namespace');
DI.get('now');
DI.get('env');
DI.get('validator_base');

| DI 名 | 绑定时机 | |-------|----------| | envconfigloggernamespacenowevent_manager | 构造时(base) | | rediscachehttp_clientrest_clientvalidator_basejwt | bootstrap()(web)或 CLI 执行路径 | | 下文中间件名 | bootstrap() |

自定义 Provider:

import eva from 'evaengine';

const { DI, providers } = eva;
const { ServiceProvider } = providers.services;

class MyApiProvider extends ServiceProvider {
  get name() { return 'my_api'; }
  register() {
    DI.bindValue(this.name, { ping: () => 'pong' });
  }
}

engine.registerService(MyApiProvider);
// 或替换整表:
// EvaEngine.setServiceProvidersForWeb([...EvaEngine.getServiceProvidersForWeb(), MyApiProvider]);

测试辅助:DI.reset()DI.registerMockedProviders(providers, configPath)DI.bindClass / bindValue / bindMethod


中间件

bootstrap() 之后按名称绑定工厂。需要调用工厂(注意部分场景二次调用):

engine.use(DI.get('trace')());
engine.use(DI.get('session')());
engine.use(DI.get('auth')());
// validator 是高阶工厂:
engine.use('/items', DI.get('validator')(() => ({
  query: eva.Joi.object({ page: eva.Joi.number().integer().required() })
})), handler);

| 名称 | 作用 | |------|------| | session | express-session(经 connect-redis 的 Redis 存储) | | auth | 从 X-Tokenapi_key 取 JWT,或 session uid;可选 faker token | | trace | 请求追踪(与 namespace 协作) | | validator | Joi 请求校验 | | view_cache | 响应缓存辅助 | | debug | 调试输出 |

config.token.provider === 'kong' 时,jwt 服务与 auth 中间件均切换为 Kong 实现。

使用 wrapper(async (req,res) => …),以便抛出的 exceptions.* 进入默认错误处理器。


命令(Command)

import eva from 'evaengine';

const { Command, DI } = eva;

export class HelloWorld extends Command {
  static getName() { return 'hello:world'; }
  static getDescription() { return 'Say hello'; }
  static getSpec() {
    return {
      id: { type: 'string', description: 'Who to greet' }
    };
  }
  async run() {
    const { id = 'world' } = this.getOptions();
    DI.get('logger').info(`Hello ${id}`);
  }
}

通过 engine.registerCommands(moduleExports) 或模块数组注册。名称来自 getName()

Engine API:runCLI()runCommand('name --flag=1')runCrontab(expression, 'name --flag=1', useSeconds?)clearCommands()clearCrontabs()


实体(Sequelize)

import path from 'path';
import eva from 'evaengine';

const { Entities, DI } = eva;

const entities = new Entities(path.join(process.cwd(), 'src/entities'));
entities.init(); // 按 config.db 构建 Sequelize 并扫描目录

const User = entities.get('user');
const all = entities.getAll();
await entities.getTransaction(async (t) => { /* … */ });

实体文件(经 require 加载的 CJS 或 ESM 工厂):

// src/entities/user.cjs
module.exports = (sequelize, DataTypes) =>
  sequelize.define('user', {
    id: { type: DataTypes.INTEGER.UNSIGNED, primaryKey: true, autoIncrement: true },
    name: { type: DataTypes.STRING, allowNull: false }
  }, { tableName: 'users' });

异常

import eva from 'evaengine';

const {
  StandardException,
  LogicException,
  InvalidArgumentException,
  UnauthorizedException,
  ResourceNotFoundException,
  RuntimeException
  // …完整列表见包导出 exceptions
} = eva.exceptions;

默认 HTTP 错误处理器在 run / runHttps 时挂载:将 StandardException 子类映射为 JSON 与状态码;生产环境会剥离 stack 等细节。


Swagger

使用 eva.swaggerExSwagger、注解辅助等)从源码注释与模型生成 Swagger 2.0。在应用脚本中自行接入生成流程;UI 资源来自依赖 swagger-ui-dist


EvaEngine API 一览

constructor({ projectRoot, configPath?, sourceRoot?, port?, config?, logger?, namespace? }, mode?='web')
getMeta() getDI()
bootstrap() use(...args) run(port?) runHttps(port?, options?) getServer()
registerCommands(commands) getCommands() clearCommands() getCommand() getCommandName()
runCLI(name?) runCommand(commandString) runCrontab(seq, commandString, useSeconds?) clearCrontabs()
registerServiceProviders(providers) registerService(ProviderClass)
setDefaultErrorHandler / getDefaultErrorHandler
setUncaughtExceptionHandler / getUncaughtExceptionHandler
setServerErrorHandler / getServerErrorHandler
static getApp() createRouter() getVersion()
static get/set BaseServiceProviders | ServiceProvidersForWeb | ServiceProvidersForCLI | MiddlewareProviders

本库不是什么

  • 不只是 Express 薄封装——HTTP 只是入口之一
  • 不是业务领域框架或业务规则层
  • 不是消息总线(可靠投递请用真正的 MQ)
  • 不是完整 ORM 产品——仅提供 Sequelize 集成辅助

本仓库开发

git clone https://github.com/EvaEngine/EvaEngine.js.git
cd EvaEngine.js
npm install
npm run lint
npm run build
npm test          # 部分测试需要本机 Redis 127.0.0.1:6379

发版:在 main 上由 semantic-release(Conventional Commits)执行——仅发布 npm,不创建 GitHub Release。维护者文档在 git 的 docs/ 下,npm 消费方无需阅读