jax-hono 1.0.7 → 1.0.9
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/core/api-controller.ts +87 -37
- package/helpers/index.ts +4 -4
- package/package.json +5 -2
- package/utils/async.ts +47 -0
- package/utils/index.ts +5 -3
package/core/api-controller.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import { Controller } from
|
|
2
|
-
import { arrayFrom } from
|
|
3
|
-
import { escapeRegex } from
|
|
4
|
-
import type { Context } from
|
|
1
|
+
import { Controller } from "./controller";
|
|
2
|
+
import { arrayFrom } from "../utils/array";
|
|
3
|
+
import { escapeRegex } from "../utils/regexp";
|
|
4
|
+
import type { Context } from "hono";
|
|
5
|
+
import { omit, pick, queue } from "jax-hono/utils";
|
|
5
6
|
|
|
6
7
|
const bodyCache = new WeakMap<Context, unknown>();
|
|
7
8
|
|
|
8
9
|
export abstract class ApiController extends Controller {
|
|
10
|
+
declare protected omit?: string | string[];
|
|
11
|
+
declare protected pick?: string | string[];
|
|
12
|
+
declare protected output?: string | string[];
|
|
13
|
+
declare protected json?: string | string[];
|
|
14
|
+
|
|
9
15
|
protected get Model(): any {
|
|
10
16
|
return undefined;
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
protected get key() {
|
|
14
|
-
return
|
|
20
|
+
return "_id";
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
protected get sort() {
|
|
@@ -26,6 +32,10 @@ export abstract class ApiController extends Controller {
|
|
|
26
32
|
return true;
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
protected get formatConcurrency() {
|
|
36
|
+
return 50;
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
protected get admin(): any {
|
|
30
40
|
return this.state.admin;
|
|
31
41
|
}
|
|
@@ -65,7 +75,7 @@ export abstract class ApiController extends Controller {
|
|
|
65
75
|
const query = c.req.query();
|
|
66
76
|
let options: Record<string, any> = { sort: this.sort };
|
|
67
77
|
|
|
68
|
-
if (typeof (this as any).beforeIndex ===
|
|
78
|
+
if (typeof (this as any).beforeIndex === "function") {
|
|
69
79
|
options = { ...options, ...(await (this as any).beforeIndex()) };
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -76,7 +86,7 @@ export abstract class ApiController extends Controller {
|
|
|
76
86
|
data = await Model.findPage(filter, {
|
|
77
87
|
...options,
|
|
78
88
|
page: query.page,
|
|
79
|
-
pageSize: query.pageSize
|
|
89
|
+
pageSize: query.pageSize,
|
|
80
90
|
});
|
|
81
91
|
data.list = await this.formatList(data.list);
|
|
82
92
|
} else {
|
|
@@ -89,13 +99,13 @@ export abstract class ApiController extends Controller {
|
|
|
89
99
|
|
|
90
100
|
async show(c: Context) {
|
|
91
101
|
const Model = this.requireModel();
|
|
92
|
-
const doc = await Model.findOne({ [this.key]: c.req.param(
|
|
102
|
+
const doc = await Model.findOne({ [this.key]: c.req.param("id") });
|
|
93
103
|
|
|
94
104
|
if (!doc) {
|
|
95
|
-
return c.fail(
|
|
105
|
+
return c.fail("数据获取失败");
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
return c.ok(await this.
|
|
108
|
+
return c.ok(await this._formatItem(doc));
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
async create(c: Context) {
|
|
@@ -104,40 +114,40 @@ export abstract class ApiController extends Controller {
|
|
|
104
114
|
|
|
105
115
|
(this as any).isNew = true;
|
|
106
116
|
|
|
107
|
-
if (typeof (this as any).beforeCreate ===
|
|
117
|
+
if (typeof (this as any).beforeCreate === "function") {
|
|
108
118
|
await (this as any).beforeCreate();
|
|
109
119
|
}
|
|
110
120
|
|
|
111
|
-
if (typeof (this as any).beforeSave ===
|
|
121
|
+
if (typeof (this as any).beforeSave === "function") {
|
|
112
122
|
await (this as any).beforeSave();
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
const doc = await Model.create(body);
|
|
116
126
|
|
|
117
|
-
if (typeof (this as any).afterSave ===
|
|
127
|
+
if (typeof (this as any).afterSave === "function") {
|
|
118
128
|
await (this as any).afterSave(doc);
|
|
119
129
|
}
|
|
120
130
|
|
|
121
|
-
return c.ok(undefined,
|
|
131
|
+
return c.ok(undefined, "操作成功");
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
async update(c: Context) {
|
|
125
135
|
const Model = this.requireModel();
|
|
126
136
|
const body = await this.body();
|
|
127
|
-
const ids = (c.req.param(
|
|
137
|
+
const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
|
|
128
138
|
|
|
129
139
|
(this as any).isUpdate = true;
|
|
130
140
|
(this as any).isBatch = ids.length > 1;
|
|
131
141
|
|
|
132
142
|
if ((this as any).isBatch && !this.batchAction) {
|
|
133
|
-
return c.fail(
|
|
143
|
+
return c.fail("不可批量操作");
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
if (typeof (this as any).beforeUpdate ===
|
|
146
|
+
if (typeof (this as any).beforeUpdate === "function") {
|
|
137
147
|
await (this as any).beforeUpdate();
|
|
138
148
|
}
|
|
139
149
|
|
|
140
|
-
if (typeof (this as any).beforeSave ===
|
|
150
|
+
if (typeof (this as any).beforeSave === "function") {
|
|
141
151
|
await (this as any).beforeSave();
|
|
142
152
|
}
|
|
143
153
|
|
|
@@ -146,27 +156,29 @@ export abstract class ApiController extends Controller {
|
|
|
146
156
|
if ((this as any).isBatch) {
|
|
147
157
|
await Model.updateMany({ [this.key]: { $in: ids } }, body);
|
|
148
158
|
} else {
|
|
149
|
-
doc = await Model.findOneAndUpdate({ [this.key]: ids[0] }, body, {
|
|
159
|
+
doc = await Model.findOneAndUpdate({ [this.key]: ids[0] }, body, {
|
|
160
|
+
new: true,
|
|
161
|
+
});
|
|
150
162
|
}
|
|
151
163
|
|
|
152
|
-
if (typeof (this as any).afterSave ===
|
|
164
|
+
if (typeof (this as any).afterSave === "function") {
|
|
153
165
|
await (this as any).afterSave(doc);
|
|
154
166
|
}
|
|
155
167
|
|
|
156
|
-
return c.ok(undefined,
|
|
168
|
+
return c.ok(undefined, "操作成功");
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
async delete(c: Context) {
|
|
160
172
|
const Model = this.requireModel();
|
|
161
|
-
const ids = (c.req.param(
|
|
173
|
+
const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
|
|
162
174
|
|
|
163
175
|
if (ids.length > 1 && !this.batchAction) {
|
|
164
|
-
return c.fail(
|
|
176
|
+
return c.fail("不可批量操作");
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
await Model.deleteMany({ [this.key]: { $in: ids } });
|
|
168
180
|
|
|
169
|
-
return c.ok(undefined,
|
|
181
|
+
return c.ok(undefined, "操作成功");
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
async destroy(c: Context) {
|
|
@@ -176,22 +188,30 @@ export abstract class ApiController extends Controller {
|
|
|
176
188
|
protected async getFilter(extraFilter?: Record<string, any>) {
|
|
177
189
|
const query = this.context.req.query();
|
|
178
190
|
const filter: Record<string, any> = { ...(extraFilter ?? {}) };
|
|
179
|
-
const searchKeys = arrayFrom<string>(
|
|
180
|
-
|
|
191
|
+
const searchKeys = arrayFrom<string>(
|
|
192
|
+
(this as any).searchKey ?? (this as any).searchKeys,
|
|
193
|
+
);
|
|
194
|
+
const likeKeys = arrayFrom<string>(
|
|
195
|
+
(this as any).likeKey ?? (this as any).likeKeys,
|
|
196
|
+
);
|
|
181
197
|
|
|
182
198
|
for (const key of searchKeys) {
|
|
183
|
-
if (query[key] !== undefined && query[key] !==
|
|
199
|
+
if (query[key] !== undefined && query[key] !== "") {
|
|
184
200
|
filter[key] = query[key];
|
|
185
201
|
}
|
|
186
202
|
}
|
|
187
203
|
|
|
188
204
|
for (const key of likeKeys) {
|
|
189
|
-
if (query[key] !== undefined && query[key] !==
|
|
190
|
-
filter[key] = new RegExp(escapeRegex(query[key]),
|
|
205
|
+
if (query[key] !== undefined && query[key] !== "") {
|
|
206
|
+
filter[key] = new RegExp(escapeRegex(query[key]), "i");
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
194
|
-
const customGetFilter = getCustomMethod(
|
|
210
|
+
const customGetFilter = getCustomMethod(
|
|
211
|
+
this,
|
|
212
|
+
"getFilter",
|
|
213
|
+
ApiController.prototype.getFilter,
|
|
214
|
+
);
|
|
195
215
|
|
|
196
216
|
if (customGetFilter) {
|
|
197
217
|
Object.assign(filter, await customGetFilter.call(this, filter));
|
|
@@ -202,17 +222,44 @@ export abstract class ApiController extends Controller {
|
|
|
202
222
|
return filter;
|
|
203
223
|
}
|
|
204
224
|
|
|
225
|
+
// 过滤
|
|
226
|
+
async _omit(item: any) {
|
|
227
|
+
const keys = arrayFrom<string>(this.omit);
|
|
228
|
+
|
|
229
|
+
return keys.length ? omit(item, keys) : item;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 挑选
|
|
233
|
+
async _pick(item: any) {
|
|
234
|
+
const keys = arrayFrom<string>(this.pick ?? this.output ?? this.json);
|
|
235
|
+
|
|
236
|
+
return keys.length ? pick(item, keys) : item;
|
|
237
|
+
}
|
|
238
|
+
|
|
205
239
|
protected async formatList(list: any[]) {
|
|
206
|
-
return
|
|
240
|
+
return queue(list, this._formatItem, {
|
|
241
|
+
concurrency: this.formatConcurrency,
|
|
242
|
+
});
|
|
207
243
|
}
|
|
208
244
|
|
|
209
|
-
protected async
|
|
210
|
-
|
|
245
|
+
protected async _formatItem(item: any) {
|
|
246
|
+
let data = item;
|
|
211
247
|
|
|
212
|
-
|
|
213
|
-
|
|
248
|
+
try {
|
|
249
|
+
data = item?.toJSON ? item.toJSON() : { ...item };
|
|
250
|
+
|
|
251
|
+
if (typeof (this as any).formatItem === "function") {
|
|
252
|
+
const formatted = await (this as any).formatItem(data);
|
|
253
|
+
|
|
254
|
+
if (formatted) {
|
|
255
|
+
data = formatted;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
214
258
|
|
|
215
|
-
|
|
259
|
+
data = await this._omit(data);
|
|
260
|
+
data = await this._pick(data);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("[ApiController] formatItem failed:", error);
|
|
216
263
|
}
|
|
217
264
|
|
|
218
265
|
return data;
|
|
@@ -233,7 +280,10 @@ function getCustomMethod(target: object, name: string, baseMethod: Function) {
|
|
|
233
280
|
while (prototype && prototype !== ApiController.prototype) {
|
|
234
281
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
235
282
|
|
|
236
|
-
if (
|
|
283
|
+
if (
|
|
284
|
+
typeof descriptor?.value === "function" &&
|
|
285
|
+
descriptor.value !== baseMethod
|
|
286
|
+
) {
|
|
237
287
|
return descriptor.value;
|
|
238
288
|
}
|
|
239
289
|
|
package/helpers/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
1
|
+
export * from "./route";
|
|
2
|
+
export * from "./config";
|
|
3
|
+
export * from "./crud";
|
|
4
|
+
export * from "../plugins/define";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jax-hono",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Lightweight framework layer on top of Hono, built for Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -82,7 +82,10 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@hono/session": "^0.2.1",
|
|
85
|
-
"
|
|
85
|
+
"@types/lodash-es": "^4.17.12",
|
|
86
|
+
"cac": "^7.0.0",
|
|
87
|
+
"lodash-es": "^4.18.1",
|
|
88
|
+
"p-limit": "^7.3.0"
|
|
86
89
|
},
|
|
87
90
|
"peerDependencies": {
|
|
88
91
|
"hono": "^4.12.27",
|
package/utils/async.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pLimit from "p-limit";
|
|
2
|
+
|
|
3
|
+
export { pLimit };
|
|
4
|
+
|
|
5
|
+
export type QueueErrorHandler<T, R> = (
|
|
6
|
+
error: unknown,
|
|
7
|
+
item: T,
|
|
8
|
+
index: number,
|
|
9
|
+
) => Promise<R | T> | R | T;
|
|
10
|
+
|
|
11
|
+
export interface QueueOptions<T, R> {
|
|
12
|
+
concurrency?: number;
|
|
13
|
+
continueOnError?: boolean;
|
|
14
|
+
onError?: QueueErrorHandler<T, R>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function queue<T, R>(
|
|
18
|
+
data: readonly T[],
|
|
19
|
+
fn: (item: T, index: number) => Promise<R> | R,
|
|
20
|
+
options: number | QueueOptions<T, R> = {},
|
|
21
|
+
) {
|
|
22
|
+
const config =
|
|
23
|
+
typeof options === "number" ? { concurrency: options } : options;
|
|
24
|
+
const concurrency = config.concurrency ?? 50;
|
|
25
|
+
const continueOnError = config.continueOnError ?? true;
|
|
26
|
+
const limit = pLimit(concurrency);
|
|
27
|
+
|
|
28
|
+
const input = data.map((item, index) => {
|
|
29
|
+
return limit(async () => {
|
|
30
|
+
try {
|
|
31
|
+
return await fn(item, index);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (!continueOnError) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.error("[queue] task failed:", error);
|
|
38
|
+
|
|
39
|
+
return config.onError ? config.onError(error, item, index) : item;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = await Promise.all(input);
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
package/utils/index.ts
CHANGED