Skip to content
Our Sponsors
Open in Anthropic

title: 扩展上下文 - ElysiaJS head: - - meta - property: 'og:title' content: 扩展上下文 - ElysiaJS

- - meta
  - name: 'description'
    content: Elysia 默认提供一个最小化的上下文,允许我们使用 state、decorate、derive 和 resolve 根据特定需求扩展上下文。

- - meta
  - property: 'og:description'
    content: Elysia 默认提供一个最小化的上下文,允许我们使用 state、decorate、derive 和 resolve 根据特定需求扩展上下文。

扩展上下文 高级概念

Elysia 默认提供一个最小化的上下文,允许我们使用 state、decorate、derive 和 resolve 根据特定需求扩展上下文。

Elysia 允许我们为各种用例扩展上下文,例如:

  • 将用户 ID 提取为变量
  • 注入通用模式仓库
  • 添加数据库连接

我们可以使用以下 API 扩展 Elysia 的上下文来自定义上下文:

何时扩展上下文

你应该仅在以下情况下扩展上下文:

  • 属性是全局可变状态,并使用 state 在多个路由间共享
  • 属性与请求或响应相关联,使用 decorate
  • 属性从现有属性派生,使用 derive / resolve

否则,我们建议单独定义值或函数,而不是扩展上下文。

TIP

建议将与请求和响应相关的属性或频繁使用的函数分配给上下文,以实现关注点分离。

State

State 是在整个 Elysia 应用中共享的全局可变对象或状态。

一旦调用 state,值将在调用时添加到 store 属性一次,并可在处理程序中使用。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
state
('version', 1)
.
get
('/a', ({
store
: {
version
} }) =>
version
)
.
get
('/b', ({
store
}) =>
store
)
.
get
('/c', () => 'still ok')
.
listen
(3000)
localhost

GET

何时使用

  • 当你需要跨多个路由共享原始可变值时
  • 如果你想使用非原始值或会改变内部状态的 wrapper 值或类,请改用 decorate

关键要点

  • store 是整个 Elysia 应用的单一事实来源的全局可变对象的表示。
  • state 是为 store 分配初始值的函数,该值稍后可以被修改。
  • 确保在处理程序中使用之前分配一个值。
typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
// ❌ TypeError: counter 不存在于 store 中 .
get
('/error', ({
store
}) =>
store
.counter)
Property 'counter' does not exist on type '{}'.
.
state
('counter', 0)
// ✅ 因为我们之前分配了 counter,现在可以访问它 .
get
('/', ({
store
}) =>
store
.
counter
)
localhost

GET

TIP

注意我们不能在分配之前使用状态值。

Elysia 会自动将状态值注册到 store 中,无需显式类型或额外的 TypeScript 泛型。

引用和值 陷阱

要修改状态,建议使用引用来修改,而不是使用实际值。

在 JavaScript 中访问属性时,如果我们从对象属性定义一个原始值作为新值,引用会丢失,该值将被视为新的独立值。

例如:

typescript
const store = {
    counter: 0
}

store.counter++
console.log(store.counter) // ✅ 1

我们可以使用 store.counter 来访问和修改属性。

但是,如果我们将 counter 定义为新值

typescript
const store = {
    counter: 0
}

let counter = store.counter

counter++
console.log(store.counter) // ❌ 0
console.log(counter) // ✅ 1

一旦原始值被重新定义为新变量,引用**"链接"**将丢失,导致意外行为。

这可以应用于 store,因为它也是一个全局可变对象。

typescript
import { Elysia } from 'elysia'

new Elysia()
    .state('counter', 0)
    // ✅ 使用引用,值是共享的
    .get('/', ({ store }) => store.counter++)
    // ❌ 在原始值上创建新变量,链接丢失
    .get('/error', ({ store: { counter } }) => counter)
localhost

GET

Decorate

decorate 在调用时直接为上下文分配附加属性。

typescript
import { 
Elysia
} from 'elysia'
class
Logger
{
log
(
value
: string) {
console
.
log
(
value
)
} } new
Elysia
()
.
decorate
('logger', new
Logger
())
// ✅ 从前一行定义 .
get
('/', ({
logger
}) => {
logger
.
log
('hi')
return 'hi' })

何时使用

  • 将常量或只读值对象分配给上下文
  • 可能包含内部可变状态的非原始值或类
  • 为所有处理程序添加附加函数、单例或不可变属性。

关键要点

  • state 不同,装饰后的值不应该被修改,尽管这是可能的
  • 确保在处理程序中使用之前分配一个值。

Derive

⚠️ Derive 不处理类型完整性,你可能想要使用 resolve 代替。

上下文中的现有属性检索值并分配新属性。

Derive 在请求发生时在转换生命周期分配,允许我们"派生" (从现有属性创建新属性)

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
derive
(({
headers
}) => {
const
auth
=
headers
['authorization']
return {
bearer
:
auth
?.
startsWith
('Bearer ') ?
auth
.
slice
(7) : null
} }) .
get
('/', ({
bearer
}) =>
bearer
)
localhost

GET

