Skip to content
Blog

Elysia 1.3 and Scientific Witchery

pink-violet tint mesh gradient background with word 'Elysia 1.3' and the word 'Scientific Witchery' below the title

以 Mili 的歌曲《Ga1ahad 和 Scientific Witchery》命名。

¥Named after the song Ga1ahad and Scientific Witchery by Mili.

此版本没有新增任何炫酷的功能。

¥This release doesn't come with shiny new features.

我们不断改进,直到它成为我们称之为 "magic" 的版本。

¥It's about refinement to make things better to the point that we consider it as "magic".

Elysia 1.3 的功能几乎为零开销,经过了改进,修复了技术债务,并重构了内部代码,具体如下:

¥Elysia 1.3 features with near 0 overhead, refinement, fixing technical debt, and refactoring internal code, featuring:

精确镜像

¥Exact Mirror

我们在 Elysia 1.1 中引入了 normalize,以确保数据符合我们期望的形状,并且它运行良好。

¥We introduced normalize in Elysia 1.1 to ensure that data matches our desired shape, and it works nicely.

它有助于减少潜在的数据泄露和意外属性,我们的用户非常喜欢它。但是,这会带来性能成本。

¥It helps reduce potential data leaks, unexpected properties and our users love it. However, it comes with a performance cost.

在底层,它使用 TypeBox's Value.Clean 将数据动态强制转换为指定的模式。

¥Under the hood, it uses TypeBox's Value.Clean to coerce data into specified schema dynamically.

它运行良好,但速度不如我们期望的那么快。

¥It works great but not as fast as we want it to be.

由于 TypeBox 不提供 Value.Clean 的编译版本,而 TypeCompiler.Check 则利用了提前知道形状的优势。

¥As TypeBox doesn't offer a compiled version of Value.Clean unlike TypeCompiler.Check that takes advantage of knowing shape ahead-of-time.

这就是为什么我们引入了 精确镜像 的替代品。

¥That's why we introduced a replacement with Exact Mirror.

Exact Mirror 是 TypeBox Value.Clean 的简易替代品,通过利用提前编译显著提升性能。

¥Exact Mirror is a drop-in replacement for TypeBox's Value.Clean with significant performance improvements by leveraging ahead-of-time compilation.

性能

¥Performance

适用于没有数组的小对象。我们测得,对于同一对象,速度最高可达约 500 倍。

¥For small objects without arrays. We measured up to ~500x faster for the same object.

Exact Mirror run on small data resulting in 582.52x faster than TypeBox Value.Clean

Exact Mirror 适用于小型数据

适用于中型和大型对象。我们测得,速度最高可达约 30 倍。

¥And for medium and large-size objects. We measured up to ~30x faster.

Exact Mirror run on medium and large data resulting in 29.46x and 31.6x in order

Exact Mirror 适用于中大型数据

这对 Elysia

¥What it means for Elysia

从 Elysia 1.3 开始,Exact Mirror 是替代 TypeBox 的默认规范化策略。

¥Starting from Elysia 1.3, Exact Mirror is a default strategy for normalization replacing TypeBox.

升级到 Elysia 1.3 后,你可以期待显著的性能提升,而无需任何代码更改。

¥By upgrading to Elysia 1.3, you can expect a significant performance improvement without any code changes.

这是 Elysia 1.2 上的吞吐量。

¥Here's the throughput on Elysia 1.2.

Elysia with normalization turned off resulting in 49k req/sec

关闭规范化的 Elysia

这是 Elysia 1.3 上的相同代码

¥And here's the same code on Elysia 1.3

Elysia with normalization turned on resulting in 77k req/sec

开启规范化的 Elysia

我们测得,在使用带规范化的单一模式时,吞吐量最高可达约 1.5 倍。

¥We measured up to ~1.5x throughput when using a single schema with normalization.

这意味着如果你使用多个模式,你应该会看到更多的性能提升。

¥This means if you use more than a single schema, you should see even more performance improvement.

与没有模式的相同代码相比,性能差异不到 2%。

¥When comparing to the same code without schema, we see < 2% performance differences.

