vellio-nestjs
v0.1.0-rc.1
Published
Vellio Server SDK — NestJS implementation (vellio-nestjs)
Maintainers
Readme
vellio-nestjs
Vellio Server SDK — NestJS 实现。在你的业务服务中接入 JWT 门禁、配额预检、用量上报、配置同步 四项核心能力。
当前版本 0.1.0-rc.1 处于 Release Candidate 阶段,API 形态已对齐 SERVER_SDK.md 设计规范,欢迎对接联调;正式 GA 前可能仍有小范围调整。
前置要求
| 依赖 | 版本 | 说明 |
| ---------------- | ------------------------------ | ------------------------------------------------- |
| Node.js | >=20 | NestJS 11 官方最低要求 |
| NestJS | ^11.0.0 | DynamicModule 模式 |
| Redis | >=6(建议 7.x) | 用于 JWKS / Entitlement 快照 / Reserve 锁 |
| Control Plane | 由 Vellio 提供 controlPlaneUrl | 用于 M2M Token 换取、WS 配置同步、用量上报 |
| TypeScript 配置 | emitDecoratorMetadata: true + experimentalDecorators: true | NestJS DI 必须 |
Peer dependencies(需在宿主项目自行安装):
pnpm add @nestjs/common @nestjs/core ioredis rxjs ws安装
正式发布前以 tarball 方式分发:
# 由 Vellio 团队提供 vellio-nestjs-0.1.0-rc.1.tgz
pnpm add file:./vellio-nestjs-0.1.0-rc.1.tgz快速开始
1. 注册模块
同步配置:
// app.module.ts
import { Module } from '@nestjs/common';
import { VellioServerModule } from 'vellio-nestjs';
@Module({
imports: [
VellioServerModule.forRoot({
controlPlaneUrl: process.env.VELLIO_CONTROL_PLANE_URL!,
projectId: process.env.VELLIO_PROJECT_ID!,
clientSecret: process.env.VELLIO_CLIENT_SECRET!,
redis: process.env.REDIS_URL!, // 或 ioredis 配置对象
}),
],
})
export class AppModule {}异步配置(推荐,配合 @nestjs/config):
import { ConfigModule, ConfigService } from '@nestjs/config';
VellioServerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
controlPlaneUrl: config.getOrThrow('VELLIO_CONTROL_PLANE_URL'),
projectId: config.getOrThrow('VELLIO_PROJECT_ID'),
clientSecret: config.getOrThrow('VELLIO_CLIENT_SECRET'),
redis: config.getOrThrow('REDIS_URL'),
failOpen: false, // 高风险场景:Control Plane 断连时拒绝请求
}),
});VellioServerModule 是 @Global() 模块,注册一次即可在全项目注入。
2. 在路由上挂载门禁(推荐用法)
@VellioGuard 自动完成 JWT 验签 → Reserve(预扣)→ Confirm(请求成功后异步上报)/ Rollback(请求失败时同步回滚):
import { Controller, Post, Body } from '@nestjs/common';
import {
VellioGuard,
VellioContext,
type VellioRequestContext,
} from 'vellio-nestjs';
@Controller('ai')
export class AiController {
@Post('generate')
@VellioGuard({
capability: 'ai.text.tokens',
estimatedAmount: (req) => req.body.maxTokens, // 动态预估
})
async generate(
@Body() dto: { maxTokens: number; prompt: string },
@VellioContext() ctx: VellioRequestContext,
) {
// ctx.customerId / ctx.projectId 已由 SDK 自动注入
return { ok: true, customer: ctx.customerId };
}
}请求方需在 Authorization: Bearer <JWT> 头中携带 Vellio 签发的 Access Token。
3. 手动调用模式(gRPC、消息队列、后台任务)
不走 HTTP 路由的场景,直接注入 VellioServerSDKService:
import { Injectable } from '@nestjs/common';
import { VellioServerSDKService } from 'vellio-nestjs';
import { uuidv7 } from 'uuidv7';
@Injectable()
export class BatchJobService {
constructor(private readonly vellio: VellioServerSDKService) {}
async processJob(customerId: string, tokensEstimate: number) {
const usageEventId = uuidv7();
const reservation = await this.vellio.reserve({
customerId,
capabilityId: 'ai.text.tokens',
estimatedAmount: tokensEstimate,
idempotencyKey: usageEventId,
});
if (reservation.blocked) {
throw new Error('quota exceeded');
}
try {
const actualTokens = await this.runActualWork();
await this.vellio.confirm({ usageEventId, actualAmount: actualTokens });
} catch (err) {
await this.vellio.rollback({ usageEventId, reason: String(err) });
throw err;
}
}
}配置项参考
VellioServerOptions(来自 src/interfaces/vellio-server-options.interface.ts):
| 字段 | 类型 | 默认值 | 说明 |
| -------------------- | ------------------------- | ------- | --------------------------------------------- |
| controlPlaneUrl | string | — | 必填。Vellio Control Plane 的 URL |
| projectId | string | — | 必填。Vellio 项目 ID |
| clientSecret | string | — | 必填。M2M 客户端密钥 |
| redis | RedisOptions \| string | — | 必填。Redis 连接字符串或 ioredis 选项 |
| failOpen | boolean | true | Control Plane 断连时是否放行请求 |
| reportBatchSize | number | 50 | 用量批量上报阈值 |
| reportIntervalMs | number | 1000 | 用量批量上报时间间隔(ms) |
| reportQueueMaxSize | number | 10000 | 内存上报队列上限 |
| shutdownTimeoutMs | number | 5000 | Graceful shutdown 最大等待时间(ms) |
@VellioGuard 选项
| 字段 | 类型 | 默认 | 说明 |
| --------------------- | ---------------------------------------------- | ----- | ----------------------------------------------------------------------- |
| capability | string | — | 必填。要门禁的 capability ID(如 ai.text.tokens) |
| estimatedAmount | number \| string \| (req) => number \| string | — | 预估用量;未传则使用 Capability 的 commercial_increment |
| streamMode | boolean | false | SSE / WebSocket 业务流模式 |
| streamFlushInterval | number | 500 | Streaming 模式下局部 Confirm 间隔(ms) |
生命周期事件
注入 VellioServerSDKService 后通过 .on() 订阅:
constructor(private readonly vellio: VellioServerSDKService) {
this.vellio
.on('ready', () => logger.log('Vellio SDK ready'))
.on('degraded', () => logger.warn('Control Plane disconnected, running degraded'))
.on('quotaExceeded', ({ customerId, capabilityId, remainingBalance }) => {
logger.warn(`quota exceeded: ${customerId} / ${capabilityId} (${remainingBalance})`);
})
.on('reportFailed', ({ events, error }) => {
logger.error(`usage report failed: ${events.length} events`, error);
})
.on('queuePressure', ({ queueSize, maxSize }) => {
logger.warn(`report queue pressure: ${queueSize}/${maxSize}`);
});
}事件全集:ready · degraded · startupError · authError · customerReady · entitlementChanged · quotaExceeded · reportFailed · queuePressure · queueFull · capabilityDiscovery · shutdown
Graceful Shutdown
VellioServerModule 已实现 OnApplicationShutdown,无需额外接线。请确保宿主应用启用了 NestJS shutdown hooks:
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // 必须,否则 SIGTERM 时上报队列可能丢失
await app.listen(3000);关闭流程:
- 等待进行中的 Reserve→Confirm/Rollback(最多
shutdownTimeoutMs) - Flush 内存中的上报队列
- 超时未 flush 的事件写入
/tmp/vellio-dlq-{timestamp}.json
已知限制
rollback()必须同步调用,不得改为异步队列(设计约束 G-05)usageEventId由 SDK 在reserve()时生成;confirm()/rollback()不重新生成,重试用同一个 ID- 所有金额相关的字段全程使用
Decimal.js,禁止用number直接做余额计算 failOpen: true时 Control Plane 断连会放行请求,不要在金融/计费强一致场景使用此默认值
反馈渠道
对接过程中遇到的问题、API 不便之处、设计建议,请联系 Vellio 团队。RC 阶段欢迎深度反馈。
