Elysia 0.8 - Steiner之门

命名源于《命运石之门0》的片尾曲,"Gate of Steiner"。
Steiner之门并不专注于新的、令人兴奋的 API 和功能,而是专注于 API 的稳定性和坚实的基础,以确保在 Elysia 1.0 发布时 API 的稳定性。
然而,我们确实带来了一些改进和新功能,包括:
宏 API
宏允许我们通过完全控制生命周期事件堆栈来定义自定义字段进行挂钩和守护。
这使我们可以将自定义逻辑组合成一个具有完整类型安全性的简单配置。
假设我们有一个基于角色限制访问的身份验证插件;我们可以定义一个自定义的 role 字段。
import { Elysia } from 'elysia'
import { auth } from '@services/auth'
const app = new Elysia()
.use(auth)
.get('/', ({ user }) => user.profile, {
role: 'admin'
})宏可以完全访问生命周期堆栈,允许我们为每个路由直接添加、修改或删除现有事件。
const plugin = new Elysia({ name: 'plugin' }).macro(({ beforeHandle }) => {
return {
role(type: 'admin' | 'user') {
beforeHandle(
{ insert: 'before' },
async ({ cookie: { session } }) => {
const user = await validateSession(session.value)
await validateRole('admin', user)
}
)
}
}
})我们希望通过这个宏 API,插件维护者能够随心所欲地定制 Elysia,开辟一种与 Elysia 更好交互的新方式,并且 Elysia 用户能够享受到 Elysia 提供的更加符合人体工程学的 API。
宏 API 的文档现在已在 pattern(模式)部分中提供。
下一代可定制性如今触手可及,只待您的键盘和想象力。
新生命周期
Elysia 引入了新的生命周期来修复现有问题并添加呼声很高的 API,包括 Resolve 和 MapResponse: resolve: derive 的安全版本。在与 beforeHandle 相同的队列中执行 mapResponse: 在 afterResponse 之后立即执行,提供一个从原始值到 Web 标准 Response 的转换函数
Resolve
derive 的“安全”版本。
设计用于在验证后将新值追加到上下文,并将其存储在与 beforeHandle 相同的堆栈中。
Resolve 的语法与 derive 相同。
下面是一个从授权插件中获取 bearer 头部的示例。
import { Elysia } from 'elysia'
new Elysia()
.guard(
{
headers: t.Object({
authorization: t.TemplateLiteral('Bearer ${string}')
})
},
(app) =>
app
.resolve(({ headers: { authorization } }) => {
return {
bearer: authorization.split(' ')[1]
}
})
.get('/', ({ bearer }) => bearer)
)
.listen(3000)MapResponse
在 afterHandle 之后立即执行;设计用于提供从原始值到 Web 标准 Response 的自定义响应映射。
下面是使用 mapResponse 提供响应压缩的示例。
import { Elysia, mapResponse } from 'elysia'
import { gzipSync } from 'bun'
new Elysia()
.mapResponse(({ response }) => {
return new Response(
gzipSync(
typeof response === 'object'
? JSON.stringify(response)
: response.toString()
)
)
})
.listen(3000)为什么不使用 afterHandle 而是引入一个新的 API?
因为 afterHandle 设计用于读取和修改原始值。存储像 HTML 和 Compression 这样依赖于创建 Web 标准 Response 的插件。
这意味着在此类插件之后注册的插件将无法读取或修改该值,从而导致插件行为不正确。
这就是为什么我们引入了一个在 afterHandle 之后运行的新生命周期,专门用于提供自定义响应映射,而不是在同一队列中混合响应映射和原始值变更。
错误函数
我们可以使用 set.status 或返回一个新的 Response 来设置状态码。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "我是一个茶壶"
})
.listen(3000)这与我们的目标一致:将字面值发送给客户端,而无需担心服务器应如何表现。
然而,这在与 Eden 集成时被证明具有挑战性。由于我们返回一个字面值,因此无法从响应中推断状态码,使得 Eden 无法将响应与状态码区分开来。
这阻止了 Eden 发挥其全部潜力,尤其是在错误处理方面,因为它无法在不为每个状态声明显式响应类型的情况下推断类型。
再加上我们用户的许多请求,希望有一种更直接的方式将状态码与值一起返回——而无需依赖 set.status 或 new Response,因为后者可能很冗长或需要从处理函数外部声明的工具函数返回响应。
这就是为什么我们引入一个 error 函数,将状态码和值一起返回给客户端。
import { Elysia, error } from 'elysia'
new Elysia()
.get('/', () => error(418, "我是一个茶壶"))
.listen(3000)这等效于:
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "我是一个茶壶"
})
.listen(3000)不同之处在于,使用 error 函数,Elysia 会自动将状态码区分为一个专用的响应类型,帮助 Eden 根据状态正确推断响应。
这意味着通过使用 error,我们不必为每个状态码都包含一个显式的响应模式,就能让 Eden 正确推断类型。
import { Elysia, error, t } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "我是一个茶壶"
}, {
response: {
418: t.String()
}
})
.listen(3000)我们建议使用 error 函数来返回带有状态码的响应,以实现正确的类型推断;但是,我们不打算从 Elysia 中移除 set.status 的使用,以保持现有服务器的正常运行。
静态内容
静态内容是指无论传入的请求如何,几乎总是返回相同值的响应。
服务器上的这种资源通常是公共的文件、视频或硬编码值,除非服务器更新,否则很少更改。
Elysia 中的大部分内容都是静态的。我们还发现许多情况,比如提供静态文件或使用模板引擎提供 HTML 页面,通常是静态内容。
这就是为什么 Elysia 引入了一个新的 API 来通过提前确定响应来优化静态内容。
new Elysia()
.get('/', () => Bun.file('video/kyuukurarin.mp4'))
.get('/', Bun.file('video/kyuukurarin.mp4'))
.listen(3000)请注意,处理程序不再是函数,而是一个内联值。
通过提前编译响应,这可以将性能提高约 20-25%。
默认属性
Elysia 0.8 将 TypeBox 更新到 0.32,该版本引入了许多新功能,包括专用的 RegEx、Deref,但最重要的是 Elysia 中最受请求的功能:default 字段支持。
现在,通过在 Type Builder 中定义默认字段,如果未提供值,Elysia 将提供默认值,支持从 type 到 body 的模式类型。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ query: { name } }) => name, {
query: t.Object({
name: t.String({
default: 'Elysia'
})
})
})
.listen(3000)这允许我们直接从模式提供默认值,在使用引用模式时特别有用。
默认头部
我们可以使用 set.headers 设置头部;Elysia 为每个请求创建一个默认的空对象。
以前,我们可以使用 onRequest 将所需的值附加到 set.headers,但这总是会增加一些开销,因为调用了一个函数。
如果值对于每个请求总是相同的,比如 CORS 或缓存头部,那么堆叠函数来修改对象会比预先设置所需值要慢一些。
这就是为什么我们现在支持开箱即用地设置默认头部,而不是为每个新请求创建一个空对象。
new Elysia()
.headers({
'X-Powered-By': 'Elysia'
})Elysia 的 CORS 插件也进行了更新,以使用这个新 API 来提高性能。
性能与显著改进
和往常一样,我们找到了进一步优化 Elysia 的方法,以确保您获得开箱即用的最佳性能。
移除 bind
我们发现 .bind 会使路径查找速度减慢约 5%。从我们的代码库中移除 bind 会稍微加快这个过程。
静态查询分析
Elysia 的静态代码分析现在可以在代码中引用了查询名称时推断该查询。
这通常会默认带来 15-20% 的速度提升。
视频流
Elysia 现在默认为 File 和 Blob 添加 content-range 头部,以解决像视频这样需要分块发送的大文件的问题。
如果状态码是 206、304、412 或 416,或者头部明确提供了 content-range 头部,Elysia 将不会发送 content-range 头部。
建议使用 ETag 插件 来处理正确的状态码,以避免来自缓存的 content-range 冲突。
这是对 content-range 头部的初步支持。我们已创建了一个讨论,以便在未来根据 RFC 7233 实现更准确的行为。欢迎在 Discussion 371 加入讨论,为 Elysia 提出关于 content-range 和 ETag 生成 的新行为建议。
运行时内存改进
Elysia 现在复用生命周期事件的返回值,而不是声明一个新的专用值。
这略微减少了内存使用,提高了高峰并发请求下的性能。
插件
大多数官方插件现在利用了更新的 Elysia.headers、静态内容、MapResponse,以及修订过的符合静态代码分析的代码,以提高整体性能。
因此而改进的插件包括 Static、HTML 和 CORS。
验证错误
Elysia 现在将验证错误作为 JSON 而不是文本返回。
它显示当前错误、所有错误和期望值,以帮助您更轻松地识别错误。
示例:
{
"type": "query",
"at": "password",
"message": "必填属性",
"expected": {
"email": "eden@elysiajs.com",
"password": ""
},
"found": {
"email": "eden@elysiajs.com"
},
"errors": [
{
"type": 45,
"schema": {
"type": "string"
},
"path": "/password",
"message": "必填属性"
},
{
"type": 54,
"schema": {
"type": "string"
},
"path": "/password",
"message": "期望字符串"
}
]
}在生产环境中,默认会移除 expect 和 errors 字段,以防止攻击者识别模型以进行进一步攻击。
显著改进
改进
- lazy query reference
- 为
Blob默认添加content-range头部 - 将 TypeBox 更新到 0.32
- 覆盖
be和af的生命周期响应
重大变更
afterHandle不再触发提前返回
变更
- 将验证响应更改为 JSON
- 将 derive 从
decorator['request']中区分出来,作为decorator['derive'] derive现在不在onRequest中显示推断类型
Bug 修复
- 从
PreContext中移除headers和path - 从
PreContext中移除derive - Elysia 类型不会输出自定义的
error
后记
自首次发布以来,这是一段伟大的旅程。
Elysia 从一个通用的 REST API 框架演变为一个支持端到端类型安全和 OpenAPI 文档生成的符合人体工程学的框架。我们希望在未来继续引入更多令人兴奋的功能。
我们很高兴有您,以及开发者们使用 Elysia 构建令人惊叹的东西,并且你们对 Elysia 压倒性的、持续的支持鼓励我们不断前进。
看到 Elysia 作为一个社区不断成长,令人兴奋:
- Scalar 的 Elysia 主题用于新文档,替代 Swagger UI
- pkgx 开箱即用地支持 Elysia
- 社区将 Elysia 提交到了 TechEmpower,在综合得分中排名第 32 位,超越了 Go 的 Gin 和 Echo
我们现在正尝试为每个运行时、插件和集成提供更多支持,以回报你们给予我们的善意,从重写文档开始,使其更详细、对初学者更友好,以及 与 Next.js 集成、Astro 以及未来更多的集成。
自 0.7 发布以来,我们看到的问题比以往版本更少。
现在我们正在准备 Elysia 的第一个稳定版本——Elysia 1.0——目标是于2024 年第一季度发布,以回报你们的善意。
Elysia 现在将进入软 API 锁定模式,以防止 API 更改,并确保在稳定版本到来时不会有或只有极少的重大变更。
因此,您可以期望您的 Elysia 应用程序从 0.7 开始,无需或只需极少更改即可支持 Elysia 的稳定版本。
我们再次感谢您对 Elysia 的持续支持,并希望在稳定版本发布之日再次见到您。
为这世上所有美好的事物而战。
直到那时,El Psy Congroo。
黑暗中的一滴 渺小的生命
永恒而独特,珍贵无比
苦乐参半的记忆 梦幻的瞬间
让这一刻成为永恒,直到永远
我们漂流于天际 无尽的思念
充满了来自上天的爱
他指引我的旅途 迫近的时限
流下一滴泪,跃向新世界