@elysiumjs/分数
Elysium.js框架的核心职能.
页:1
这个软件包是Elysium.js框架的一部分,专为Bun runtime而建.
安装
bun install @elysiumjs/core
特征
- 依赖性注射
- HTTP 控制器
- WebSocket 支持
- WAMP 协议集成
- 命令行接口( E)
@elysiumjs/styx),支持自定义命令 - 生命周期事件
- 缓存(记忆和再生)
- 用 Drizzle ORM 支持 PostgreSQL 数据库
- 数据库互动模式类别和储存库
- 启用多重租赁支持
- OpenAPI 文档( Scalar 或 Swagger UI)
使用量
创建控制器类 :
import { Http } from '@elysiumjs/core';
@Http.controller('/users')
export class UserController {
@Http.get('/:id')
public getUser(id: string) {
return { id };
}
}
创建模块类 。 一个模块是唯一在 Elysium.js app 中注册任何东西的方法: 控制器、命令、工作和服务都由拥有它们的模块宣布.
import { Module } from '@elysiumjs/core';
import { UserController } from './controllers/http/user.controller';
import { UserService } from './services/user.service';
@Module.register({
services: [UserService],
controllers: [UserController]
})
export class MainModule extends Module {}
创建应用程序类 。 需要配置的模块被绑定在其中 with()数字 :
import { Application, RedisModule } from '@elysiumjs/core';
import { HermesModule } from '@elysiumjs/hermes';
import { MainModule } from './main.module';
@Application.register({
modules: [
HermesModule.with({ level: 'debug', format: 'pretty' }),
RedisModule.with({
default: 'cache',
connections: { cache: { url: process.env.REDIS_URL! } }
}),
MainModule
],
server: {
name: 'Elysium',
port: 3000
}
})
export class App extends Application {}
创建条目点文件 :
import { App } from './app';
await App.start();
启动服务器 :
bun run index.ts serve
或者,如果使用 styx,运行 :
bun styx serve
请求值
路由参数接收请求的部分. 在操作器运行和记录OpenAPI的路由之前,他们的计划验证了请求:
import { Http, t } from '@elysiumjs/core';
@Http.controller('/users')
export class UserController {
@Http.patch('/:id', { headers: t.Object({ 'if-match': t.Optional(t.String()) }) })
public update(
@Http.param('id') id: string,
@Http.body(t.Object({ name: t.String() })) body: { name: string },
@Http.query(t.Object({ notify: t.Optional(t.String()) })) query: { notify?: string },
@Http.header('x-tenant-id') tenant: string
) {
return { id, tenant, ...body };
}
}
@Http.param(), (中文(简体) ),@Http.body()财务报告和审定财务报表@Http.query()给出 URL 参数、正文和查询。 不符合其计划值的值得到422VALIDATION_FAILED问题.@Http.header()给一个标题,一个需要的字符串,除非一个计划另有说明。 这个headers选项声明处理器不自读的页眉。 缺少或缺陷头部得到400个BAD_REQUEST问题.
对策和问题
路由宣布其处理器结果的策略 response,以及它回答的问题 errors。这两个文件都记录在 OpenAPI 中.
import { DomainError, Http, t } from '@elysiumjs/core';
export class InsufficientFunds extends DomainError.for('INSUFFICIENT_FUNDS', 409, {
extensions: { missing: t.Integer() }
}) {}
const Wallet = t.Object({ id: t.String(), balance: t.Integer() });
@Http.controller('/wallets')
export class WalletController {
@Http.post('/:id/pay', { response: Wallet, errors: [InsufficientFunds] })
public pay(
@Http.param('id') id: string,
@Http.body(t.Object({ amount: t.Integer() })) body: { amount: number }
) {
if (body.amount > 100)
return new InsufficientFunds('Top up first.', { extensions: { missing: body.amount - 100 } });
return { id, balance: 100 - body.amount };
}
}
- 一个班级做的
DomainError.for(code, status, { extensions })有静态code, (中文(简体) ),status财务报告和审定财务报表schema。 。 。 .schema问题的细节被发送为: 代码和字面状态以及扩展成员; 构造者extensions从它们中打出. - 一个地位的问题被记载为一个工会。 路由给出的用于此状态的计划
response战胜他们. - 框架增加了自己的:422个
VALIDATION_FAILED当正文或查询被验证时(400与problems.validationStatus),400个BAD_REQUEST413页PAYLOAD_TOO_LARGE受身体限制的429人RATE_LIMITED规定费率限额的,500INTERNAL_ERROR在每一个路线. - 回答问题的中间软件、拦截器或验证器在静态中列出其类
errors,以及它们用来记录它们的路线. status设定成功响应的状况:@Http.post('/', { status: 201, response: Wallet })答复和文档Wallet在它下面,和@Http.del('/:id', { status: 204 })答无生身. 一个设置的处理器ctx.set.status保持它自己的.Http.currentContext()提供处理中请求的背景到处理器外的编码,例如服务,以及undefined在请求之外.
一个改变结果形状的截取器对此进行了描述,这样路由就可以宣布其处理器的结果. TransformInterceptor.with(transform, { schema }) 通过它所覆盖的路线的成功计划 schema,在顺序中,结果通过截取器(路由,控制器,模块,再应用). 新计划是记录、验证和序列的 schema 一定要离开什么 transform 单独离开:
const Envelope = TransformInterceptor.with(
(result) => (isEnveloped(result) ? result : { data: result }),
{ schema: (schema) => (isEnvelopedSchema(schema) ? schema : t.Object({ data: schema })) }
);
结果
可能失败的支票或电话返回 Result: 所传递的值,或 DomainError 失败了 处理器可能返回一个:一个通过的结果可以解答它的价值,一个失败的结果可以解答它的问题. AsyncResult 承诺的结果, 方法相同, 处理者可能还它.
import { AsyncResult, ForbiddenError, Result } from '@elysiumjs/core';
const mayBuy = (account: Account): Result<Buyer, ForbiddenError> =>
account.adult ? Result.pass({ id: account.id }) : Result.fail(new ForbiddenError('Adults only.'));
const buyer = mayBuy(account);
if (!buyer.check()) return buyer.error;
buyer.result.id;
return mayBuy(account).errorOr((buyer) => view.render(buyer));
const wallet = await AsyncResult.from(wallets.open(userId)).orThrow();
check()缩小结果 :result是过去方的值,error失败一方的问题.orThrow()给出数值或丢出错误,orError()给出值或错误,以及errorOr(transform)给出错误或已转换的值,因为处理器返回它.map(transform)转换值和andThen(next)链接下一个结果;失败会跳过两个结果.Result.of(value)财务报告和审定财务报表AsyncResult.from(promise)产生结果T | DomainError结合,保持结果不变.Result.is(value)显示来自其他值的结果.Result并不是一个可以实现的,所以等待本身就是结果。 等待一个AsyncResult给它Result。 。 。 .
环境
App及其模块宣布他们读取的环境变量为TypeBox对象. 当应用程序运行或构建时,它们会被验证,列出每个缺失或无效的变量,并与 Env.get()。一个宣布计划的文件也会增加 AppEnv,这样 Env.get() 了解每个名称及其类型 :
// src/env.ts
import type { Env } from '@elysiumjs/core';
import { Secret, t } from '@elysiumjs/core';
export const env = t.Object({
PORT: t.Number({ default: 3000 }),
DATABASE_URL: Secret.schema()
});
declare module '@elysiumjs/core' {
interface AppEnv extends Env.Infer<typeof env> {}
}
应用通过计划 env 选项和模块 envSchema 选项,在自己的文件中以同样方式声明。 然后说:
Env.get('PORT'); // number
Env.get('DATABASE_URL').reveal(); // string: a Secret prints as [secret] in logs and JSON
Env.get('PROT'); // compile error: not declared
- 一旦任何变量被宣布,
Env.get()只接受宣布的名字。 无所取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取出取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取. - 变量即使其模块在当前环境中不活动,也会被键入: 以
t.Optional()。 。 。 . - 在有计划的一个应用程序中,读取
Env在环境被验证之前EnvironmentNotReadyError读入onInit,服务,或with()工厂. - 运行时间
env()在验证前保留原始环境.
时间
决定时间的代码,如过期,时间表和记录的瞬间,从 Clock 改为 new Date(). Moirai, Heracles 时刻表和 Themis 符号, 会话和签名的 URL 都有. 测试和开发可以将其冻结或移动:
import { Clock } from '@elysiumjs/core';
Clock.now(); // a Date
Clock.epochMs(); // milliseconds since the epoch
Clock.freeze('2026-01-01T00:00:00Z');
Clock.travel(14 * 24 * 3600 * 1000); // 14 days later, frozen or running
Clock.reset();
freeze() 财务报告和审定财务报表 travel() 掷出 ClockAdjustmentRefusedError 外部 development 财务报告和审定财务报表 test。 。 。 . Clock.use(source) 从另一个地方抽出时间 ClockSource。只测量机器的持续时间,例如请求计时和锁定,保持实时.
服务
服务由其班级确定。 给它一个名字 @Service.register({ name }) 由绳子查出来 类名称从未作为标识符使用,因此服务在捆绑者重命名类后解决方式相同(bun run check:minified 建立一个精密的应用程序并检查它.
import { Service, ServiceScope } from '@elysiumjs/core';
@Service.register()
export class UserRepository {}
@Service.register({ name: 'mailer' })
export class MailerService {}
@Service.register()
export class UserService {
constructor(
@Service.inject(UserRepository) private readonly users: UserRepository,
@Service.inject('mailer') private readonly mailer: MailerService
) {}
}
@Service.inject() 参数类型类别,从 emitDecoratorMetadata。当类型是接口或缺失时,通过该类或名称;当Elysium无法告诉要注入哪个服务时,在声明时丢出.
一个服务有三个范围之一:
| 范围 | 实例 |
|---|---|
ServiceScope.SINGLETON | 一个用于应用( 默认) |
ServiceScope.FACTORY | 每一次解决一次新的 |
ServiceScope.REQUEST | 每个HTTP请求一个,由它的中间软件和处理器共享 |
请求范围服务仅在请求内解决:在别处解决 OutsideRequestScopeError。路线外的代码,如工作或测试,打开一个范围 Service.runInRequestScope(fn); 内部解决了每个请求范围服务 fn,包括在 await,用于该运行。 单通,或具有默认服务器范围的控制器,不能依赖于请求范围服务,因为它会保留第一个请求的例: Elysium srows ScopeMismatchError。使用请求范围控制器(scope: HttpControllerScope.REQUEST),或者在路由处理器参数中注入服务.
标记
标签组服务:每个注册在册的服务与其它服务一起注入. 用于模块促进的扩展点,例如健康检查或许可规则.
import { Service } from '@elysiumjs/core';
export const HealthChecks = Service.tag<HealthCheck>('health.checks');
@Service.register({ tags: [HealthChecks] })
export class DatabaseCheck implements HealthCheck {}
@Service.register()
export class Health {
constructor(@Service.injectAll(HealthChecks) private readonly checks: HealthCheck[]) {}
}
@Service.injectAll(tag) 财务报告和审定财务报表 Service.getAll(tag) 以注册顺序给出标记的服务,并在无标记时给出空数组; Service.resolveAll(tag) 也等着他们 onInit模块接收应用程序每个模块的标记服务,包括不导入的服务,同时按其类别获得其中之一仍然遵循模块的进出口。 范围适用于每一份贴有标记的服务,如同适用于 @Service.inject()。 。 。 .
延长点
当标签的每一项服务都带有自己的选项,例如名称和频道, Service.extension() 使标记、 注册服务及其选项的装饰器和浏览。 名称必须是现行服务中独一无二的: all() 掷出 DuplicateExtensionError 否则.
type TransportOptions = { name: string; channel: 'email' | 'sms'; devOnly?: boolean };
export const Transports = Service.extension<Transport, TransportOptions>(
'notifications.transports',
{
env: (options) => (options.devOnly ? Environments.local : undefined)
}
);
@Transports.register({ name: 'resend', channel: 'email' })
export class ResendTransport implements Transport {}
Transports.all(); // [{ service, options }], in registration order
Transports.find('resend'); // the service, or undefined
Transports.optionsOf(ResendTransport); // { name: 'resend', channel: 'email' }
Transports.tag 是标记,用于 @Service.injectAll()。 。 。 . settings.scope 财务报告和审定财务报表 settings.global 适用于延长点的每一项服务.
Service.invoke(target, method, args) 调用其参数使用 @Service.inject() 或者说 @Service.injectAll():每个参数接受其服务,在当前模块背景下解决,其他参数接受 args 以顺序.
Service.get() 答题 null 用于不可见的服务:保留用于可选服务。 对于需要代码的服务,请使用 Service.require(),即时返回,或 Service.resolve(),它等待其初始化:在缺失时,两者都投出精确错误.
背景工作情况
背景瞄准镜将每个单位的背景工作(一个任务,一个事件处理器,一个发箱处理器),截取器包装请求的方式:它们设定了工作一次需要的东西,例如一个配置或一个演员范围. 范围是注册在 @Background.scope(); 瞄准器以模块的顺序嵌入它们,这是第一个最外层的.
import type { BackgroundScope, BackgroundWork } from '@elysiumjs/core';
import { Background } from '@elysiumjs/core';
@Background.scope()
export class SystemActorScope implements BackgroundScope {
public run<T>(work: BackgroundWork, next: () => Promise<T>): Promise<T> {
return Actor.run(Actor.system(work.name), next);
}
}
work.kind 说什么跑job, (中文(简体) ), event 或者说 outbox),以及 work.name 说吧. Background.run(work, fn) 运行范围中的其他工作.
懒靴子
懒散的 CLI 命令靴: 正在运行 app:cmd 列出命令本身的模块及其导入的模块,而不是整个图表. help <cmd> 总是懒惰,即使是命令 static boot = true。 。 。 .
- 同步帮助是安全的 (单位:千美元)
Service.get(), (中文(简体) ),Database.getConnection(), (中文(简体) ),Redis.getConnection(),.),当拥有服务的模块时同步. Elysium 然后在当场发布该模块,即使命令没有导入它. - 使用
await Service.resolve(...)当拥有的模块有一个同步onInit同步get然后掷出ServiceNotReadyError,该模块的名称. - 使用
static boot = true在运行前需要每个模块(工人、服务器)的命令。 它将每个模块登录,并首先构建每个服务. - 服务工厂接收同步
get; 它只能同步读取自有模块的服务ServiceNotReadyError。 。 。 .
运行时间
Core从不直接调用主机API. 环境变量、计时器、HTTP服务器、处理退出和关闭信号来自运行时间,该时间作为自己的软件包:
| 软件包 | 出口 | 页:1 |
|---|---|---|
@elysiumjs/runtime-bun | runtime | Bun.env, (中文(简体) ), Bun.sleep, (中文(简体) ), listen,处理退出和信号 |
@elysiumjs/runtime-node | runtime | 节点.js相同;设置 server: { adapter: node() } 从 @elysiajs/node |
@elysiumjs/runtime-workers | runtime, (中文(简体) ), createRuntime({ env }) | 信封来源 process.env (单位:千美元)nodejs_compat)或(或) env; 无 serve (出口应用程序) fetch、 退出或信号 |
每个软件包出口 runtime (如果喜欢,请在导入时重命名). 把它交给申请,或者打电话 Runtime.use() 在一切需要它之前:
import { Application } from '@elysiumjs/core';
import { runtime } from '@elysiumjs/runtime-bun';
@Application.register({ runtime, modules: [] })
class App extends Application {}
没有跑步时间, Runtime.get() 掷出 RuntimeNotConfiguredError; 只有环境名称返回到 development。 。 。 . bun run check:runtime 捆绑核心,如果它触及到 Bun global,然后从节点.js下捆绑的应用程序启动.
文档
开始
捐款
本项目的建造主要是为了满足内部项目的要求 工作布德技术公司.
任何错误或问题都可以打开 拉动请求也是受欢迎的,但将对其进行审查,以确保它们符合我们内部项目的目标和标准.
许可证
这个项目根据Apache许可证2.0版本获得许可. 见 自由主义 文档以获取更多信息.