主题
宏
¥Macro
宏类似于一个函数,可以控制生命周期事件、Schema 和上下文,并具有完全的类型安全性。
¥Macro is similar to a function that have a control over the lifecycle event, schema, context with full type safety.
一旦定义,它将在钩子中可用,并可通过添加属性来激活。
¥Once defined, it will be available in hook and can be activated by adding the property.
typescript
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" 记录为结果。
¥Accessing the path should log "Elysia" as the results.
属性简写
¥Property shorthand
从 Elysia 1.2.10 版本开始,宏对象中的每个属性可以是函数或对象。
¥Starting from Elysia 1.2.10, each property in the macro object can be a function or an object.
如果属性是对象,它将被转换为接受布尔参数的函数,并在参数为 true 时执行该函数。
¥If the property is an object, it will be translated to a function that accept a boolean parameter, and will be executed if the parameter is true.
typescript
import { Elysia } from 'elysia'
export const auth = new Elysia()
.macro({
// This property shorthand
isAuth: {
resolve: () => ({
user: 'saltyaom'
})
},
// is equivalent to
isAuth(enabled: boolean) {
if(!enabled) return
return {
resolve() {
return {
user
}
}
}
}
})
API
宏与钩子具有相同的 API。
¥macro has the same API as hook.
在前面的示例中,我们创建了一个接受字符串的 hi 宏。
¥In previous example, we create a hi macro accepting a string.
然后,我们将 hi 赋值给 "Elysia",该值被返回给 hi 函数,之后该函数向 beforeHandle 堆栈添加了一个新事件。
¥We then assigned hi to "Elysia", the value was then sent back to the hi function, and then the function added a new event to beforeHandle stack.
这相当于将函数推送到 beforeHandle,如下所示:
¥Which is an equivalent of pushing function to beforeHandle as the following:
typescript
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'hi', {
beforeHandle() {
console.log('Elysia')
}
})
当逻辑比接受新函数更复杂时,宏会大放异彩,例如为每个路由创建一个授权层。
¥macro shine when a logic is more complex than accepting a new function, for example creating an authorization layer for each route.
typescript
import { Elysia } from 'elysia'
import { auth } from './auth'
const app = new Elysia()
.use(auth)
.get('/', ({ user }) => user, {
isAuth: true,
role: 'admin'
})
宏还可以向上下文注册一个新属性,从而允许我们直接从上下文访问该属性的值。
¥Macro can also register a new property to the context, allowing us to access the value directly from the context.
该字段可以接受从字符串到函数的任何内容,这使我们能够创建自定义生命周期事件。
¥The field can accept anything ranging from string to function, allowing us to create a custom life cycle event.
宏将根据钩子中的定义从上到下按顺序执行,确保堆栈以正确的顺序处理。
¥macro will be executed in order from top-to-bottom according to definition in hook, ensure that the stack is handled in the correct order.
解析
¥Resolve
通过返回带有 resolve 函数的对象,可以向上下文添加属性。
¥You add a property to the context by returning an object with a resolve function.
ts
import { Elysia } from 'elysia'
new Elysia()
.macro({
user: (enabled: true) => ({
resolve: () => ({
user: 'Pardofelis'
})
})
})
.get('/', ({ user }) => user, {
user: true
})
在上面的例子中,我们通过返回一个带有 resolve 函数的对象,向上下文中添加了一个新的属性 user。
¥In the example above, we add a new property user to the context by returning an object with a resolve function.
以下是宏解析可能有用的示例:
¥Here's an example that macro resolve could be useful:
执行身份验证并将用户添加到上下文
运行额外的数据库查询并将数据添加到上下文
向上下文添加新属性
带解析的宏扩展
¥Macro extension with resolve
由于 TypeScript 的限制,扩展其他宏的宏无法将类型推断到 resolve 函数中。
¥Due to TypeScript limitation, macro that extends other macro cannot infer type into resolve function.
我们提供了一个命名的单宏作为解决此限制的解决方法。
¥We provide a named single macro as a workaround to this limitation.
typescript
import { Elysia, t } from 'elysia'
new Elysia()
.macro('user', {
resolve: () => ({
user: 'lilith' as const
})
})
.macro('user2', {
user: true,
resolve: ({ user }) => {
}
})
Schema
你可以为宏定义自定义架构,以确保使用该宏的路由传递正确的类型。
¥You can define a custom schema for your macro, to make sure that the route using the macro is passing the correct type.
typescript
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 的宏会自动验证和推断类型以确保类型安全,并且它可以与现有 Schema 共存。
¥Macro with schema will automatically validate and infer type to ensure type safety, and it can co-exist with existing schema as well.
你还可以堆叠来自不同宏的多个模式,甚至来自标准验证器,它们将无缝协作。
¥You can also stack multiple schema from different macro, or even from Standard Validator and it will work together seamlessly.
Schema 生命周期在同一个宏中
¥Schema with lifecycle in the same macro
与 带解析的宏扩展 类似,
¥Similar to Macro extension with resolve,
宏 Schema 也支持同一宏内生命周期的类型推断,但由于 TypeScript 的限制,仅限于命名单个宏。
¥Macro schema also support type inference for lifecycle within the same macro BUT only with named single macro due to TypeScript limitation.
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 } }) {
}
})
如果你想在同一个宏中使用生命周期类型推断,你可能需要使用命名的单个宏而不是多个堆叠宏。
¥If you want to use lifecycle type inference within the same macro, you might want to use a named single macro instead of multiple stacked macro
不要与使用宏模式推断路由生命周期事件的类型相混淆。这样就很好了,但这个限制仅适用于在同一个宏中使用生命周期。
扩展
¥Extension
宏可以扩展其他宏,允许你在现有宏的基础上进行构建。
¥Macro can extends other macro, allowing you to build upon existing one.
typescript
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
})
这允许你在现有宏的基础上构建并添加更多功能。
¥This allow you to build upon existing macro, and add more functionality to it.
数据去重
¥Deduplication
宏会自动删除重复的生命周期事件,确保每个生命周期事件只执行一次。
¥Macro will automatically deduplicate the lifecycle event, ensuring that each lifecycle event is only executed once.
默认情况下,Elysia 将使用属性值作为种子,但你可以通过提供自定义种子来覆盖它。
¥By default, Elysia will use the property value as the seed, but you can override it by providing a custom seed.
typescript
import { Elysia, t } from 'elysia'
new Elysia()
.macro({
sartre: (role: string) => ({
seed: role,
body: t.Object({
sartre: t.Literal('Sartre')
})
})
})
但是,如果你意外创建了循环依赖,Elysia 的限制堆栈为 16,以防止运行时和类型推断中的无限循环。
¥However, if you evert accidentally create a circular dependency, Elysia have a limit stack of 16 to prevent infinite loop in both runtime and type inference.
如果路由已包含 OpenAPI 详细信息,它会将这些详细信息合并在一起,但优先使用路由详细信息而不是宏详细信息。
¥If the route already has OpenAPI detail, it will merge the detail together but prefers the route detail over macro detail.