Elysia runs with no validation results in 79k req/sec

Elysia 运行时无需验证

这很大。

¥This is huge.

之前,你必须在安全性和性能之间做出选择,因为我们正在缩小使用和不使用验证之间的性能差距。但现在你不必再为此担心了。

¥Previously, you had to choose between safety and performance as we close the performance gap between using and not using validation. But now you don't have to worry about it.

现在,我们将验证开销从相当大的部分降低到几乎接近于零,而无需你进行任何更改。

¥But now, we drop validation overhead from a significant amount to almost near zero without requiring any changes on your side.

它就像魔法一样运行。

¥It just works, like magic.

但是,如果你想使用 TypeBox 或完全禁用规范化。你可以像其他配置一样使用构造函数进行设置:

¥However, if you would like to use TypeBox or disable normalization entirely. You can set it with constructor like any other configuration:

ts
import { Elysia } from 'elysia'

new Elysia({
	normalize: 'typebox' // Using TypeBox
})

你可以通过访问 GitHub 上的 Exact 镜像 亲自测试基准测试。

¥You can try the benchmark out yourself by visiting Exact Mirror on GitHub.

系统路由

¥System Router

Elysia 中的路由从未出现过性能问题。

¥We have never had performance problems with router in Elysia.

它具有出色的性能,并且我们尽可能地对其进行了超优化。

¥It has excellent performance, and hyper-optimized it as much as we possibly can.

我们将其推向了 JavaScript 在实际应用中所能提供的极限。

¥We pushed it to the near limit of what JavaScript can offer in a practical sense.

Bun 路由

¥Bun Router

然而,Bun 1.2.3 提供了一个内置解决方案,可以在原生代码中进行路由(可能)。

¥However, Bun 1.2.3 offers a built-in solution to routing (possibly) in native code.

虽然对于静态路由,我们没有看到太多的性能提升,但我们发现动态路由的执行速度在不更改任何代码的情况下提高了 2-5%。

¥Although for static route, we didn't see much performance improvement but we found that dynamic routes perform 2-5% faster without any code changes.

从 Elysia 1.3 开始,我们提供双路由策略,同时使用 Bun 的原生路由和 Elysia 的路由。

¥Starting from Elysia 1.3, we offer a dual router strategy by using both Bun's native router and Elysia's router.

Elysia 将尽可能尝试使用 Bun 路由,并回退到 Elysia 的路由。

¥Elysia will try to use a Bun router if possible and fall back to Elysia's router.

适配器

¥Adapter

为了实现这一点,我们必须重写内部编译代码,以支持从适配器自定义路由。

¥To make this possible, we have to rewrite our internal compilation code to support custom router from adapter.

这意味着,现在可以将自定义路由与 Elysia 自己的路由一起使用。

¥Which means that, it's now possible to use a custom router alongside Elysia's own router.

这为某些环境下的性能提升提供了机会,例如:使用内置 uWebSocket.js router,该 uWebSocket.js router 具有原生路由实现。

¥This opens up an opportunity for performance improvement in some environments, for example: using built-in uWebSocket.js router which has native implementation for routing.

独立验证器

¥Standalone Validator

在 Elysia 中,我们可以定义一个模式,并使用 guard 将其应用于多个路由。

¥In Elysia, we can define a schema and apply it to multiple routes with guard.

然后,我们可以通过在路由处理程序中提供模式来覆盖公共模式,有时如下所示:

¥We can then override a public schema by providing a schema in a route handler which sometimes looks like this:

Elysia run with default override guard showing schema gets override

Elysia 使用默认覆盖守护程序运行

但有时我们不想覆盖模式。

¥But sometimes we don't want to override a schema.

相反,我们希望它能够同时工作,允许我们组合模式,而不是覆盖它们。

¥Instead we want it to work both allowing us to combine schemas instead of overriding them.

从 Elysia 1.3 开始,我们可以做到这一点。

¥Starting from Elysia 1.3, we can do just that.

现在我们可以告诉 Elysia 不要覆盖它,而是通过提供一个独立的模式将其视为自己的。

