宏
宏类似于一个函数,能够对生命周期事件、模式、上下文进行控制,并具备完全的类型安全。
一旦定义,它将在钩子中可用,并且可以通过添加该属性来激活。
import { Elysia } from 'elysia'
const plugin = new Elysia({ name: 'plugin' })
.macro({
hi: (word: string) => ({
beforeHandle() {
console.log(word)
}
})
})
const app = new Elysia()
.use(plugin)
.get('/', () => 'hi', {
hi: 'Elysia'
})
访问该路径时,应会在控制台输出 "Elysia"。
属性简写
从 Elysia 1.2.10 开始,宏对象中的每个属性都可以是一个函数或一个对象。
如果属性是对象,它将被转换为一个接受布尔参数的函数,并且在参数为 true 时执行。
import { Elysia } from 'elysia'
export const auth = new Elysia()
.macro({
// 这个属性的简写形式
isAuth: {
resolve: () => ({
user: 'saltyaom'
})
},
// 等价于
isAuth(enabled: boolean) {
if(!enabled) return
return {
resolve() {
return {
user
}
}
}
}
})
API
macro 拥有与钩子相同的 API。
在之前的示例中,我们创建了一个接受 string 类型参数的 hi 宏。
然后我们将 hi 设置为 "Elysia",这个值随后被传回 hi 函数,接着该函数往 beforeHandle 栈中添加了一个新事件。
这相当于向 beforeHandle 推入了一个函数,如下所示:
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'hi', {
beforeHandle() {
console.log('Elysia')
}
})
当逻辑比仅传入一个新函数更复杂时,macro 能表现得尤为出色,例如为每个路由创建授权层。
import { Elysia } from 'elysia'
import { auth } from './auth'
const app = new Elysia()
.use(auth)
.get('/', ({ user }) => user, {
isAuth: true,
role: 'admin'
})
宏还能为上下文注册新属性,允许我们直接从上下文访问该值。
该字段可以接受从字符串到函数的任何内容,使我们能够创建自定义的生命周期事件。
macro 会根据钩子的定义从上到下顺序执行,以确保堆栈按正确顺序处理。
解析 (Resolve)
通过返回一个带有 resolve 函数的对象,您可以将属性添加到上下文中。
import { Elysia } from 'elysia'
new Elysia()
.macro({
user: (enabled: true) => ({
resolve: () => ({
user: 'Pardofelis'
})
})
})
.get('/', ({ user }) => user, {
user: true
})
上例中,我们通过返回一个包含 resolve 函数的对象,向上下文添加了一个新属性 user。
下面是宏解析可能派上用场的示例:
- 执行认证并向上下文添加用户信息
- 运行额外的数据库查询并将数据添加到上下文
- 向上下文添加新属性
模式 (Schema)
您可以为您的宏定义自定义模式,以确保使用宏的路由传递正确的类型。
import { Elysia, t } from 'elysia'
new Elysia()
.macro({
withFriends: {
body: t.Object({
friends: t.Tuple([t.Literal('Fouco'), t.Literal('Sartre')])
})
}
})
.post('/', ({ body }) => body.friends, {
body: t.Object({
name: t.Literal('Lilith')
}),
withFriends: true
})
带有模式的宏将自动进行校验并推断类型,确保类型安全,并且可以与现有的模式共存。
您也可以堆叠来自不同宏的多个模式,甚至与标准验证器配合使用,它们将无缝协作。
同一宏中的模式和生命周期
宏的模式还支持对同一宏内的生命周期进行类型推断,但由于 TypeScript 的限制,只支持带名称的单一宏。
import { Elysia, t } from 'elysia'
new Elysia()
.macro('withFriends', {
body: t.Object({
friends: t.Tuple([t.Literal('Fouco'), t.Literal('Sartre')])
}),
beforeHandle({ body: { friends } }) {
}
})
如果您想在同一宏内使用生命周期类型推断,建议使用命名的单一宏,而非多个叠加宏。
不要将此与使用宏模式推断路由生命周期事件中的类型混淆。后者运行良好,此限制仅针对在同一宏中使用生命周期。
扩展
宏可以扩展其他宏,允许您基于已有宏进行构建。
import { Elysia, t } from 'elysia'
new Elysia()
.macro({
sartre: {
body: t.Object({
sartre: t.Literal('Sartre')
})
},
fouco: {
body: t.Object({
fouco: t.Literal('Fouco')
})
},
lilith: {
fouco: true,
sartre: true,
body: t.Object({
lilith: t.Literal('Lilith')
})
}
})
.post('/', ({ body }) => body, {
lilith: true
})
这允许您基于已有宏构建,并为其添加更多功能。
去重
宏会自动去重生命周期事件,确保每个生命周期事件只执行一次。
默认情况下,Elysia 会使用属性值作为种子,但您也可以通过提供自定义种子来覆盖它。
import { Elysia, t } from 'elysia'
new Elysia()
.macro({
sartre: (role: string) => ({
seed: role,
body: t.Object({
sartre: t.Literal('Sartre')
})
})
})
不过,如果您不慎创建了循环依赖,Elysia 会有一个16层的限制堆栈,以防止运行时和类型推断中出现无限循环。
如果路由已经有 OpenAPI 详情,它将合并该详情,但优先保留路由的详情,而非宏的详情。