宏
Macro 类似于一个函数,它可以控制生命周期事件、模式和上下文,并具有完整的类型安全性。
一旦定义,它将在 hook 中可用,并可以通过添加属性来激活。
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 开始,macro 对象中的每个属性可以是函数或对象。
如果属性是一个对象,它将被转换为接受一个布尔参数的函数,如果参数为 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 具有与 hook 相同的 API。
在上一个示例中,我们创建了一个接受 string 的 hi macro。
然后我们将 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 还可以向上下文中注册一个新属性,允许我们直接从上下文中访问该值。
该字段可以接受从字符串到函数的任何内容,允许我们创建自定义生命周期事件。
macro 将按照 hook 中定义的从上到下的顺序执行,确保堆栈以正确的顺序处理。
Resolve
通过返回一个包含 resolve 函数的对象,将属性添加到上下文中。
import { Elysia } from 'elysia'
new Elysia()
.macro({
user: (enabled: true) => ({
resolve: () => ({
user: 'Pardofelis'
})
})
})
.get('/', ({ user }) => user, {
user: true
})
在上例中,我们通过返回一个包含 resolve 函数的对象,将一个新属性 user 添加到上下文中。
这是一个 macro resolve 可能有用的示例:
- 执行身份验证并将用户添加到上下文中
- 运行额外的数据库查询并将数据添加到上下文中
- 将新属性添加到上下文中
使用 resolve 的 Macro 扩展
由于 TypeScript 的限制,扩展其他 macro 的 macro 无法将类型推断到 resolve 函数中。
我们提供了一个命名单个 macro 作为此限制的变通方法。
import { Elysia, t } from 'elysia'
new Elysia()
.macro('user', {
resolve: () => ({
user: 'lilith' as const
})
})
.macro('user2', {
user: true,
resolve: ({ user }) => {
}
})
Schema
您可以为 macro 定义自定义模式,以确保使用 macro 的路由传递正确的类型。
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
})
带有 schema 的 Macro 将自动验证并推断类型以确保类型安全,并且它可以与现有 schema 共存。
您还可以堆叠来自不同 macro 的多个 schema,甚至来自 Standard Validator 的 schema,它们将无缝协作。
同一 Macro 中的 Schema 与生命周期
Macro schema 也支持 同一 Macro 内生命周期 的类型推断 但 仅限于命名单个 macro,由于 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 } }) {
}
})
如果您想在同一 macro 内使用生命周期类型推断,您可能想使用命名单个 macro 而不是多个堆叠 macro
不要与使用 macro schema 将类型推断到路由的生命周期事件混淆。那样做没问题,此限制仅适用于同一 macro 内的生命周期。
Extension
Macro 可以扩展其他 macro,允许您基于现有的进行构建。
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
})
这允许您基于现有 macro 进行构建,并为其添加更多功能。
Deduplication
Macro 将自动对生命周期事件进行去重,确保每个生命周期事件仅执行一次。
默认情况下,Elysia 将使用属性值作为种子,但您可以通过提供自定义种子来覆盖它。
import { Elysia, t } from 'elysia'
new Elysia()
.macro({
sartre: (role: string) => ({
seed: role,
body: t.Object({
sartre: t.Literal('Sartre')
})
})
})
但是,如果您意外创建了循环依赖,Elysia 有一个 16 的限制堆栈,以防止运行时和类型推断中的无限循环。
如果路由已经具有 OpenAPI 细节,它将合并细节,但优先使用路由细节而不是 macro 细节。