¥We can now tell Elysia not to override it and instead treat it as its own by providing a schema as standalone.

ts
import { Elysia } from 'elysia'

new Elysia()
	.guard({
		schema: 'standalone', 
		response: t.Object({
			title: t.String()
		})
	})

最终,我们得到的结果就像将本地架构和全局架构合并在一起一样。

¥As a result, we have results that are like merging a local and global schema together.

Elysia run with standalone merging multiple guard together

Elysia 使用独立运行,将多个守护程序合并在一起

精简类型实例化

¥Reduced Type Instantiation

Elysia 的类型推断速度已经非常快了。

¥Elysia's type inference is already extremely fast.

我们对类型推断的优化非常有信心,它比大多数使用类似 Express 语法的框架都要快。

¥We are really confident in our optimization of type inference and it's faster than most frameworks that use an express-like syntax.

然而,我们的用户拥有非常大规模的应用,这些应用具有多个路由和复杂的类型推断。

¥However, our users with really really large scale applications with multiple routes and complex type inference.

在大多数情况下,我们设法将类型实例化减少了一半,并且推断速度提高了 60%。

¥We managed to reduce type instantiation by half in most cases, and measured up to 60% improvement in inference speed.

type instantiation reduced from 109k to 52k

类型实例化从 109k 减少到 52k

我们还将 decorate 的默认行为从递归循环每个对象和属性改为执行 intersect。

¥We also changed the default behavior of decorate instead of looping every object and property recursively to do intersect instead.

这应该可以解决使用重对象/类(例如 PrismaClient)的用户的问题。

¥This should solve the problem with users who use heavy object/class for example PrismaClient.

最终,我们将获得更快的 IDE 自动补齐、建议、类型检查和 Eden 条约功能。

¥As a result, we should end up with faster IDE auto-completion, suggestion, type checking and Eden Treaty.

性能改进

¥Performance Improvement

我们重构并优化了大量内部代码,这些代码积累起来取得了显著的改进。

¥We have refactored and optimized a lot of internal code which accumulates up to significant improvements.

路由注册

¥Route Registration

我们重构了路由信息的存储方式,并重新使用对象引用,而不是克隆/创建新的对象引用。

¥We have refactored how we store route information and reuse an object reference instead of cloning/creating a new one.

我们注意到了以下改进:

¥We saw the following improvements:

  • 内存使用量减少高达约 5.6 倍

  • 路由注册时间加快高达约 2.7 倍

Route registration comparison between Elysia 1.2 (left), and 1.3 (right)

Elysia 1.2(左)和 1.3(右)的路由注册对比

这些优化应该会对中大型应用产生实际效果,因为它会根据服务器的路由数量进行扩展。

¥These optimizations should show real results for medium to large scale apps as it scales with how many routes the server has.

Sucrose

我们实现了 Sucrose 缓存,以减少不必要的重复计算,并在为非内联事件编译每个路由时重用已编译的路由。

¥We have implemented Sucrose cache to reduce unnecessary re-computation and reused compiled routes when compiling each route for non-inline events.

Sucrose performance comparison between Elysia 1.2 (left), and 1.3 (right)

Elysia 1.2(左)和 1.3(右)之间的 Sucrose 性能对比

Sucrose 将每个事件转换为校验和,并将其存储为缓存。它占用很少的内存,服务器启动后就会被清理。

¥Sucrose converts each event into a checksum number and stores it as a cache. It uses little memory and will be cleaned up once the server has started.

这项改进应该有助于缩短每个重用全局/作用域事件的路由的启动时间。

¥This improvement should help with the startup time of each route that reuses global/scoped events.

实例

¥Instance

创建多个实例并将其作为插件应用时,我们发现了显著的改进。

¥We saw a significant improvement when creating multiple instances and apply them as plugins.

  • 内存使用量减少高达约 10 倍

  • 插件创建速度加快高达约 3 倍

Elysia instance comparison between Elysia 1.2 (left), and 1.3 (right)

Elysia 1.2(左)和 1.3(右)实例对比

