Elysia 0.8 - Gate of Steiner

以 Steins;Gate Zero 的结尾歌曲命名,"Gate of Steiner"。
Gate of Steiner 并非专注于新的激动人心的 API 和功能,而是注重 API 的稳定性以及坚实的基础,以确保 Elysia 1.0 发布后 API 将保持稳定。
不过,我们确实带来了改进和新功能,包括:
Macro API
Macro 允许我们定义自定义字段来钩入和守护,通过暴露对生命周期事件栈的完全控制。
这允许我们将自定义逻辑组合成简单的配置,同时保持完整的类型安全。
假设我们有一个认证插件,根据角色限制访问,我们可以定义一个自定义的 role 字段。
import { Elysia } from 'elysia'
import { auth } from '@services/auth'
const app = new Elysia()
.use(auth)
.get('/', ({ user }) => user.profile, {
role: 'admin'
})
Macro 可以完全访问生命周期栈,允许我们为每个路由直接添加、修改或删除现有事件。
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)
}
)
}
}
})
我们希望通过这个 Macro API,插件维护者能够尽情定制 Elysia,从而开辟一种更好地与 Elysia 交互的新方式,而 Elysia 用户则能够享受到更符合人体工程学的 API。
Macro API 的文档现已在 pattern 部分可用。
下一代的可定制性现在只需触及您的键盘和想象力即可实现。
新生命周期
Elysia 引入了新的生命周期来修复现有问题,并提供了备受期待的 API,包括 Resolve 和 MapResponse: resolve:derive 的安全版本。与 beforeHandle 在同一队列中执行 mapResponse:在 afterResponse 之后立即执行,用于从原始值提供转换函数到 Web 标准 Response
Resolve
derive 的“安全”版本。
设计用于在验证过程后将新值追加到上下文中,并存储在与 beforeHandle 相同的栈中。
Resolve 语法与 derive 相同,以下是从 Authorization 插件检索 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 之后运行的新生命周期,专门用于提供自定义响应映射,而不是将响应映射和原始值变异混合在同一队列中。
Error Function
我们可以使用 set.status 或返回新的 Response 来设置状态码。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "I'm a teapot"
})
.listen(3000)
这符合我们的目标,即直接将字面值发送给客户端,而无需担心服务器的行为方式。
然而,这被证明与 Eden 的集成具有挑战性。由于我们返回字面值,无法从响应中推断状态码,从而使 Eden 无法区分响应和状态码。
这导致 Eden 无法发挥其全部潜力,尤其是在错误处理方面,因为它无法在不为每个状态声明显式响应类型的情况下推断类型。
伴随着用户许多请求,希望有一种更明确的方式直接与值返回状态码,而不依赖 set.status 和 new Response 的冗长性,或从处理函数外部声明的实用函数返回响应。
这就是为什么我们引入 error 函数来与值一起返回状态码回客户端。
import { Elysia, error } from 'elysia'
new Elysia()
.get('/', () => error(418, "I'm a teapot"))
.listen(3000)
这等同于:
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "I'm a teapot"
})
.listen(3000)
不同之处在于,使用 error 函数,Elysia 将自动将状态码区分到专用的响应类型中,帮助 Eden 根据状态正确推断响应。
这意味着使用 error,我们无需包含显式的响应 schema 来使 Eden 为每个状态码正确推断类型。
import { Elysia, error, t } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status = 418
return "I'm a teapot"
}, {
response: {
418: t.String()
}
})
.listen(3000)
我们推荐使用 error
函数返回带有状态码的响应以进行正确的类型推断,不过,我们不打算从 Elysia 中移除 set.status 的使用,以保持现有服务器的正常运行。
Static Content
Static Content 指的是几乎总是返回相同值的响应,无论传入请求如何。
服务器上的这种资源通常是像公共 File、video 或很少更改的硬编码值,除非服务器更新。
到目前为止,Elysia 中的大多数内容都是静态内容。但我们也发现许多情况,如使用模板引擎提供静态文件或 HTML 页面,通常也是静态内容。
这就是为什么 Elysia 引入了一个新 API 来优化静态内容,通过提前确定 Response 来实现。
new Elysia()
.get('/', () => Bun.file('video/kyuukurarin.mp4'))
.get('/', Bun.file('video/kyuukurarin.mp4'))
.listen(3000)
请注意,现在的处理程序不是函数,而是内联值。
这将通过提前编译响应来提高约 20-25% 的性能。
Default Property
Elysia 0.8 更新到 TypeBox 0.32,这引入了许多新功能,包括专用的 RegEx、Deref,但最重要的是 Elysia 中最受期待的功能,default 字段支持。
现在,在 Type Builder 中定义默认字段,如果值未提供,Elysia 将提供默认值,支持从 type 到 body 的 schema 类型。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ query: { name } }) => name, {
query: t.Object({
name: t.String({
default: 'Elysia'
})
})
})
.listen(3000)
这允许我们直接从 schema 提供默认值,尤其在使用引用 schema 时非常有用。
Default Header
我们可以使用 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 头部,以修复像视频这样的大型文件需要分块发送的问题。
为了修复这个问题,Elysia 现在默认添加 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 的内存使用,对于峰值并发请求来说更好一些。
插件
大多数官方插件现在利用了更新的 Elysia.headers、Static Content、MapResponse 和修订的代码,以更好地符合静态代码分析,从而提高整体性能。
通过此改进的插件包括:Static、HTML 和 CORS。
验证错误
Elysia 现在返回 JSON 格式的验证错误,而不是文本。
显示当前错误、所有错误和预期值,以帮助您更容易识别错误。
示例:
{
"type": "query",
"at": "password",
"message": "Required property",
"expected": {
"email": "eden@elysiajs.com",
"password": ""
},
"found": {
"email": "eden@elysiajs.com"
},
"errors": [
{
"type": 45,
"schema": {
"type": "string"
},
"path": "/password",
"message": "Required property"
},
{
"type": 54,
"schema": {
"type": "string"
},
"path": "/password",
"message": "Expected string"
}
]
}
在生产环境中,默认移除 expect 和 errors 字段,以防止攻击者识别模型以进行进一步攻击。
显著改进
改进
- 懒加载查询引用
- 默认为
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。
我们现在正努力为每个运行时、插件和集成提供更多支持,以回报您给予我们的善意,从重写文档开始,使其更详细和对初学者友好,与 Nextjs 集成,Astro 以及未来更多内容。
自 0.7 发布以来,与之前版本相比,我们看到的问题更少了。
现在 我们正在准备 Elysia 的第一个稳定发布,Elysia 1.0 旨在 在 2024 年第一季度 发布,以回报您的善意。 Elysia 现在将进入软 API 锁定模式,以防止 API 变更,并确保稳定发布到来时不会有或少有破坏性变更。
因此,您可以预期从 0.7 开始的 Elysia 应用只需无或最小变更即可支持 Elysia 的稳定发布。
我们再次感谢您对 Elysia 的持续支持,并希望在稳定发布日再次见到您。
为这个世界中所有美好的事物而战。
在那之前,El Psy Congroo。
A drop in the darkness 小さな命
Unique and precious forever
Bittersweet memories 夢幻の刹那
Make this moment last, last forever
We drift through the heavens 果てない想い
Filled with the love from up above
He guides my travels せまる刻限
Shed a tear and leap to a new world