因为 derive 在新请求开始时分配一次,derive 可以访问请求属性,如 headersquerybody,而 storedecorate 不能。

何时使用

  • 上下文中的现有属性创建新属性,无需验证或类型检查
  • 当你需要访问请求属性,如 headersquerybody 而无需验证时

关键要点

  • statedecorate 不同,不是在调用时分配,derive 在新请求开始时分配。
  • derive 在转换时或验证发生之前调用,Elysia 无法安全确认请求属性的类型,结果为 unknown。如果你想从类型化的请求属性分配新值,你可能想要使用 resolve 代替。

Resolve

类似于 derive 但确保类型完整性。

Resolve 允许我们为上下文分配新属性。

Resolve 在 beforeHandle 生命周期或验证之后调用,允许我们安全地解析请求属性。

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
guard
({
headers
:
t
.
Object
({
bearer
:
t
.
String
({
pattern
: '^Bearer .+$'
}) }) }) .
resolve
(({
headers
}) => {
return {
bearer
:
headers
.
bearer
.
slice
(7)
} }) .
get
('/', ({
bearer
}) =>
bearer
)

何时使用

  • 上下文中的现有属性创建新属性,具有类型完整性(类型检查)
  • 当你需要访问请求属性,如 headersquerybody 并进行验证时

关键要点

  • resolve 在 beforeHandle 或验证发生时调用。Elysia 可以安全确认请求属性的类型,结果为类型化的

来自 resolve/derive 的错误

由于 resolve 和 derive 基于 transformbeforeHandle 生命周期,我们可以从 resolve 和 derive 返回错误。如果错误从 derive 返回,Elysia 将提前退出并将错误作为响应返回。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
derive
(({
headers
,
status
}) => {
const
auth
=
headers
['authorization']
if(!
auth
) return
status
(400)
return {
bearer
:
auth
?.
startsWith
('Bearer ') ?
auth
.
slice
(7) : null
} }) .
get
('/', ({
bearer
}) =>
bearer
)

模式 高级概念

statedecorate 为向上下文分配属性提供了类似的 API 模式,如下所示:

  • 键值对
  • 对象
  • 重映射

其中 derive 只能与重映射一起使用,因为它依赖于现有值。

键值对

我们可以使用 statedecorate 通过键值对模式分配值。

typescript
import { Elysia } from 'elysia'

class Logger {
    log(value: string) {
        console.log(value)
    }
}

new Elysia()
    .state('counter', 0)
    .decorate('logger', new Logger())

这种模式对于设置单个属性的可读性很有好处。

对象

分配多个属性最好包含在一个对象中进行单次分配。

typescript
import { Elysia } from 'elysia'

new Elysia()
    .decorate({
        logger: new Logger(),
        trace: new Trace(),
        telemetry: new Telemetry()
    })

对象为设置多个值提供了更少重复的 API。

重映射

重映射是函数重新赋值。

允许我们从现有值创建新值,如重命名或删除属性。

通过提供一个函数,并返回一个全新的对象来重新赋值。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
state
('counter', 0)
.
state
('version', 1)
.
state
(({
version
, ...
store
}) => ({
...
store
,
elysiaVersion
: 1
})) // ✅ 从状态重映射创建 .
get
('/elysia-version', ({
store
}) =>
store
.
elysiaVersion
)
// ❌ 从状态重映射中排除 .
get
('/version', ({
store
}) =>
store
.version)
Property 'version' does not exist on type '{ elysiaVersion: number; counter: number; }'.
localhost

GET

使用状态重映射从现有值创建新初始值是个好主意。

但是,需要注意的是,Elysia 不提供此方法的响应性,因为重映射只分配初始值。

TIP

使用重映射时,Elysia 会将返回的对象视为新属性,删除对象中缺失的任何属性。

Affix 高级概念

为了提供更流畅的体验,一些插件可能有很多属性值,逐一重映射可能会让人不知所措。

Affix 函数由 prefixsuffix 组成,允许我们重映射实例的所有属性。

ts
import { 
Elysia
} from 'elysia'
const
setup
= new
Elysia
({
name
: 'setup' })
.
decorate
({
argon
: 'a',
boron
: 'b',
carbon
: 'c'
}) const
app
= new
Elysia
()
.
use
(
setup
)
.
prefix
('decorator', 'setup')
.
get
('/', ({
setupCarbon
, ...
rest
}) =>
setupCarbon
)
localhost

GET

允许我们轻松地批量重映射插件的属性,防止插件的名称冲突。

默认情况下,affix 将自动处理运行时和类型级别的代码,将属性重映射为驼峰命名法作为命名约定。

在某些情况下,我们也可以重映射插件的 all 属性:

ts
import { 
Elysia
} from 'elysia'
const
setup
= new
Elysia
({
name
: 'setup' })
.
decorate
({
argon
: 'a',
boron
: 'b',
carbon
: 'c'
}) const
app
= new
Elysia
()
.
use
(
setup
)
.
prefix
('all', 'setup')
.
get
('/', ({
setupCarbon
, ...
rest
}) =>
setupCarbon
)