jax-hono 1.0.20 → 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.
- package/README.md +32 -32
- package/bin/jax.js +66 -66
- package/bin/jax.ts +8 -8
- package/build/build.ts +19 -19
- package/build/generate-config.ts +122 -122
- package/build/generate-registry.ts +902 -902
- package/build/generated-path.ts +20 -20
- package/config.ts +68 -68
- package/core/api-controller.ts +290 -290
- package/core/app.ts +207 -207
- package/core/controller.ts +85 -85
- package/core/generated.ts +73 -73
- package/core/hono.ts +63 -63
- package/core/service.ts +44 -44
- package/docs/agent.md +164 -164
- package/docs/jax.md +326 -326
- package/helpers/config.ts +23 -23
- package/helpers/crud.ts +27 -27
- package/helpers/index.ts +4 -4
- package/helpers/route.ts +7 -7
- package/index.ts +121 -121
- package/middleware/api-response.ts +44 -44
- package/middleware/jwt.ts +90 -90
- package/middleware/request-log.ts +3 -3
- package/package.json +129 -124
- package/plugins/config.ts +8 -8
- package/plugins/define.ts +5 -5
- package/plugins/mongoose/index.ts +57 -57
- package/plugins/mongoose/model.ts +322 -322
- package/plugins/mongoose/schema.ts +30 -30
- package/plugins/session/index.ts +86 -86
- package/setup.ts +105 -105
- package/types/config.ts +9 -9
- package/types/context.ts +9 -9
- package/types/index.ts +2 -2
- package/utils/array.ts +7 -7
- package/utils/async.ts +47 -47
- package/utils/crypto.ts +6 -6
- package/utils/http.ts +106 -0
- package/utils/index.ts +3 -2
- package/utils/regexp.ts +14 -14
- package/utils/transform.ts +3 -3
|
@@ -1,322 +1,322 @@
|
|
|
1
|
-
import { basename, parse } from "node:path";
|
|
2
|
-
import mongoose, {
|
|
3
|
-
Schema,
|
|
4
|
-
model as mongooseModel,
|
|
5
|
-
type InferSchemaType,
|
|
6
|
-
type Model,
|
|
7
|
-
type SchemaOptions,
|
|
8
|
-
} from "mongoose";
|
|
9
|
-
import config from "../../config";
|
|
10
|
-
import { rankSetter, rankedFields } from "./schema";
|
|
11
|
-
import dayjs from "dayjs";
|
|
12
|
-
|
|
13
|
-
export { rankSetter };
|
|
14
|
-
|
|
15
|
-
type PageOptions = {
|
|
16
|
-
page?: number | string;
|
|
17
|
-
pageSize?: number | string;
|
|
18
|
-
sort?: Record<string, unknown>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type ModelStatics = {
|
|
22
|
-
findPage(
|
|
23
|
-
filter?: Record<string, unknown>,
|
|
24
|
-
options?: PageOptions,
|
|
25
|
-
): Promise<{
|
|
26
|
-
total: number;
|
|
27
|
-
list: unknown[];
|
|
28
|
-
maxPage: number;
|
|
29
|
-
page: number;
|
|
30
|
-
pageSize: number;
|
|
31
|
-
}>;
|
|
32
|
-
tree(options?: {
|
|
33
|
-
filter?: Record<string, unknown>;
|
|
34
|
-
sort?: Record<string, unknown>;
|
|
35
|
-
}): Promise<unknown[]>;
|
|
36
|
-
flatTree(options?: {
|
|
37
|
-
filter?: Record<string, unknown>;
|
|
38
|
-
sort?: Record<string, unknown>;
|
|
39
|
-
}): Promise<unknown[]>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export function createLooseModel(filePathOrName: string, collection?: string) {
|
|
43
|
-
const name = getModelName(filePathOrName);
|
|
44
|
-
const schema = new Schema({}, { strict: false });
|
|
45
|
-
|
|
46
|
-
applyDefaultSchemaOptions(schema);
|
|
47
|
-
installCommonFields(schema);
|
|
48
|
-
installModelStatics(schema);
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
mongoose.models[name] ??
|
|
52
|
-
mongooseModel(name, schema, collection ?? getCollectionName(name))
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function createSchemaModel<TSchema extends Schema>(
|
|
57
|
-
filePathOrName: string,
|
|
58
|
-
schema: TSchema,
|
|
59
|
-
collection?: string,
|
|
60
|
-
): Model<InferSchemaType<TSchema>> & ModelStatics {
|
|
61
|
-
const name = getModelName(filePathOrName);
|
|
62
|
-
|
|
63
|
-
applyDefaultSchemaOptions(schema);
|
|
64
|
-
installCommonFields(schema);
|
|
65
|
-
installRankHooksIfNeeded(schema);
|
|
66
|
-
installModelStatics(schema);
|
|
67
|
-
|
|
68
|
-
return (mongoose.models[name] ??
|
|
69
|
-
mongooseModel<InferSchemaType<TSchema>>(
|
|
70
|
-
name,
|
|
71
|
-
schema as any,
|
|
72
|
-
collection ?? getCollectionName(name),
|
|
73
|
-
)) as Model<InferSchemaType<TSchema>> & ModelStatics;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export const createModel = createSchemaModel;
|
|
77
|
-
|
|
78
|
-
const rankHooksInstalled = Symbol.for("jax-hono.mongoose.rankHooksInstalled");
|
|
79
|
-
|
|
80
|
-
export function getModelName(filePathOrName: string) {
|
|
81
|
-
const baseName =
|
|
82
|
-
filePathOrName.includes("/") || filePathOrName.includes("\\")
|
|
83
|
-
? parse(basename(filePathOrName)).name.replace(/\.model$/, "")
|
|
84
|
-
: filePathOrName;
|
|
85
|
-
|
|
86
|
-
return toPascalCase(baseName);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getCollectionName(modelName: string) {
|
|
90
|
-
const prefix =
|
|
91
|
-
(config as any).mongoose?.prefix ?? (config as any).mongo?.prefix ?? "";
|
|
92
|
-
const name = toSnakeCase(modelName);
|
|
93
|
-
|
|
94
|
-
return prefix ? `${prefix}_${name}` : name;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function schemaOptions(): SchemaOptions {
|
|
98
|
-
return {
|
|
99
|
-
timestamps: true,
|
|
100
|
-
versionKey: false,
|
|
101
|
-
toJSON: {
|
|
102
|
-
virtuals: true,
|
|
103
|
-
getters: true,
|
|
104
|
-
transform(_doc: unknown, ret: any) {
|
|
105
|
-
// if (ret._id && !ret.id) {
|
|
106
|
-
// ret.id = ret._id.toString();
|
|
107
|
-
// }
|
|
108
|
-
|
|
109
|
-
if (ret.createdAt) {
|
|
110
|
-
ret.createdAt = dayjs(ret.createdAt).format("YYYY-MM-DD HH:mm:ss");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (ret.updatedAt) {
|
|
114
|
-
ret.updatedAt = dayjs(ret.updatedAt).format("YYYY-MM-DD HH:mm:ss");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
delete ret.__v;
|
|
118
|
-
delete ret._id;
|
|
119
|
-
|
|
120
|
-
return ret;
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
toObject: {
|
|
124
|
-
virtuals: true,
|
|
125
|
-
getters: true,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function applyDefaultSchemaOptions(schema: Schema) {
|
|
131
|
-
const defaults = schemaOptions();
|
|
132
|
-
|
|
133
|
-
if (schema.options.timestamps === undefined) {
|
|
134
|
-
schema.set("timestamps", defaults.timestamps);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
schema.options.versionKey === undefined ||
|
|
139
|
-
schema.options.versionKey === "__v"
|
|
140
|
-
) {
|
|
141
|
-
schema.set("versionKey", defaults.versionKey);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const toJSON = {
|
|
145
|
-
...defaults.toJSON,
|
|
146
|
-
...(schema.options.toJSON ?? {}),
|
|
147
|
-
} as any;
|
|
148
|
-
|
|
149
|
-
schema.set("toJSON", toJSON);
|
|
150
|
-
schema.set("toObject", {
|
|
151
|
-
...defaults.toObject,
|
|
152
|
-
...(schema.options.toObject ?? {}),
|
|
153
|
-
} as any);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function installRankHooks(schema: Schema) {
|
|
157
|
-
const fields: Record<string, unknown> = {};
|
|
158
|
-
|
|
159
|
-
if (!schema.path("rank")) {
|
|
160
|
-
fields.rank = rankedFields.rank;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (!schema.path("isTop")) {
|
|
164
|
-
fields.isTop = rankedFields.isTop;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (Object.keys(fields).length) {
|
|
168
|
-
schema.add(fields as any);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if ((schema as any)[rankHooksInstalled]) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
(schema as any)[rankHooksInstalled] = true;
|
|
176
|
-
|
|
177
|
-
// 修改
|
|
178
|
-
schema.pre(["updateOne", "findOneAndUpdate", "updateMany"], function () {
|
|
179
|
-
console.log('updateOne');
|
|
180
|
-
normalizeRank(this.getUpdate());
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// 新增
|
|
184
|
-
schema.pre("save", function () {
|
|
185
|
-
console.log('save');
|
|
186
|
-
normalizeRank(this);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function installRankHooksIfNeeded(schema: Schema) {
|
|
191
|
-
if (schema.path("rank") && schema.path("isTop")) {
|
|
192
|
-
installRankHooks(schema);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function installAutoNo(schema: Schema, field: string, start = 0) {
|
|
197
|
-
schema.pre("save", async function () {
|
|
198
|
-
if (!this.isNew || this.get(field)) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const model = this.constructor as any;
|
|
203
|
-
const doc = await model.findOne({}).sort({ [field]: -1 });
|
|
204
|
-
|
|
205
|
-
this.set(field, (doc?.[field] ?? start) + 1);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function installModelStatics(schema: Schema) {
|
|
210
|
-
schema.static(
|
|
211
|
-
"findPage",
|
|
212
|
-
async function findPage(filter = {}, options: PageOptions = {}) {
|
|
213
|
-
const page = Number(options.page || 1);
|
|
214
|
-
const pageSize = Number(options.pageSize || 20);
|
|
215
|
-
const sort = (options.sort ?? { _id: -1 }) as any;
|
|
216
|
-
const total = await this.countDocuments(filter);
|
|
217
|
-
let query = this.find(filter).sort(sort);
|
|
218
|
-
|
|
219
|
-
if (pageSize !== -1) {
|
|
220
|
-
query = query.skip((page - 1) * pageSize).limit(pageSize);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const list = await query;
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
total,
|
|
227
|
-
list,
|
|
228
|
-
maxPage: pageSize === -1 ? 1 : Math.ceil(total / pageSize),
|
|
229
|
-
page,
|
|
230
|
-
pageSize,
|
|
231
|
-
};
|
|
232
|
-
},
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
schema.static("tree", async function tree(options: any = {}) {
|
|
236
|
-
const docs = await this.find(options.filter ?? {})
|
|
237
|
-
.sort(options.sort ?? {})
|
|
238
|
-
.lean({ virtuals: true });
|
|
239
|
-
const byId = new Map<string, any>();
|
|
240
|
-
const tree: any[] = [];
|
|
241
|
-
|
|
242
|
-
for (const doc of docs) {
|
|
243
|
-
const id = String(doc._id ?? doc.id);
|
|
244
|
-
byId.set(id, { ...doc, id, children: [] });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
for (const item of byId.values()) {
|
|
248
|
-
const parentId = item.parentId ? String(item.parentId) : "";
|
|
249
|
-
const parent = parentId ? byId.get(parentId) : undefined;
|
|
250
|
-
|
|
251
|
-
if (parent) {
|
|
252
|
-
parent.children.push(item);
|
|
253
|
-
} else {
|
|
254
|
-
tree.push(item);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return tree;
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
schema.static(
|
|
262
|
-
"flatTree",
|
|
263
|
-
async function flatTree(this: any, options: any = {}) {
|
|
264
|
-
const tree = await this.tree(options);
|
|
265
|
-
const list: any[] = [];
|
|
266
|
-
|
|
267
|
-
walkTree(tree, 1, list);
|
|
268
|
-
|
|
269
|
-
return list;
|
|
270
|
-
},
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export function installCommonFields(schema: Schema) {
|
|
275
|
-
const fields: Record<string, unknown> = {};
|
|
276
|
-
|
|
277
|
-
if (!schema.path("isDelete")) {
|
|
278
|
-
fields.isDelete = { type: Boolean, default: false };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!schema.path("isOpen")) {
|
|
282
|
-
fields.isOpen = { type: Boolean, default: false };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (Object.keys(fields).length > 0) {
|
|
286
|
-
schema.add(fields as any);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export function toSnakeCase(value: string) {
|
|
291
|
-
return value
|
|
292
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
293
|
-
.replace(/[-\s]+/g, "_")
|
|
294
|
-
.toLowerCase();
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function toPascalCase(value: string) {
|
|
298
|
-
const camel = value.replace(/[-_\s]+([a-zA-Z0-9])/g, (_match, char: string) =>
|
|
299
|
-
char.toUpperCase(),
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function walkTree(items: any[], depth: number, list: any[]) {
|
|
306
|
-
for (const item of items) {
|
|
307
|
-
const { children = [], ...data } = item;
|
|
308
|
-
|
|
309
|
-
list.push({ ...data, children, index: depth });
|
|
310
|
-
walkTree(children, depth + 1, list);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function normalizeRank(data: any) {
|
|
315
|
-
if (data.rank != undefined) {
|
|
316
|
-
if (data.rank) {
|
|
317
|
-
data.isTop = true
|
|
318
|
-
} else {
|
|
319
|
-
data.isTop = false
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
1
|
+
import { basename, parse } from "node:path";
|
|
2
|
+
import mongoose, {
|
|
3
|
+
Schema,
|
|
4
|
+
model as mongooseModel,
|
|
5
|
+
type InferSchemaType,
|
|
6
|
+
type Model,
|
|
7
|
+
type SchemaOptions,
|
|
8
|
+
} from "mongoose";
|
|
9
|
+
import config from "../../config";
|
|
10
|
+
import { rankSetter, rankedFields } from "./schema";
|
|
11
|
+
import dayjs from "dayjs";
|
|
12
|
+
|
|
13
|
+
export { rankSetter };
|
|
14
|
+
|
|
15
|
+
type PageOptions = {
|
|
16
|
+
page?: number | string;
|
|
17
|
+
pageSize?: number | string;
|
|
18
|
+
sort?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ModelStatics = {
|
|
22
|
+
findPage(
|
|
23
|
+
filter?: Record<string, unknown>,
|
|
24
|
+
options?: PageOptions,
|
|
25
|
+
): Promise<{
|
|
26
|
+
total: number;
|
|
27
|
+
list: unknown[];
|
|
28
|
+
maxPage: number;
|
|
29
|
+
page: number;
|
|
30
|
+
pageSize: number;
|
|
31
|
+
}>;
|
|
32
|
+
tree(options?: {
|
|
33
|
+
filter?: Record<string, unknown>;
|
|
34
|
+
sort?: Record<string, unknown>;
|
|
35
|
+
}): Promise<unknown[]>;
|
|
36
|
+
flatTree(options?: {
|
|
37
|
+
filter?: Record<string, unknown>;
|
|
38
|
+
sort?: Record<string, unknown>;
|
|
39
|
+
}): Promise<unknown[]>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function createLooseModel(filePathOrName: string, collection?: string) {
|
|
43
|
+
const name = getModelName(filePathOrName);
|
|
44
|
+
const schema = new Schema({}, { strict: false });
|
|
45
|
+
|
|
46
|
+
applyDefaultSchemaOptions(schema);
|
|
47
|
+
installCommonFields(schema);
|
|
48
|
+
installModelStatics(schema);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
mongoose.models[name] ??
|
|
52
|
+
mongooseModel(name, schema, collection ?? getCollectionName(name))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createSchemaModel<TSchema extends Schema>(
|
|
57
|
+
filePathOrName: string,
|
|
58
|
+
schema: TSchema,
|
|
59
|
+
collection?: string,
|
|
60
|
+
): Model<InferSchemaType<TSchema>> & ModelStatics {
|
|
61
|
+
const name = getModelName(filePathOrName);
|
|
62
|
+
|
|
63
|
+
applyDefaultSchemaOptions(schema);
|
|
64
|
+
installCommonFields(schema);
|
|
65
|
+
installRankHooksIfNeeded(schema);
|
|
66
|
+
installModelStatics(schema);
|
|
67
|
+
|
|
68
|
+
return (mongoose.models[name] ??
|
|
69
|
+
mongooseModel<InferSchemaType<TSchema>>(
|
|
70
|
+
name,
|
|
71
|
+
schema as any,
|
|
72
|
+
collection ?? getCollectionName(name),
|
|
73
|
+
)) as Model<InferSchemaType<TSchema>> & ModelStatics;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const createModel = createSchemaModel;
|
|
77
|
+
|
|
78
|
+
const rankHooksInstalled = Symbol.for("jax-hono.mongoose.rankHooksInstalled");
|
|
79
|
+
|
|
80
|
+
export function getModelName(filePathOrName: string) {
|
|
81
|
+
const baseName =
|
|
82
|
+
filePathOrName.includes("/") || filePathOrName.includes("\\")
|
|
83
|
+
? parse(basename(filePathOrName)).name.replace(/\.model$/, "")
|
|
84
|
+
: filePathOrName;
|
|
85
|
+
|
|
86
|
+
return toPascalCase(baseName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getCollectionName(modelName: string) {
|
|
90
|
+
const prefix =
|
|
91
|
+
(config as any).mongoose?.prefix ?? (config as any).mongo?.prefix ?? "";
|
|
92
|
+
const name = toSnakeCase(modelName);
|
|
93
|
+
|
|
94
|
+
return prefix ? `${prefix}_${name}` : name;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function schemaOptions(): SchemaOptions {
|
|
98
|
+
return {
|
|
99
|
+
timestamps: true,
|
|
100
|
+
versionKey: false,
|
|
101
|
+
toJSON: {
|
|
102
|
+
virtuals: true,
|
|
103
|
+
getters: true,
|
|
104
|
+
transform(_doc: unknown, ret: any) {
|
|
105
|
+
// if (ret._id && !ret.id) {
|
|
106
|
+
// ret.id = ret._id.toString();
|
|
107
|
+
// }
|
|
108
|
+
|
|
109
|
+
if (ret.createdAt) {
|
|
110
|
+
ret.createdAt = dayjs(ret.createdAt).format("YYYY-MM-DD HH:mm:ss");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (ret.updatedAt) {
|
|
114
|
+
ret.updatedAt = dayjs(ret.updatedAt).format("YYYY-MM-DD HH:mm:ss");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
delete ret.__v;
|
|
118
|
+
delete ret._id;
|
|
119
|
+
|
|
120
|
+
return ret;
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
toObject: {
|
|
124
|
+
virtuals: true,
|
|
125
|
+
getters: true,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function applyDefaultSchemaOptions(schema: Schema) {
|
|
131
|
+
const defaults = schemaOptions();
|
|
132
|
+
|
|
133
|
+
if (schema.options.timestamps === undefined) {
|
|
134
|
+
schema.set("timestamps", defaults.timestamps);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
schema.options.versionKey === undefined ||
|
|
139
|
+
schema.options.versionKey === "__v"
|
|
140
|
+
) {
|
|
141
|
+
schema.set("versionKey", defaults.versionKey);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const toJSON = {
|
|
145
|
+
...defaults.toJSON,
|
|
146
|
+
...(schema.options.toJSON ?? {}),
|
|
147
|
+
} as any;
|
|
148
|
+
|
|
149
|
+
schema.set("toJSON", toJSON);
|
|
150
|
+
schema.set("toObject", {
|
|
151
|
+
...defaults.toObject,
|
|
152
|
+
...(schema.options.toObject ?? {}),
|
|
153
|
+
} as any);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function installRankHooks(schema: Schema) {
|
|
157
|
+
const fields: Record<string, unknown> = {};
|
|
158
|
+
|
|
159
|
+
if (!schema.path("rank")) {
|
|
160
|
+
fields.rank = rankedFields.rank;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!schema.path("isTop")) {
|
|
164
|
+
fields.isTop = rankedFields.isTop;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Object.keys(fields).length) {
|
|
168
|
+
schema.add(fields as any);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if ((schema as any)[rankHooksInstalled]) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
(schema as any)[rankHooksInstalled] = true;
|
|
176
|
+
|
|
177
|
+
// 修改
|
|
178
|
+
schema.pre(["updateOne", "findOneAndUpdate", "updateMany"], function () {
|
|
179
|
+
console.log('updateOne');
|
|
180
|
+
normalizeRank(this.getUpdate());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 新增
|
|
184
|
+
schema.pre("save", function () {
|
|
185
|
+
console.log('save');
|
|
186
|
+
normalizeRank(this);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function installRankHooksIfNeeded(schema: Schema) {
|
|
191
|
+
if (schema.path("rank") && schema.path("isTop")) {
|
|
192
|
+
installRankHooks(schema);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function installAutoNo(schema: Schema, field: string, start = 0) {
|
|
197
|
+
schema.pre("save", async function () {
|
|
198
|
+
if (!this.isNew || this.get(field)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const model = this.constructor as any;
|
|
203
|
+
const doc = await model.findOne({}).sort({ [field]: -1 });
|
|
204
|
+
|
|
205
|
+
this.set(field, (doc?.[field] ?? start) + 1);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function installModelStatics(schema: Schema) {
|
|
210
|
+
schema.static(
|
|
211
|
+
"findPage",
|
|
212
|
+
async function findPage(filter = {}, options: PageOptions = {}) {
|
|
213
|
+
const page = Number(options.page || 1);
|
|
214
|
+
const pageSize = Number(options.pageSize || 20);
|
|
215
|
+
const sort = (options.sort ?? { _id: -1 }) as any;
|
|
216
|
+
const total = await this.countDocuments(filter);
|
|
217
|
+
let query = this.find(filter).sort(sort);
|
|
218
|
+
|
|
219
|
+
if (pageSize !== -1) {
|
|
220
|
+
query = query.skip((page - 1) * pageSize).limit(pageSize);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const list = await query;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
total,
|
|
227
|
+
list,
|
|
228
|
+
maxPage: pageSize === -1 ? 1 : Math.ceil(total / pageSize),
|
|
229
|
+
page,
|
|
230
|
+
pageSize,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
schema.static("tree", async function tree(options: any = {}) {
|
|
236
|
+
const docs = await this.find(options.filter ?? {})
|
|
237
|
+
.sort(options.sort ?? {})
|
|
238
|
+
.lean({ virtuals: true });
|
|
239
|
+
const byId = new Map<string, any>();
|
|
240
|
+
const tree: any[] = [];
|
|
241
|
+
|
|
242
|
+
for (const doc of docs) {
|
|
243
|
+
const id = String(doc._id ?? doc.id);
|
|
244
|
+
byId.set(id, { ...doc, id, children: [] });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const item of byId.values()) {
|
|
248
|
+
const parentId = item.parentId ? String(item.parentId) : "";
|
|
249
|
+
const parent = parentId ? byId.get(parentId) : undefined;
|
|
250
|
+
|
|
251
|
+
if (parent) {
|
|
252
|
+
parent.children.push(item);
|
|
253
|
+
} else {
|
|
254
|
+
tree.push(item);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return tree;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
schema.static(
|
|
262
|
+
"flatTree",
|
|
263
|
+
async function flatTree(this: any, options: any = {}) {
|
|
264
|
+
const tree = await this.tree(options);
|
|
265
|
+
const list: any[] = [];
|
|
266
|
+
|
|
267
|
+
walkTree(tree, 1, list);
|
|
268
|
+
|
|
269
|
+
return list;
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function installCommonFields(schema: Schema) {
|
|
275
|
+
const fields: Record<string, unknown> = {};
|
|
276
|
+
|
|
277
|
+
if (!schema.path("isDelete")) {
|
|
278
|
+
fields.isDelete = { type: Boolean, default: false };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!schema.path("isOpen")) {
|
|
282
|
+
fields.isOpen = { type: Boolean, default: false };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (Object.keys(fields).length > 0) {
|
|
286
|
+
schema.add(fields as any);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function toSnakeCase(value: string) {
|
|
291
|
+
return value
|
|
292
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
293
|
+
.replace(/[-\s]+/g, "_")
|
|
294
|
+
.toLowerCase();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function toPascalCase(value: string) {
|
|
298
|
+
const camel = value.replace(/[-_\s]+([a-zA-Z0-9])/g, (_match, char: string) =>
|
|
299
|
+
char.toUpperCase(),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function walkTree(items: any[], depth: number, list: any[]) {
|
|
306
|
+
for (const item of items) {
|
|
307
|
+
const { children = [], ...data } = item;
|
|
308
|
+
|
|
309
|
+
list.push({ ...data, children, index: depth });
|
|
310
|
+
walkTree(children, depth + 1, list);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function normalizeRank(data: any) {
|
|
315
|
+
if (data.rank != undefined) {
|
|
316
|
+
if (data.rank) {
|
|
317
|
+
data.isTop = true
|
|
318
|
+
} else {
|
|
319
|
+
data.isTop = false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|