jax-hono 1.0.19 → 1.0.21

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,290 +1,290 @@
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";
6
-
7
- const bodyCache = new WeakMap<Context, unknown>();
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
-
15
- protected get Model(): any {
16
- return undefined;
17
- }
18
-
19
- protected get key() {
20
- return "_id";
21
- }
22
-
23
- protected get sort() {
24
- return { [this.key]: -1 };
25
- }
26
-
27
- protected get isPage() {
28
- return true;
29
- }
30
-
31
- protected get batchAction() {
32
- return true;
33
- }
34
-
35
- protected get formatConcurrency() {
36
- return 50;
37
- }
38
-
39
- protected get admin(): any {
40
- return this.state.admin;
41
- }
42
-
43
- protected get user(): any {
44
- return this.state.user;
45
- }
46
-
47
- protected get query() {
48
- return this.context.req.query();
49
- }
50
-
51
- protected get params() {
52
- return this.context.req.param();
53
- }
54
-
55
- protected async body<T = Record<string, any>>() {
56
- if (bodyCache.has(this.context)) {
57
- return bodyCache.get(this.context) as T;
58
- }
59
-
60
- try {
61
- const body = await this.context.req.json<T>();
62
- bodyCache.set(this.context, body);
63
-
64
- return body;
65
- } catch {
66
- const body = {} as T;
67
- bodyCache.set(this.context, body);
68
-
69
- return body;
70
- }
71
- }
72
-
73
- async index(c: Context) {
74
- const Model = this.requireModel();
75
- const query = c.req.query();
76
- let options: Record<string, any> = { sort: this.sort };
77
-
78
- if (typeof (this as any).beforeIndex === "function") {
79
- options = { ...options, ...(await (this as any).beforeIndex()) };
80
- }
81
-
82
- const filter = await this.getFilter(options.filter);
83
- let data: any;
84
-
85
- if (this.isPage) {
86
- data = await Model.findPage(filter, {
87
- ...options,
88
- page: query.page,
89
- pageSize: query.pageSize,
90
- });
91
- data.list = await this.formatList(data.list);
92
- } else {
93
- data = await Model.find(filter).sort(options.sort ?? this.sort);
94
- data = await this.formatList(data);
95
- }
96
-
97
- return c.ok(data);
98
- }
99
-
100
- async show(c: Context) {
101
- const Model = this.requireModel();
102
- const doc = await Model.findOne({ [this.key]: c.req.param("id") });
103
-
104
- if (!doc) {
105
- return c.fail("数据获取失败");
106
- }
107
-
108
- return c.ok(await this._formatItem(doc));
109
- }
110
-
111
- async create(c: Context) {
112
- const Model = this.requireModel();
113
- const body = await this.body();
114
-
115
- (this as any).isNew = true;
116
-
117
- if (typeof (this as any).beforeCreate === "function") {
118
- await (this as any).beforeCreate();
119
- }
120
-
121
- if (typeof (this as any).beforeSave === "function") {
122
- await (this as any).beforeSave();
123
- }
124
-
125
- const doc = await Model.create(body);
126
-
127
- if (typeof (this as any).afterSave === "function") {
128
- await (this as any).afterSave(doc);
129
- }
130
-
131
- return c.ok(undefined, "操作成功");
132
- }
133
-
134
- async update(c: Context) {
135
- const Model = this.requireModel();
136
- const body = await this.body();
137
- const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
138
-
139
- (this as any).isUpdate = true;
140
- (this as any).isBatch = ids.length > 1;
141
-
142
- if ((this as any).isBatch && !this.batchAction) {
143
- return c.fail("不可批量操作");
144
- }
145
-
146
- if (typeof (this as any).beforeUpdate === "function") {
147
- await (this as any).beforeUpdate();
148
- }
149
-
150
- if (typeof (this as any).beforeSave === "function") {
151
- await (this as any).beforeSave();
152
- }
153
-
154
- let doc: any;
155
-
156
- if ((this as any).isBatch) {
157
- await Model.updateMany({ [this.key]: { $in: ids } }, body);
158
- } else {
159
- doc = await Model.findOneAndUpdate({ [this.key]: ids[0] }, body, {
160
- new: true,
161
- });
162
- }
163
-
164
- if (typeof (this as any).afterSave === "function") {
165
- await (this as any).afterSave(doc);
166
- }
167
-
168
- return c.ok(undefined, "操作成功");
169
- }
170
-
171
- async destroy(c: Context) {
172
- const Model = this.requireModel();
173
- const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
174
-
175
- if (ids.length > 1 && !this.batchAction) {
176
- return c.fail("不可批量操作");
177
- }
178
-
179
- await Model.deleteMany({ [this.key]: { $in: ids } });
180
-
181
- return c.ok(undefined, "操作成功");
182
- }
183
-
184
- protected async getFilter(extraFilter?: Record<string, any>) {
185
- const query = this.context.req.query();
186
- const filter: Record<string, any> = { ...(extraFilter ?? {}) };
187
- const searchKeys = arrayFrom<string>(
188
- (this as any).searchKey ?? (this as any).searchKeys,
189
- );
190
- const likeKeys = arrayFrom<string>(
191
- (this as any).likeKey ?? (this as any).likeKeys,
192
- );
193
-
194
- for (const key of searchKeys) {
195
- if (query[key] !== undefined && query[key] !== "") {
196
- filter[key] = query[key];
197
- }
198
- }
199
-
200
- for (const key of likeKeys) {
201
- if (query[key] !== undefined && query[key] !== "") {
202
- filter[key] = new RegExp(escapeRegex(query[key]), "i");
203
- }
204
- }
205
-
206
- const customGetFilter = getCustomMethod(
207
- this,
208
- "getFilter",
209
- ApiController.prototype.getFilter,
210
- );
211
-
212
- if (customGetFilter) {
213
- Object.assign(filter, await customGetFilter.call(this, filter));
214
- } else if ((this as any).filter) {
215
- Object.assign(filter, (this as any).filter);
216
- }
217
-
218
- return filter;
219
- }
220
-
221
- // 过滤
222
- async _omit(item: any) {
223
- const keys = arrayFrom<string>(this.omit);
224
-
225
- return keys.length ? omit(item, keys) : item;
226
- }
227
-
228
- // 挑选
229
- async _pick(item: any) {
230
- const keys = arrayFrom<string>(this.pick ?? this.output ?? this.json);
231
-
232
- return keys.length ? pick(item, keys) : item;
233
- }
234
-
235
- protected async formatList(list: any[]) {
236
- return queue(list, this._formatItem, {
237
- concurrency: this.formatConcurrency,
238
- });
239
- }
240
-
241
- protected async _formatItem(item: any) {
242
- let data = item;
243
-
244
- try {
245
- data = item?.toJSON ? item.toJSON() : { ...item };
246
-
247
- if (typeof (this as any).formatItem === "function") {
248
- const formatted = await (this as any).formatItem(data);
249
-
250
- if (formatted) {
251
- data = formatted;
252
- }
253
- }
254
-
255
- data = await this._omit(data);
256
- data = await this._pick(data);
257
- } catch (error) {
258
- console.error("[ApiController] formatItem failed:", error);
259
- }
260
-
261
- return data;
262
- }
263
-
264
- private requireModel() {
265
- if (!this.Model) {
266
- throw new Error(`${this.constructor.name} must define Model.`);
267
- }
268
-
269
- return this.Model;
270
- }
271
- }
272
-
273
- function getCustomMethod(target: object, name: string, baseMethod: Function) {
274
- let prototype = Object.getPrototypeOf(target);
275
-
276
- while (prototype && prototype !== ApiController.prototype) {
277
- const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
278
-
279
- if (
280
- typeof descriptor?.value === "function" &&
281
- descriptor.value !== baseMethod
282
- ) {
283
- return descriptor.value;
284
- }
285
-
286
- prototype = Object.getPrototypeOf(prototype);
287
- }
288
-
289
- return undefined;
290
- }
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";
6
+
7
+ const bodyCache = new WeakMap<Context, unknown>();
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
+
15
+ protected get Model(): any {
16
+ return undefined;
17
+ }
18
+
19
+ protected get key() {
20
+ return "_id";
21
+ }
22
+
23
+ protected get sort() {
24
+ return { [this.key]: -1 };
25
+ }
26
+
27
+ protected get isPage() {
28
+ return true;
29
+ }
30
+
31
+ protected get batchAction() {
32
+ return true;
33
+ }
34
+
35
+ protected get formatConcurrency() {
36
+ return 50;
37
+ }
38
+
39
+ protected get admin(): any {
40
+ return this.state.admin;
41
+ }
42
+
43
+ protected get user(): any {
44
+ return this.state.user;
45
+ }
46
+
47
+ protected get query() {
48
+ return this.context.req.query();
49
+ }
50
+
51
+ protected get params() {
52
+ return this.context.req.param();
53
+ }
54
+
55
+ protected async body<T = Record<string, any>>() {
56
+ if (bodyCache.has(this.context)) {
57
+ return bodyCache.get(this.context) as T;
58
+ }
59
+
60
+ try {
61
+ const body = await this.context.req.json<T>();
62
+ bodyCache.set(this.context, body);
63
+
64
+ return body;
65
+ } catch {
66
+ const body = {} as T;
67
+ bodyCache.set(this.context, body);
68
+
69
+ return body;
70
+ }
71
+ }
72
+
73
+ async index(c: Context) {
74
+ const Model = this.requireModel();
75
+ const query = c.req.query();
76
+ let options: Record<string, any> = { sort: this.sort };
77
+
78
+ if (typeof (this as any).beforeIndex === "function") {
79
+ options = { ...options, ...(await (this as any).beforeIndex()) };
80
+ }
81
+
82
+ const filter = await this.getFilter(options.filter);
83
+ let data: any;
84
+
85
+ if (this.isPage) {
86
+ data = await Model.findPage(filter, {
87
+ ...options,
88
+ page: query.page,
89
+ pageSize: query.pageSize,
90
+ });
91
+ data.list = await this.formatList(data.list);
92
+ } else {
93
+ data = await Model.find(filter).sort(options.sort ?? this.sort);
94
+ data = await this.formatList(data);
95
+ }
96
+
97
+ return c.ok(data);
98
+ }
99
+
100
+ async show(c: Context) {
101
+ const Model = this.requireModel();
102
+ const doc = await Model.findOne({ [this.key]: c.req.param("id") });
103
+
104
+ if (!doc) {
105
+ return c.fail("数据获取失败");
106
+ }
107
+
108
+ return c.ok(await this._formatItem(doc));
109
+ }
110
+
111
+ async create(c: Context) {
112
+ const Model = this.requireModel();
113
+ const body = await this.body();
114
+
115
+ (this as any).isNew = true;
116
+
117
+ if (typeof (this as any).beforeCreate === "function") {
118
+ await (this as any).beforeCreate();
119
+ }
120
+
121
+ if (typeof (this as any).beforeSave === "function") {
122
+ await (this as any).beforeSave();
123
+ }
124
+
125
+ const doc = await Model.create(body);
126
+
127
+ if (typeof (this as any).afterSave === "function") {
128
+ await (this as any).afterSave(doc);
129
+ }
130
+
131
+ return c.ok(undefined, "操作成功");
132
+ }
133
+
134
+ async update(c: Context) {
135
+ const Model = this.requireModel();
136
+ const body = await this.body();
137
+ const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
138
+
139
+ (this as any).isUpdate = true;
140
+ (this as any).isBatch = ids.length > 1;
141
+
142
+ if ((this as any).isBatch && !this.batchAction) {
143
+ return c.fail("不可批量操作");
144
+ }
145
+
146
+ if (typeof (this as any).beforeUpdate === "function") {
147
+ await (this as any).beforeUpdate();
148
+ }
149
+
150
+ if (typeof (this as any).beforeSave === "function") {
151
+ await (this as any).beforeSave();
152
+ }
153
+
154
+ let doc: any;
155
+
156
+ if ((this as any).isBatch) {
157
+ await Model.updateMany({ [this.key]: { $in: ids } }, body);
158
+ } else {
159
+ doc = await Model.findOneAndUpdate({ [this.key]: ids[0] }, body, {
160
+ new: true,
161
+ });
162
+ }
163
+
164
+ if (typeof (this as any).afterSave === "function") {
165
+ await (this as any).afterSave(doc);
166
+ }
167
+
168
+ return c.ok(undefined, "操作成功");
169
+ }
170
+
171
+ async destroy(c: Context) {
172
+ const Model = this.requireModel();
173
+ const ids = (c.req.param("id") ?? "").split(",").filter(Boolean);
174
+
175
+ if (ids.length > 1 && !this.batchAction) {
176
+ return c.fail("不可批量操作");
177
+ }
178
+
179
+ await Model.deleteMany({ [this.key]: { $in: ids } });
180
+
181
+ return c.ok(undefined, "操作成功");
182
+ }
183
+
184
+ protected async getFilter(extraFilter?: Record<string, any>) {
185
+ const query = this.context.req.query();
186
+ const filter: Record<string, any> = { ...(extraFilter ?? {}) };
187
+ const searchKeys = arrayFrom<string>(
188
+ (this as any).searchKey ?? (this as any).searchKeys,
189
+ );
190
+ const likeKeys = arrayFrom<string>(
191
+ (this as any).likeKey ?? (this as any).likeKeys,
192
+ );
193
+
194
+ for (const key of searchKeys) {
195
+ if (query[key] !== undefined && query[key] !== "") {
196
+ filter[key] = query[key];
197
+ }
198
+ }
199
+
200
+ for (const key of likeKeys) {
201
+ if (query[key] !== undefined && query[key] !== "") {
202
+ filter[key] = new RegExp(escapeRegex(query[key]), "i");
203
+ }
204
+ }
205
+
206
+ const customGetFilter = getCustomMethod(
207
+ this,
208
+ "getFilter",
209
+ ApiController.prototype.getFilter,
210
+ );
211
+
212
+ if (customGetFilter) {
213
+ Object.assign(filter, await customGetFilter.call(this, filter));
214
+ } else if ((this as any).filter) {
215
+ Object.assign(filter, (this as any).filter);
216
+ }
217
+
218
+ return filter;
219
+ }
220
+
221
+ // 过滤
222
+ async _omit(item: any) {
223
+ const keys = arrayFrom<string>(this.omit);
224
+
225
+ return keys.length ? omit(item, keys) : item;
226
+ }
227
+
228
+ // 挑选
229
+ async _pick(item: any) {
230
+ const keys = arrayFrom<string>(this.pick ?? this.output ?? this.json);
231
+
232
+ return keys.length ? pick(item, keys) : item;
233
+ }
234
+
235
+ protected async formatList(list: any[]) {
236
+ return queue(list, this._formatItem, {
237
+ concurrency: this.formatConcurrency,
238
+ });
239
+ }
240
+
241
+ protected async _formatItem(item: any) {
242
+ let data = item;
243
+
244
+ try {
245
+ data = item?.toJSON ? item.toJSON() : { ...item };
246
+
247
+ if (typeof (this as any).formatItem === "function") {
248
+ const formatted = await (this as any).formatItem(data);
249
+
250
+ if (formatted) {
251
+ data = formatted;
252
+ }
253
+ }
254
+
255
+ data = await this._omit(data);
256
+ data = await this._pick(data);
257
+ } catch (error) {
258
+ console.error("[ApiController] formatItem failed:", error);
259
+ }
260
+
261
+ return data;
262
+ }
263
+
264
+ private requireModel() {
265
+ if (!this.Model) {
266
+ throw new Error(`${this.constructor.name} must define Model.`);
267
+ }
268
+
269
+ return this.Model;
270
+ }
271
+ }
272
+
273
+ function getCustomMethod(target: object, name: string, baseMethod: Function) {
274
+ let prototype = Object.getPrototypeOf(target);
275
+
276
+ while (prototype && prototype !== ApiController.prototype) {
277
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
278
+
279
+ if (
280
+ typeof descriptor?.value === "function" &&
281
+ descriptor.value !== baseMethod
282
+ ) {
283
+ return descriptor.value;
284
+ }
285
+
286
+ prototype = Object.getPrototypeOf(prototype);
287
+ }
288
+
289
+ return undefined;
290
+ }