升级到 Elysia 1.3 后,这些优化将自动应用。然而,这些性能优化对于小型应用来说可能并不明显。

¥These optimizations will be applied automatically by upgrading to Elysia 1.3. However, these performance optimizations might not be significantly noticeable for small apps.

由于为一个简单的 Bun 服务器提供服务,其固定成本约为 10-15MB。这些优化更多地是为了减少现有开销,并有助于缩短启动时间。

¥As serving a simple Bun server as a fixed cost of around 10-15MB. These optimizations are more of reducing an existing overhead and helps improve startup time.

总体性能更快

¥Faster performance in general

通过各种微优化、修复技术债务以及消除未使用的编译指令来实现。

¥Through various micro-optimizations, fixing technical debt, and eliminating unused compiled instructions.

Elysia 的请求处理速度也得到了一些整体提升。在某些情况下,最高可达 40%。

¥We saw some general improvements in Elysia request processing speed. In some cases up to 40%.

Elysia.handle comparison between Elysia 1.2 and 1.3

Elysia.handle 在 Elysia 1.2 和 1.3 版本之间的比较

验证 DX 改进

¥Validation DX Improvement

我们希望 Elysia 的验证能够正常工作。

¥We want Elysia validation to just work.

你只需说出你想要的内容即可获得它。这是 Elysia 最有价值的方面之一。

¥The one that you can just tell what you want then you get it. It's one of the most valuable aspects of Elysia.

在本次更新中,我们改进了一些之前缺乏的方面。

¥In this update, we have improved some areas that we have been lacking.

编码模式

¥Encode schema

我们已将 encodeSchemaexperimental 中移出,并默认启用它。

¥We have moved encodeSchema out of experimental, and enabled it by default.

这使我们能够使用 t.Transform 应用自定义响应映射以返回给终端用户。

¥This allows us to use t.Transform to apply custom response mapping to return to the end user.

Using t.Transform to intercept a value into a new one

使用 t.Transform 将一个值截取为一个新值

此示例代码将拦截响应,并将 "hi" 替换为 "intercepted"。

¥This example code will intercept a response, replacing "hi" with "intercepted" instead.

清理

¥Sanitize

为了防止 SQL 注入和 XSS,并确保字符串输入/输出安全,我们引入了 sanitize 选项。

¥To prevent SQL injection and XSS, and to ensure string input/output is safe, we introduced sanitize option.

它接受一个函数或一个函数数组,该函数或函数数组会拦截每个 t.String,并将其转换为新值。

¥It accepts a function or an array of functions that intercepts every t.String, and transforms it into a new value.

Using sanitize with Bun.escapeHTML

使用 Bun.escapeHTML 进行清理

在此示例中,我们使用 Bun.escapeHTML 并将每个 "dorothy" 替换为 "doro"。

¥In this example, we are using Bun.escapeHTML and replace every "dorothy" with "doro" instead.

由于 sanitize 将全局应用于每个模式,因此必须将其应用于根实例。

¥As sanitize will apply to every schema globally, it must be applied on a root instance.

这应该可以大大减少手动安全验证和转换每个字符串字段的样板代码。

¥This should greatly reduce the boilerplate to safely validate and transform each string field manually.

表单

¥Form

在之前的 Elysia 版本中,无法在编译时使用 formt.Object 对 FormData 响应进行类型检查。

¥In previous versions of Elysia, it's not possible to type-check FormData response with form and t.Object at compile time.

我们现在引入了一种新的 t.Form 类型来解决这个问题。

¥We have now introduced a new t.Form type to fix that.

Using t.Form to validate FormData

使用 t.Form 验证 FormData

要迁移到类型检查表单,只需在响应模式中将 t.Object 替换为 t.Form 即可。

¥To migrate to type-check form, simply replace t.Object with t.Form in response schema.

文件类型

¥File Type

Elysia 现在使用 file-type 来验证文件类型。

¥Elysia now uses file-type to validate file type.

Defining file type using t.File

使用 t.File 定义文件类型

一旦指定了 type,Elysia 就会通过检查魔法数字自动检测文件类型。

