mcpField
E
elysia

elysia

github.com/epicenterhq开发4.8k55 次安装今天更新
代码仓库

关于 elysia

这项技能提供编写和重构 Elysia 处理器、响应架构及插件的约定,适用于需要类型化错误响应和统一路由行为的开发者,也涵盖 Eden Treaty 类型推断。

主要功能

  1. 01使用 status() 返回非 200 响应
  2. 02按状态码定义响应架构
  3. 03依据控制流选择 return 或 throw
  4. 04组合插件与共享认证守卫
  5. 05迁移 set.status 写法

适用场景

  1. 01重构 Elysia 错误处理器
  2. 02为 Eden Treaty 错误响应添加类型
  3. 03为多个路由添加共享认证
  4. 04构建不绑定前缀的插件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill elysia

安装后在 Claude Code 中输入/elysia确认已加载。

详细介绍

Elysia.js 模式(v1.2+)

参考仓库

  • Hono:适用于 Cloudflare Workers 的超高速 Web 框架
  • Cloudflare Docs:Cloudflare Workers、Durable Objects、KV 文档

上游依据

当 Worker 运行时行为、Hono 中间件语义、请求/响应流式处理、Durable Objects、WebSockets 或 Cloudflare 部署限制会影响正确性时,请先针对 honojs/hono 或 cloudflare/cloudflare-docs 向 DeepWiki 提出一个范围明确的问题,再依据记忆作答。先用它了解情况,然后在修改代码前,根据本地已安装的类型、源代码或官方文档核实关键细节。

对于下文已记录的 Elysia 特定约定,请跳过 DeepWiki。

何时应用此模式

在以下情况下使用此模式:

  • 编写或重构 Elysia 处理器,使其使用 status() 响应。
  • 为 Eden Treaty 类型安全定义按状态码区分的响应架构。
  • 将处理器从 set.status 加返回错误对象的写法迁移出去。
  • 组合 Elysia 插件/守卫,以共享身份验证和路由行为。
  • 根据控制流上下文,在 return status(...) 和 throw status(...) 之间进行选择。

status() 辅助函数(始终使用)

切勿使用 set.status 加返回对象。 始终从处理器上下文中解构 status,并将其用于所有非 200 响应。这样可以获得:

  • 带完整 IntelliSense 的类型安全字符串字面量(例如,使用 "Bad Request" 而不是 400)
  • 根据状态码自动推断响应类型
  • 错误响应的 Eden Treaty 端到端类型安全
基本用法
import { Elysia, t } from 'elysia';

new Elysia().post(
	'/chat',
	async ({ body, headers, status }) => {
		//                       ^^^^^^ destructure status from context

		if (!isValid(body.provider)) {
			// Use string literal for self-documenting, typesafe status codes
			return status('Bad Request', 'Unsupported provider');
		}

		if (!apiKey) {
			return status('Unauthorized', 'Missing API key');
		}

		return doWork(body);
	},
	{
		// Define response schemas per status code for full type safety
		response: {
			200: t.Any(),
			400: t.String(),
			401: t.String(),
		},
	},
);
return status() 与 throw status()

两种方式都可行。框架会处理其中任一种。区别仅在于控制流:

模式行为适用场景
return status(...)正常返回,继续进入响应处理管线正好需要返回时(验证守卫、处理器末尾)
throw status(...)立即中断执行位于较深的嵌套逻辑中,或在 try/catch 中需要提前退出时

本代码库的约定:优先使用 return status(...)。 这与其他地方普遍采用的遇错提前返回模式一致(参见 error-handling 技能)。仅在 catch 块或深层嵌套代码中使用 throw status(...),尤其是在使用 return 不方便时。

// GOOD: return for validation guards (matches codebase style)
async ({ body, status }) => {
	if (!isValid(body.provider)) {
		return status('Bad Request', `Unsupported provider: ${body.provider}`);
	}

	const apiKey = resolveApiKey(body.provider, headerApiKey);
	if (!apiKey) {
		return status('Unauthorized', 'Missing API key');
	}

	// happy path
	return doWork(body);
};

// GOOD: throw inside catch blocks
async ({ body, status }) => {
	try {
		return await streamResponse(body);
	} catch (error) {
		if (isAbortError(error)) {
			throw status(499, 'Client closed request');
		}
		throw status('Bad Gateway', `Provider error: ${error.message}`);
	}
};
两种写法的类型推断完全相同

return status(...) 和 throw status(...) 都会生成相同的 ElysiaCustomStatusResponse 对象。Elysia 的类型系统根据路由选项中的 response 架构推断响应类型,而非依据调用 status() 的方式。两种写法都能同样实现 Eden Treaty 类型安全。

