jax-hono 1.0.6 → 1.0.8

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.
@@ -1,17 +1,23 @@
1
- import { Controller } from './controller';
2
- import { arrayFrom } from '../utils/array';
3
- import { escapeRegex } from '../utils/regexp';
4
- import type { Context } from 'hono';
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 '_id';
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 === 'function') {
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,10 +99,10 @@ 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('id') });
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
108
  return c.ok(await this.formatItem(doc));
@@ -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 === 'function') {
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 === 'function') {
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 === 'function') {
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('id') ?? '').split(',').filter(Boolean);
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 === 'function') {
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 === 'function') {
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, { new: true });
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 === 'function') {
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('id') ?? '').split(',').filter(Boolean);
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>((this as any).searchKey ?? (this as any).searchKeys);
180
- const likeKeys = arrayFrom<string>((this as any).likeKey ?? (this as any).likeKeys);
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]), 'i');
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(this, 'getFilter', ApiController.prototype.getFilter);
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 Promise.all(list.map((item) => this.formatItem(item)));
240
+ return queue(list, this._formatItem, {
241
+ concurrency: this.formatConcurrency,
242
+ });
207
243
  }
208
244
 
209
- protected async formatItem(item: any) {
210
- const data = item?.toJSON ? item.toJSON() : { ...item };
245
+ protected async _formatItem(item: any) {
246
+ let data = item;
211
247
 
212
- if (typeof (this as any).formatData === 'function') {
213
- const formatted = await (this as any).formatData(data);
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
- return formatted ?? data;
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 (typeof descriptor?.value === 'function' && descriptor.value !== baseMethod) {
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 './route';
2
- export * from './config';
3
- export * from './crud';
4
- export * from '../plugins/define';
1
+ export * from "./route";
2
+ export * from "./config";
3
+ export * from "./crud";
4
+ export * from "../plugins/define";
@@ -13,7 +13,7 @@ export type ApiFailResponse = {
13
13
 
14
14
  export type ApiResponse<T = unknown> = ApiSuccessResponse<T> | ApiFailResponse;
15
15
 
16
- export function ok<T>(c: Context, data?: T, msg = 'success') {
16
+ export function ok<T>(c: Context, data?: T, msg = 'success'): Response {
17
17
  return c.json<ApiSuccessResponse<T>>({
18
18
  code: 0,
19
19
  msg,
@@ -21,7 +21,7 @@ export function ok<T>(c: Context, data?: T, msg = 'success') {
21
21
  });
22
22
  }
23
23
 
24
- export function fail(c: Context, msg: string) {
24
+ export function fail(c: Context, msg: string): Response {
25
25
  return c.json<ApiFailResponse>({
26
26
  code: 1,
27
27
  msg
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jax-hono",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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
- "cac": "^7.0.0"
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
@@ -1,3 +1,5 @@
1
- export * from './array';
2
- export * from './crypto';
3
- export * from './regexp';
1
+ export * from "./array";
2
+ export * from "./async";
3
+ export * from "./crypto";
4
+ export * from "./regexp";
5
+ export * from "./transform";