¥Once type is specified, Elysia will automatically detect file type by checking magic number.

但是,它也被列为 peerDependencies,并且默认情况下不会随 Elysia 一起安装,以便为不需要它的用户减少包大小。

¥However, it's also listed as peerDependencies and not installed with Elysia by default to reduce bundle size for users who don't need it.

如果你依赖文件类型验证来提高安全性,建议更新到 Elysia 1.3。

¥It's recommended to update to Elysia 1.3 if you rely on file type validation for better security.

Elysia.Ref

我们可以使用 Elysia.model 创建一个参考模型并使用名称引用它。

¥We can create a reference model by using Elysia.model and reference it with name.

然而,有时我们需要在模式中引用它。

¥However, sometimes we need to reference it inside a schema.

我们可以通过使用 Elysia.Ref 引用具有自动补齐功能的模型来实现这一点。

¥We can do just that by using Elysia.Ref to reference the model with auto-completion.

Using Elysia.Ref to reference model

使用 Elysia.Ref 引用模型

你还可以使用 t.Ref 引用模型,但它不提供自动补齐功能。

¥You can also use t.Ref to reference a model, but it wouldn't provide auto-completion.

NoValidate

我们收到一些反馈,一些用户希望快速构建 API 原型,或者有时在尝试强制执行验证时遇到问题。

¥We received some feedback that some users want to quickly prototype their API or sometimes have problems trying to enforce validation.

在 Elysia 1.3 中,我们引入了 t.NoValidate 来跳过验证。

¥In Elysia 1.3, we introduced t.NoValidate to skip validation.

Using t.NoValidate to tell Elysia to skip validation

使用 t.NoValidate 告诉 Elysia 跳过验证

这将告诉 Elysia 跳过运行时验证,但仍提供 TypeScript 类型检查和用于 API 文档的 OpenAPI 模式。

¥This will tell Elysia to skip runtime validation, but still provides TypeScript type checking and OpenAPI schema for API documentation.

状态

¥Status

我们收到了很多关于 error 命名的反馈。

¥We have received a lot of responses about the naming of error.

从 Elysia 1.3 开始,我们决定弃用 error,并推荐使用 status

¥Starting with Elysia 1.3, we decided to deprecate error, and recommend the use of status instead.

IDE showing that error is deprecated and renamed to status

IDE 显示该错误已弃用并重命名为状态

error 函数将与上一版本一样运行,无需立即进行更改。

¥The error function will work as it is in the previous version, with no immediate changes required.

但是,我们建议重构为 status,因为我们将至少在接下来的 6 个月内或直到 Elysia 1.4 或 1.5 左右支持 error 功能。

¥However, we recommend refactoring to status instead as we will be supporting error function for at least the next 6 months or until around Elysia 1.4 or 1.5.

要迁移,只需将 error 重命名为 status

¥To migrate, simply rename error to status.

".index" 已从 Treaty 中移除

¥".index" is removed from Treaty

之前,你必须添加 (treaty).index 来处理以 / 结尾的路径。

¥Previously, you had to add (treaty).index to handle paths that end with /.

从 Elysia 1.3 开始,我们决定放弃使用 .index,并绕过它直接调用方法。

¥Starting with Elysia 1.3, we decided to drop the use of .index and can simply bypass it to call the method directly.

Eden Treaty showing no-use of .index

Eden Treaty 显示未使用 .index

这是一个重大变更,但迁移起来应该只需要很少的努力。

¥This is a breaking change but should require minimal effort to migrate.

要迁移,只需从代码库中删除 .index。这应该是一个简单的更改,只需使用 IDE 搜索功能,通过匹配 .index 进行批量更改和替换即可将其删除。

¥To migrate, simply remove .index from your codebase. This should be a simple change by using IDE search to bulk change-and-replace by matching .index to remove it.

显著变化

¥Notable changes

以下是更新日志中的一些显著变化。

¥Here are some notable changes from changelog.

改进

