jax-hono 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/bin/jax.ts +68 -0
- package/build/build.ts +19 -0
- package/build/generate-config.ts +115 -0
- package/build/generate-registry.ts +615 -0
- package/config.ts +76 -0
- package/core/api-controller.ts +244 -0
- package/core/app.ts +199 -0
- package/core/controller.ts +86 -0
- package/core/hono.ts +64 -0
- package/core/service.ts +46 -0
- package/docs/jax.md +272 -0
- package/helpers/config.ts +23 -0
- package/helpers/crud.ts +30 -0
- package/helpers/index.ts +4 -0
- package/helpers/route.ts +7 -0
- package/index.ts +114 -0
- package/middleware/api-response.ts +44 -0
- package/middleware/jwt.ts +76 -0
- package/middleware/request-log.ts +3 -0
- package/package.json +64 -0
- package/plugins/config.ts +8 -0
- package/plugins/define.ts +5 -0
- package/plugins/mongoose/index.ts +57 -0
- package/plugins/mongoose/model.ts +281 -0
- package/plugins/mongoose/schema.ts +27 -0
- package/plugins/session/index.ts +20 -0
- package/setup.ts +11 -0
- package/types/config.ts +13 -0
- package/types/context.ts +10 -0
- package/types/index.ts +2 -0
- package/utils/array.ts +7 -0
- package/utils/crypto.ts +9 -0
- package/utils/index.ts +3 -0
- package/utils/regexp.ts +17 -0
- package/utils/transform.ts +3 -0
package/docs/jax.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Jax 框架说明
|
|
2
|
+
|
|
3
|
+
Jax 是当前项目内置且绑定 Hono 的轻量服务框架层,负责应用创建、路由注册、Controller/Service 基类、运行时上下文、统一响应、配置加载和代码生成。
|
|
4
|
+
|
|
5
|
+
业务代码建议从 `jax` 入口导入框架核心能力:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { ApiController, Service, createApi, config } from 'jax';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
工具、错误、类型等分类能力建议走子入口;统一响应类型从 `jax` 主入口导出:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { md5, createToken, escapeRegex } from 'jax/utils';
|
|
15
|
+
import { JaxHttpError } from 'jax/errors';
|
|
16
|
+
import type { ApiResponse } from 'jax';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
除非是在维护 Jax 内部代码,否则不要从 `src/jax/**` 文件路径导入。
|
|
20
|
+
|
|
21
|
+
## 目录结构
|
|
22
|
+
|
|
23
|
+
```txt
|
|
24
|
+
src/jax/
|
|
25
|
+
core/ # Hono runtime、生命周期、Controller/Service
|
|
26
|
+
middleware/ # 框架中间件
|
|
27
|
+
plugins/ # 插件定义、内置插件配置、内置插件实现
|
|
28
|
+
build/ # 代码生成/扫描注册
|
|
29
|
+
helpers/ # 框架辅助函数
|
|
30
|
+
utils/ # 更底层的纯工具
|
|
31
|
+
errors/ # 统一错误类型
|
|
32
|
+
types/ # 框架公共类型
|
|
33
|
+
generated/ # 自动生成的注册表
|
|
34
|
+
config.ts # 配置加载入口
|
|
35
|
+
index.ts # 对业务侧暴露的统一入口
|
|
36
|
+
setup.ts # 生成配置/路由/服务/模型注册表
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 分层边界
|
|
40
|
+
|
|
41
|
+
### `base`
|
|
42
|
+
|
|
43
|
+
放框架基类和请求上下文访问能力。
|
|
44
|
+
|
|
45
|
+
- `Controller`:Controller 基类,提供 `this.context`、`this.ctx`、`this.state`、`this.model`、`this.service`。
|
|
46
|
+
- `ApiController`:CRUD API Controller 基类,提供 `index/show/create/update/delete` 默认实现。
|
|
47
|
+
- `Service`:Service 基类,提供 `this.config`、`this.model`、`this.service` 和请求上下文访问。
|
|
48
|
+
|
|
49
|
+
业务 Controller 和 Service 应优先继承这些基类。
|
|
50
|
+
|
|
51
|
+
### `middleware`
|
|
52
|
+
|
|
53
|
+
只放框架级中间件,例如统一响应、日志、异常兜底等。
|
|
54
|
+
|
|
55
|
+
业务鉴权、后台权限、企业状态校验等应放在 `src/utils`、`src/router` 或业务模块中。
|
|
56
|
+
|
|
57
|
+
### `plugin`
|
|
58
|
+
|
|
59
|
+
放 Jax 内置插件,例如 Mongoose 插件。
|
|
60
|
+
|
|
61
|
+
插件配置参考 Egg:`src/jax/plugins/config.ts` 声明框架内置插件,业务项目通过 `src/config/plugin.ts` 显式启用或覆盖。
|
|
62
|
+
|
|
63
|
+
插件形态参考 Fastify:插件模块可以导出函数,也可以通过 `definePlugin` 导出对象。
|
|
64
|
+
|
|
65
|
+
插件生命周期参考 Adonis/Nuxt:对象插件支持 `setup`、`ready`、`close`。
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { definePlugin } from 'jax';
|
|
69
|
+
|
|
70
|
+
export default definePlugin({
|
|
71
|
+
async setup({ config }) {
|
|
72
|
+
// 初始化插件
|
|
73
|
+
},
|
|
74
|
+
async ready() {
|
|
75
|
+
// 插件初始化后执行
|
|
76
|
+
},
|
|
77
|
+
async close() {
|
|
78
|
+
// 应用退出前清理资源
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
未启用的插件不会写入 `src/jax/generated/plugins.generated.ts`,因此 `bun build` 不会解析它的运行时依赖。
|
|
84
|
+
|
|
85
|
+
### `build`
|
|
86
|
+
|
|
87
|
+
放代码生成和文件扫描逻辑。
|
|
88
|
+
|
|
89
|
+
当前 `bun run setup` 会生成:
|
|
90
|
+
|
|
91
|
+
- `src/jax/generated/config.ts`
|
|
92
|
+
- `src/jax/generated/controllers.generated.ts`
|
|
93
|
+
- `src/jax/generated/middlewares.generated.ts`
|
|
94
|
+
- `src/jax/generated/models.generated.ts`
|
|
95
|
+
- `src/jax/generated/plugins.generated.ts`
|
|
96
|
+
- `src/jax/generated/services.generated.ts`
|
|
97
|
+
|
|
98
|
+
业务代码不要手动编辑 `generated` 目录。
|
|
99
|
+
|
|
100
|
+
### `helpers`
|
|
101
|
+
|
|
102
|
+
放和 Jax 框架语义有关的辅助函数。
|
|
103
|
+
|
|
104
|
+
适合放这里的例子:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
normalizeRoutePath()
|
|
108
|
+
getPagination()
|
|
109
|
+
getClientIp()
|
|
110
|
+
assertRequestContext()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
如果函数依赖 Hono Context、Jax Context、路由、响应、Controller 生命周期等框架概念,优先放在 `helpers`。
|
|
114
|
+
|
|
115
|
+
### `utils`
|
|
116
|
+
|
|
117
|
+
放无框架依赖的纯工具函数。
|
|
118
|
+
|
|
119
|
+
适合放这里的例子:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
arrayFrom()
|
|
123
|
+
escapeRegex()
|
|
124
|
+
md5()
|
|
125
|
+
createToken()
|
|
126
|
+
isPlainObject()
|
|
127
|
+
safeJsonParse()
|
|
128
|
+
sleep()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
如果函数复制到任意 TypeScript 项目都成立,可以放在 `utils`。
|
|
132
|
+
|
|
133
|
+
### `errors`
|
|
134
|
+
|
|
135
|
+
放框架统一错误类型。
|
|
136
|
+
|
|
137
|
+
当前提供:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
throw new JaxHttpError('没有权限', 403);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
后续可以在这里扩展 `JaxValidationError`、`JaxAuthError` 等。
|
|
144
|
+
|
|
145
|
+
### `response`
|
|
146
|
+
|
|
147
|
+
放统一响应结构和响应构造函数。
|
|
148
|
+
|
|
149
|
+
当前 API 响应格式:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
{
|
|
153
|
+
code: 0,
|
|
154
|
+
msg: 'success',
|
|
155
|
+
data: {}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
失败响应格式:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
{
|
|
163
|
+
code: 1,
|
|
164
|
+
msg: '错误信息'
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
业务 API 中继续使用:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
return c.ok(data);
|
|
172
|
+
return c.fail('操作失败');
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `types`
|
|
176
|
+
|
|
177
|
+
放框架公共类型,例如 Jax 上下文变量、状态、扩展类型。
|
|
178
|
+
|
|
179
|
+
业务侧需要类型时优先从 `jax/types` 导入:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import type { JaxState } from 'jax/types';
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 业务代码推荐写法
|
|
186
|
+
|
|
187
|
+
### Controller
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { ApiController } from 'jax';
|
|
191
|
+
|
|
192
|
+
export default class CompanyController extends ApiController {
|
|
193
|
+
protected get Model() {
|
|
194
|
+
return this.model.Company;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Controller 中推荐使用:
|
|
200
|
+
|
|
201
|
+
- `this.model.Xxx` 访问模型
|
|
202
|
+
- `this.service.xxx` 访问服务
|
|
203
|
+
- `this.state` 访问请求状态
|
|
204
|
+
- `this.context` / `this.ctx` 访问 Hono Context
|
|
205
|
+
|
|
206
|
+
### Service
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { Service } from 'jax';
|
|
210
|
+
|
|
211
|
+
export default class CompanyService extends Service {
|
|
212
|
+
async getCompanyInfo(companyId: string) {
|
|
213
|
+
return this.model.Company.findById(companyId).lean();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Service 中推荐使用:
|
|
219
|
+
|
|
220
|
+
- `this.config` 访问配置
|
|
221
|
+
- `this.model.Xxx` 访问模型
|
|
222
|
+
- `this.service.xxx` 访问其他服务
|
|
223
|
+
|
|
224
|
+
不建议在业务文件里直接 import 具体 model 或 service,除非是在做类型声明、纯工具函数或非常明确的底层实现。
|
|
225
|
+
|
|
226
|
+
## 什么应该放进 Jax
|
|
227
|
+
|
|
228
|
+
可以放入 Jax:
|
|
229
|
+
|
|
230
|
+
- 所有项目都可复用的框架能力
|
|
231
|
+
- Hono/Jax 上下文相关 helper
|
|
232
|
+
- Controller/Service 生命周期相关能力
|
|
233
|
+
- 统一响应、错误、分页、路由注册
|
|
234
|
+
- 代码生成、自动注册、配置加载
|
|
235
|
+
|
|
236
|
+
不建议放入 Jax:
|
|
237
|
+
|
|
238
|
+
- 企业、车辆、危废、后台账号等业务概念
|
|
239
|
+
- 某个接口专用的数据转换
|
|
240
|
+
- 某个业务模块专属常量
|
|
241
|
+
- 和当前项目强绑定的鉴权策略
|
|
242
|
+
|
|
243
|
+
简单判断:
|
|
244
|
+
|
|
245
|
+
> 换一个 Jax 项目仍然成立,放 `src/jax`。只在这个业务里成立,放 `src/utils` 或具体业务模块。
|
|
246
|
+
|
|
247
|
+
## 扩展规范
|
|
248
|
+
|
|
249
|
+
新增框架能力时优先按下面顺序判断位置:
|
|
250
|
+
|
|
251
|
+
1. 是 Hono runtime、生命周期、路由扩展或基类,放 `core`。
|
|
252
|
+
2. 是框架中间件,放 `middleware`。
|
|
253
|
+
3. 是插件定义、内置插件配置或内置插件实现,放 `plugins`。
|
|
254
|
+
4. 是框架语义 helper,放 `helpers`。
|
|
255
|
+
4. 是纯函数工具,放 `utils`。
|
|
256
|
+
5. 是错误类型,放 `errors`。
|
|
257
|
+
6. 是响应结构,放 `response`。
|
|
258
|
+
7. 是公共类型,放 `types`。
|
|
259
|
+
|
|
260
|
+
新增文件后,如果业务侧需要使用,应优先在对应子入口导出,例如 `src/jax/utils/index.ts`、`src/jax/errors/index.ts`。只有框架核心 API 才放到 `src/jax/index.ts`。
|
|
261
|
+
|
|
262
|
+
内置插件配置放在 `src/jax/plugins/config.ts`,内置插件实现放在 `src/jax/plugins/*`,项目级插件通过 `src/config/plugin.ts` 显式启用或覆盖。生成器只读取插件配置,不扫描插件目录。
|
|
263
|
+
|
|
264
|
+
## 常用命令
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
bun run setup
|
|
268
|
+
bun run dev
|
|
269
|
+
bun run build
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`setup` 会重新扫描并生成 Jax 注册表;新增 model、service、controller、middleware、config 后建议先执行一次。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { JaxConfig } from '../types/config';
|
|
2
|
+
|
|
3
|
+
type PlainConfig = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export type PluginConfigItem =
|
|
6
|
+
| false
|
|
7
|
+
| {
|
|
8
|
+
enable?: boolean;
|
|
9
|
+
package?: string;
|
|
10
|
+
options?: unknown;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type PluginConfig = Record<string, PluginConfigItem>;
|
|
14
|
+
|
|
15
|
+
export function defineConfig<TProjectConfig extends PlainConfig = PlainConfig>(
|
|
16
|
+
config: Partial<JaxConfig & TProjectConfig>
|
|
17
|
+
) {
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function definePluginConfig<TPluginConfig extends PluginConfig>(config: TPluginConfig) {
|
|
22
|
+
return config;
|
|
23
|
+
}
|
package/helpers/crud.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { normalizeRoutePath } from './route';
|
|
2
|
+
import type { Handler, Hono } from 'hono';
|
|
3
|
+
|
|
4
|
+
export type JaxCrudController = {
|
|
5
|
+
index: Handler;
|
|
6
|
+
show: Handler;
|
|
7
|
+
create: Handler;
|
|
8
|
+
update: Handler;
|
|
9
|
+
delete?: Handler;
|
|
10
|
+
destroy?: Handler;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function registerCrudRoutes(app: Hono, path: string, controller: JaxCrudController) {
|
|
14
|
+
const basePath = normalizeRoutePath(path);
|
|
15
|
+
const detailPath = basePath === '/' ? '/:id' : `${basePath}/:id`;
|
|
16
|
+
const deleteHandler = controller.delete ?? controller.destroy;
|
|
17
|
+
|
|
18
|
+
app.get(basePath, controller.index);
|
|
19
|
+
app.get(detailPath, controller.show);
|
|
20
|
+
app.post(basePath, controller.create);
|
|
21
|
+
app.put(detailPath, controller.update);
|
|
22
|
+
|
|
23
|
+
if (!deleteHandler) {
|
|
24
|
+
throw new TypeError(`CRUD controller for ${basePath} must define delete or destroy.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
app.delete(detailPath, deleteHandler);
|
|
28
|
+
|
|
29
|
+
return app;
|
|
30
|
+
}
|
package/helpers/index.ts
ADDED
package/helpers/route.ts
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { createHono, createApi } from './core/hono';
|
|
3
|
+
import { registerCrudRoutes } from './helpers/crud';
|
|
4
|
+
import type { Config } from './config';
|
|
5
|
+
import type { controllers as generatedControllers } from './generated/controllers.generated';
|
|
6
|
+
import type { models as generatedModels } from './generated/models.generated';
|
|
7
|
+
import type { createServices as generatedCreateServices } from './generated/services.generated';
|
|
8
|
+
import type * as AppModule from './core/app';
|
|
9
|
+
|
|
10
|
+
type AppRuntimeModule = typeof AppModule;
|
|
11
|
+
type Controllers = typeof generatedControllers;
|
|
12
|
+
type Models = typeof generatedModels;
|
|
13
|
+
type CreateServices = typeof generatedCreateServices;
|
|
14
|
+
|
|
15
|
+
let appRuntimeModule: AppRuntimeModule | undefined;
|
|
16
|
+
let controllersModule: { controllers: Controllers } | undefined;
|
|
17
|
+
let modelsModule: { models: Models } | undefined;
|
|
18
|
+
let servicesModule: { createServices: CreateServices } | undefined;
|
|
19
|
+
let configModule: { default: Config } | undefined;
|
|
20
|
+
|
|
21
|
+
function getAppRuntimeModule() {
|
|
22
|
+
appRuntimeModule ??= require('./core/app') as AppRuntimeModule;
|
|
23
|
+
|
|
24
|
+
return appRuntimeModule;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getControllers() {
|
|
28
|
+
controllersModule ??= require('./generated/controllers.generated') as {
|
|
29
|
+
controllers: Controllers;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return controllersModule.controllers;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getModels() {
|
|
36
|
+
modelsModule ??= require('./generated/models.generated') as { models: Models };
|
|
37
|
+
|
|
38
|
+
return modelsModule.models;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getCreateServices() {
|
|
42
|
+
servicesModule ??= require('./generated/services.generated') as {
|
|
43
|
+
createServices: CreateServices;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return servicesModule.createServices;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getConfig() {
|
|
50
|
+
configModule ??= require('./config') as { default: Config };
|
|
51
|
+
|
|
52
|
+
return configModule.default;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { createHono, createApi, registerCrudRoutes };
|
|
56
|
+
export { defineConfig, definePluginConfig } from './helpers/config';
|
|
57
|
+
export { definePlugin } from './plugins/define';
|
|
58
|
+
export type { AppContext, AppPlugin } from './core/app';
|
|
59
|
+
export type { Config, JaxConfig } from './config';
|
|
60
|
+
export { ApiController } from './core/api-controller';
|
|
61
|
+
export { Controller } from './core/controller';
|
|
62
|
+
export { Service } from './core/service';
|
|
63
|
+
export type { ServiceContext } from './core/service';
|
|
64
|
+
export type { Services } from './generated/services.generated';
|
|
65
|
+
export type { ApiFailResponse, ApiResponse, ApiSuccessResponse } from './middleware/api-response';
|
|
66
|
+
|
|
67
|
+
export function createApp() {
|
|
68
|
+
return getAppRuntimeModule().createApp({
|
|
69
|
+
models: getModels(),
|
|
70
|
+
createServices: getCreateServices()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const controllers = new Proxy({} as Controllers, {
|
|
75
|
+
get(_target, property, receiver) {
|
|
76
|
+
return Reflect.get(getControllers(), property, receiver);
|
|
77
|
+
},
|
|
78
|
+
has(_target, property) {
|
|
79
|
+
return property in getControllers();
|
|
80
|
+
},
|
|
81
|
+
ownKeys() {
|
|
82
|
+
return Reflect.ownKeys(getControllers());
|
|
83
|
+
},
|
|
84
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
85
|
+
return Object.getOwnPropertyDescriptor(getControllers(), property);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const models = new Proxy({} as Models, {
|
|
90
|
+
get(_target, property, receiver) {
|
|
91
|
+
return Reflect.get(getModels(), property, receiver);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const createServices: CreateServices = ((deps: unknown) => {
|
|
96
|
+
return getCreateServices()(deps);
|
|
97
|
+
}) as CreateServices;
|
|
98
|
+
|
|
99
|
+
export const config = new Proxy({} as Config, {
|
|
100
|
+
get(_target, property, receiver) {
|
|
101
|
+
return Reflect.get(getConfig(), property, receiver);
|
|
102
|
+
},
|
|
103
|
+
has(_target, property) {
|
|
104
|
+
return property in getConfig();
|
|
105
|
+
},
|
|
106
|
+
ownKeys() {
|
|
107
|
+
return Reflect.ownKeys(getConfig());
|
|
108
|
+
},
|
|
109
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
110
|
+
return Object.getOwnPropertyDescriptor(getConfig(), property);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export { Hono };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
|
+
|
|
3
|
+
export type ApiSuccessResponse<T = unknown> = {
|
|
4
|
+
code: 0;
|
|
5
|
+
msg: string;
|
|
6
|
+
data?: T;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ApiFailResponse = {
|
|
10
|
+
code: 1;
|
|
11
|
+
msg: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiFailResponse;
|
|
15
|
+
|
|
16
|
+
export function ok<T>(c: Context, data?: T, msg = 'success') {
|
|
17
|
+
return c.json<ApiSuccessResponse<T>>({
|
|
18
|
+
code: 0,
|
|
19
|
+
msg,
|
|
20
|
+
data
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function fail(c: Context, msg: string) {
|
|
25
|
+
return c.json<ApiFailResponse>({
|
|
26
|
+
code: 1,
|
|
27
|
+
msg
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module 'hono' {
|
|
32
|
+
interface Context {
|
|
33
|
+
ok: <T>(data?: T, msg?: string) => Response;
|
|
34
|
+
fail: (msg: string) => Response;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const apiResponse: MiddlewareHandler = async (c, next) => {
|
|
39
|
+
// 把统一响应方法挂到 Hono Context 上,业务接口直接使用 c.ok / c.fail。
|
|
40
|
+
c.ok = <T>(data?: T, msg = 'success') => ok(c, data, msg);
|
|
41
|
+
c.fail = (msg: string) => fail(c, msg);
|
|
42
|
+
|
|
43
|
+
await next();
|
|
44
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import config from '../config';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import type { Context, MiddlewareHandler, Next } from 'hono';
|
|
4
|
+
|
|
5
|
+
type JwtAuthOptions<TPayload extends Record<string, any> = Record<string, any>> = {
|
|
6
|
+
secretSuffix: string;
|
|
7
|
+
stateKey: string;
|
|
8
|
+
validate?: (payload: TPayload, c: Context) => boolean | Promise<boolean>;
|
|
9
|
+
resolveState?: (payload: TPayload, c: Context) => unknown | Promise<unknown>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function createJwtAuth<TPayload extends Record<string, any> = Record<string, any>>(
|
|
13
|
+
options: JwtAuthOptions<TPayload>
|
|
14
|
+
): MiddlewareHandler {
|
|
15
|
+
return async (c, next) => {
|
|
16
|
+
const session = c.get('session');
|
|
17
|
+
const sessionData = session.get(options.stateKey);
|
|
18
|
+
|
|
19
|
+
if (sessionData) {
|
|
20
|
+
console.log('sessionData', sessionData);
|
|
21
|
+
c.state[options.stateKey] = sessionData;
|
|
22
|
+
await next();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const token = getAuthorizationToken(c);
|
|
26
|
+
|
|
27
|
+
if (!token) {
|
|
28
|
+
return c.json({ code: 401, msg: '请先登录' });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const payload = jwt.verify(
|
|
33
|
+
token,
|
|
34
|
+
(config as any).jwt.secret + options.secretSuffix
|
|
35
|
+
) as TPayload;
|
|
36
|
+
const valid = options.validate ? await options.validate(payload, c) : true;
|
|
37
|
+
|
|
38
|
+
if (!valid) {
|
|
39
|
+
return c.json({ code: 401, msg: '请重新登录' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const stateValue = options.resolveState ? await options.resolveState(payload, c) : payload;
|
|
43
|
+
|
|
44
|
+
if (!stateValue) {
|
|
45
|
+
return c.json({ code: 401, msg: '请重新登录' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
c.state[options.stateKey] = stateValue;
|
|
49
|
+
|
|
50
|
+
await next();
|
|
51
|
+
} catch {
|
|
52
|
+
return c.json({ code: 401, msg: '请重新登录' });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createOptionalAuth(auth: MiddlewareHandler): MiddlewareHandler {
|
|
58
|
+
return async (c, next) => {
|
|
59
|
+
if (!c.req.header('authorization')) {
|
|
60
|
+
await next();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return auth(c, next as Next);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getAuthorizationToken(c: Context) {
|
|
69
|
+
const authorization = c.req.header('authorization') ?? '';
|
|
70
|
+
|
|
71
|
+
return authorization.startsWith('Bearer ')
|
|
72
|
+
? authorization.slice('Bearer '.length)
|
|
73
|
+
: authorization;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { createJwtAuth, createOptionalAuth, getAuthorizationToken };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jax-hono",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight framework layer on top of Hono, built for Bun",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"types": "./index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.ts",
|
|
11
|
+
"types": "./index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./*": "./*"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"jax": "./bin/jax.ts"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.ts",
|
|
20
|
+
"setup.ts",
|
|
21
|
+
"config.ts",
|
|
22
|
+
"bin/",
|
|
23
|
+
"core/",
|
|
24
|
+
"middleware/",
|
|
25
|
+
"plugins/",
|
|
26
|
+
"helpers/",
|
|
27
|
+
"utils/",
|
|
28
|
+
"types/",
|
|
29
|
+
"build/",
|
|
30
|
+
"docs/",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "echo 'jax-hono requires Bun. Visit https://bun.sh to install.'"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@hono/session": "^0.2.1",
|
|
38
|
+
"cac": "^7.0.0",
|
|
39
|
+
"hono": "^4.12.27"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"dayjs": "*",
|
|
43
|
+
"deepmerge": "*",
|
|
44
|
+
"dotenv": "*",
|
|
45
|
+
"jsonwebtoken": "*",
|
|
46
|
+
"minimist": "*",
|
|
47
|
+
"mongoose": "*"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"dayjs": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"mongoose": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"jsonwebtoken": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"bun": ">=1.0.0"
|
|
62
|
+
},
|
|
63
|
+
"license": "MIT"
|
|
64
|
+
}
|