可用的字符串状态码(StatusMap)

使用这些字符串字面量代替数字代码,可提高可读性:

字符串字面量代码常见用途
'Bad Request'400验证失败、输入格式错误
'Unauthorized'401缺少或无效的身份验证凭据
'Forbidden'403身份验证有效,但权限不足
'Not Found'404资源不存在
'Conflict'409状态冲突(重复、已存在)
'Unprocessable Content'422语义上无效的输入
'Too Many Requests'429速率限制
'Internal Server Error'500意外的服务端故障
'Bad Gateway'502上游提供方错误
'Service Unavailable'503临时过载/维护

对于非标准代码(例如 nginx 的 499),直接使用数字字面量:status(499, 'Client closed request')。

用于 Eden Treaty 类型安全的响应架构

在路由选项中按状态码定义 response 架构。这是 Eden Treaty 在客户端推断错误类型的依据:

new Elysia().post(
	'/chat',
	async ({ body, status }) => {
		if (!isValid(body.provider)) {
			return status('Bad Request', `Unsupported provider: ${body.provider}`);
		}
		return streamResult;
	},
	{
		body: t.Object({
			provider: t.String(),
			model: t.String(),
		}),
		response: {
			200: t.Any(), // Success type
			400: t.String(), // Bad Request body type
			401: t.String(), // Unauthorized body type
			502: t.String(), // Bad Gateway body type
		},
	},
);

随后 Eden Treaty 会推断出:

const { data, error } = await api.chat.post({
	provider: 'openai',
	model: 'gpt-4',
});

if (error) {
	// error.status is typed as 400 | 401 | 502
	// error.value is typed per status code (string in this case)
	switch (error.status) {
		case 400: // error.value: string
		case 401: // error.value: string
		case 502: // error.value: string
	}
}

错误响应正文:字符串与对象

错误正文优先使用纯字符串。 状态码已经说明了错误类别。使用描述性字符串消息即可,既足够清晰,也能让 API 保持简单。

// GOOD: Plain string - status code provides the category
return status('Bad Request', `Unsupported provider: ${provider}`);
return status('Unauthorized', 'Missing API key: set x-provider-api-key header');

// AVOID: Wrapping in { error: "..." } object - redundant with status code
set.status = 400;
return { error: `Unsupported provider: ${provider}` };

如果需要结构化的错误正文(多个字段、错误代码、验证详情),请定义 TypeBox 架构:

const ErrorBody = t.Object({
  message: t.String(),
  code: t.Optional(t.String()),
});

// In route options:
response: {
  400: ErrorBody,
  401: ErrorBody,
}

插件组合

Elysia 插件只是返回 Elysia 实例的函数。在插件中使用 new Elysia(),不要使用 new Elysia({ prefix }):由使用方控制挂载:

// GOOD: Plugin is prefix-agnostic
export function createMyPlugin() {
	return new Elysia().post('/endpoint', async ({ body, status }) => {
		// ...
	});
}

// Consumer controls the prefix
app.use(new Elysia({ prefix: '/api' }).use(createMyPlugin()));

用于共享身份验证的 Guards

对于适用于多个路由的身份验证,请使用 .guard() 和 beforeHandle:

const authed = new Elysia().guard({
	async beforeHandle({ headers, status }) {
		const token = extractBearerToken(headers.authorization);
		if (!isValid(token)) {
			return status('Unauthorized', 'Invalid or missing token');
		}
	},
});

// All routes under this guard require auth
return authed
	.get('/protected', () => 'secret')
	.post('/admin', () => 'admin stuff');

迁移检查清单:从 set.status 迁移到 status()

更新现有处理程序时:

  1. 在处理程序解构中将 set 替换为 status
  2. 将 set.status = N; return { error: msg }; 替换为 return status('String Literal', msg);
  3. 在 catch 块中,使用 throw status(...) 代替 set.status = N; return { error: msg };
  4. 在路由选项中添加 response 架构,以便 Eden Treaty 推断类型
  5. 仅当仍需使用 set.headers 设置 content-type 等内容时,才在解构中保留 set
// BEFORE
async ({ body, headers, set }) => {
	if (!valid) {
		set.status = 400;
		return { error: 'Bad input' };
	}
};

// AFTER
async ({ body, headers, status }) => {
	if (!valid) {
		return status('Bad Request', 'Bad input');
	}
};

// AFTER (when you also need set.headers)
async ({ body, headers, set, status }) => {
	if (!valid) {
		return status('Bad Request', 'Bad input');
	}
	set.headers['content-type'] = 'application/octet-stream';
	return binaryData;
};

评论

支持 Markdown