¥Improvement

  • encodeSchema 现已稳定并默认启用

  • 优化类型

  • 减少使用 Encode 时冗余的类型检查

  • 优化 isAsync 操作

  • 默认使用 unwrap Definition['typebox'] 来避免不必要的 UnwrapTypeModule 调用

  • Elysia.form 现在可以进行类型检查了。

  • 重构类型系统

  • _types 重构为 ~Types

  • 使用 aot 编译检查自定义 Elysia 类型,例如 Numeric

  • 重构 app.router.static,并将静态路由代码生成移至编译阶段

  • 优化 add_use 和一些实用函数的内存使用情况

  • 优化多路由的启动时间

  • 在编译过程中根据需要动态创建 cookie 验证器

  • 减少对象克隆

  • 优化查找内容类型标头分隔符的起始索引

  • Promise 现在可以作为静态响应。

  • ParseError 现在保留堆栈跟踪

  • 重构 parseQueryparseQueryFromURL

  • config 选项添加到 mount

  • 异步模块挂载后自动重新编译

  • 当钩子函数有函数时,支持开启宏

  • 支持在 ws 上使用 resolve 宏

  • #1146 添加从处理程序返回 Web API 文件的支持

  • #1165 在响应模式验证中跳过非数字状态码

  • #1177 cookie 在发生错误时不签名

错误修复

¥Bug fix

  • onError 返回的 Response 使用八位字节流

  • 使用 mergeObjectArray 时会出现意外的内存分配问题

  • 处理日期查询中的空格

变更

¥Change

  • 仅当 maybeStream 为 true 时向 mapResponse 提供 c.request 参数

  • 使用普通对象代替 Map

  • 删除 compressHistoryHookdecompressHistoryHook

  • 如果不在 Bun 上,webstandard 处理程序现在返回 text/plain

  • 除非明确指定,否则 decorate 应使用非常量值

  • Elysia.mount 现在默认设置 detail.hide = true

重大变更

¥Breaking Change

  • 移除 as('plugin') 函数,使用 as('scoped') 替代

  • 移除 Eden Treaty 的根 index

  • ElysiaAdapter 中删除 websocket

  • 移除 inference.request

后记

¥Afterword

你好?已经有一段时间了。

¥Hi? It's been a while.

生活总是令人困惑,不是吗?

¥Life can be confusing, isn't it?

总有一天,你会追逐自己的梦想,并为之努力奋斗。

¥One day you're chasing your dream, working hard toward it.

不知不觉中,你回头一看,就会发现你已经远远领先于你的目标了。

¥Before you know it, you look back and realize that you are far ahead of your goal.

有人仰慕你,而你成为他们的灵感来源。某人的榜样。

¥Someone looks up to you, and you become their inspiration. A role model for someone.

听起来很棒,对吧?

¥It sounds amazing, right?

但我不认为我会成为其他人的好榜样。

¥But I don't think I would be a good role model for others.

我想诚实地生活

¥I want to live an honest life

有时,事情会被夸大。

¥Sometimes, things just get exaggerated.

我可能看起来不太像。我只是尽力了。

¥I may appear I'm a genius who can create anything but I'm not. I just try my best.

我和朋友们一起玩电子游戏,听奇怪的歌,看电影。我甚至在 Cosplay 大会上遇到了我的朋友。

¥I hang out playing video games with friends, listening to weird songs, and watching movies. I even meet my friends at cosplay conventions.

就像普通人一样。

¥Just like a normal person.

一直以来,我都紧紧地拥抱着你的手臂。

¥All this time, I've just been hugging tightly to your arm.

我和你一样,没什么特别的。

¥I'm just like you, nothing special.

我尽力了,但有时也会像个傻瓜一样。

¥I try my best but I also act like a fool from time to time.

即使我认为我没有任何可以让我成为榜样的东西,我也希望你能让我表达我的感激之情。

¥Even if I don't think I have anything that makes me a role model, I want you to let me say that I'm grateful.

我无聊又略带孤独的生活,请不要过度美化它。

¥My boring and slightly lonely life, please don't beautify it too much.

~ 我很高兴你也很邪恶。

¥~ I'm glad you're evil too.

Elysia: Ergonomic Framework for Humans