monsqlize 1.0.2 → 1.0.5

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.
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Model 层 - 基于 collection 的增强封装
3
+ *
4
+ * 核心功能:
5
+ * 1. Schema 定义与验证(集成 schema-dsl)
6
+ * 2. 自定义方法扩展(instance + static)
7
+ * 3. 生命周期钩子(before/after)
8
+ * 4. 索引管理(自动创建)
9
+ * 5. 关系定义(hasOne/hasMany/belongsTo)
10
+ *
11
+ * @module lib/model
12
+ * @version 1.0.3
13
+ * @since 1.0.3
14
+ */
15
+
16
+ // ========== 依赖导入 ==========
17
+ let schemaDsl;
18
+ try {
19
+ schemaDsl = require('schema-dsl');
20
+ } catch (err) {
21
+ // schema-dsl 未安装时的友好提示
22
+ const installHint = `
23
+ ╔════════════════════════════════════════════════════════════════╗
24
+ ║ ⚠️ schema-dsl 未安装 ║
25
+ ║ ║
26
+ ║ Model 功能需要 schema-dsl 包支持 ║
27
+ ║ ║
28
+ ║ 安装方式: ║
29
+ ║ 1. npm link(开发): ║
30
+ ║ cd path/to/schema-dsl && npm link ║
31
+ ║ cd path/to/monSQLize && npm link schema-dsl ║
32
+ ║ ║
33
+ ║ 2. npm 安装(生产): ║
34
+ ║ npm install schema-dsl ║
35
+ ╚════════════════════════════════════════════════════════════════╝
36
+ `;
37
+ console.error(installHint);
38
+ throw new Error('schema-dsl is required for Model functionality. Please install it first.');
39
+ }
40
+
41
+ const { dsl, validate } = schemaDsl;
42
+
43
+ /**
44
+ * Model 基类
45
+ * 提供全局注册和实例化能力
46
+ */
47
+ class Model {
48
+ /**
49
+ * 全局 Model 注册表
50
+ * @private
51
+ * @type {Map<string, Object>}
52
+ */
53
+ static _registry = new Map();
54
+
55
+ /**
56
+ * 定义并注册 Model
57
+ *
58
+ * @param {string} collectionName - 集合名称
59
+ * @param {Object} definition - Model 定义对象
60
+ * @param {Object} [definition.enums] - 枚举配置(可选)
61
+ * @param {Function|Object} definition.schema - Schema 定义(必需)
62
+ * @param {Function} [definition.methods] - 自定义方法(可选)
63
+ * @param {Function} [definition.hooks] - 生命周期钩子(可选)
64
+ * @param {Array} [definition.indexes] - 索引定义(可选)
65
+ * @param {Object} [definition.relations] - 关系定义(可选)
66
+ *
67
+ * @throws {Error} 集合名称无效
68
+ * @throws {Error} schema 未定义
69
+ * @throws {Error} Model 已存在
70
+ *
71
+ * @example
72
+ * Model.define('users', {
73
+ * enums: {
74
+ * role: 'admin|user'
75
+ * },
76
+ * schema: function(dsl) {
77
+ * return dsl({
78
+ * username: 'string:3-32!',
79
+ * role: this.enums.role.default('user')
80
+ * });
81
+ * }
82
+ * });
83
+ */
84
+ static define(collectionName, definition) {
85
+ try {
86
+ // ========== 参数验证 ==========
87
+ if (!collectionName || typeof collectionName !== 'string' || collectionName.trim() === '') {
88
+ const err = new Error('Collection name must be a non-empty string.');
89
+ err.code = 'INVALID_COLLECTION_NAME';
90
+ throw err;
91
+ }
92
+
93
+ if (!definition || typeof definition !== 'object') {
94
+ const err = new Error('Model definition must be an object.');
95
+ err.code = 'INVALID_MODEL_DEFINITION';
96
+ throw err;
97
+ }
98
+
99
+ if (!definition.schema) {
100
+ const err = new Error('Model definition must include a schema property.');
101
+ err.code = 'MISSING_SCHEMA';
102
+ throw err;
103
+ }
104
+
105
+ if (typeof definition.schema !== 'function' && typeof definition.schema !== 'object') {
106
+ const err = new Error('Schema must be a function or object.');
107
+ err.code = 'INVALID_SCHEMA_TYPE';
108
+ throw err;
109
+ }
110
+
111
+ // ========== 检查重复注册 ==========
112
+ if (this._registry.has(collectionName)) {
113
+ const err = new Error(`Model '${collectionName}' is already defined.`);
114
+ err.code = 'MODEL_ALREADY_EXISTS';
115
+ throw err;
116
+ }
117
+
118
+ // ========== 解析 timestamps 配置 ==========
119
+ const timestampsConfig = this._parseTimestampsConfig(definition.options?.timestamps);
120
+ if (timestampsConfig) {
121
+ // 保存到内部 hooks 配置
122
+ if (!definition._internalHooks) {
123
+ definition._internalHooks = {};
124
+ }
125
+ definition._internalHooks.timestamps = timestampsConfig;
126
+ }
127
+
128
+ // ========== 注册 Model ==========
129
+ this._registry.set(collectionName, {
130
+ collectionName,
131
+ definition
132
+ });
133
+
134
+ } catch (err) {
135
+ // 统一错误处理
136
+ if (!err.code) {
137
+ err.code = 'MODEL_DEFINE_ERROR';
138
+ }
139
+ throw err;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 解析 timestamps 配置
145
+ *
146
+ * @private
147
+ * @param {boolean|Object} config - timestamps 配置
148
+ * @returns {Object|null} 解析后的配置对象,包含 createdAt 和 updatedAt 字段名
149
+ *
150
+ * @example
151
+ * _parseTimestampsConfig(true)
152
+ * // => { createdAt: 'createdAt', updatedAt: 'updatedAt' }
153
+ *
154
+ * _parseTimestampsConfig({ createdAt: 'created_time', updatedAt: false })
155
+ * // => { createdAt: 'created_time' }
156
+ */
157
+ static _parseTimestampsConfig(config) {
158
+ if (!config) return null;
159
+
160
+ // 简单模式:timestamps: true
161
+ if (config === true) {
162
+ return {
163
+ createdAt: 'createdAt',
164
+ updatedAt: 'updatedAt'
165
+ };
166
+ }
167
+
168
+ // 对象模式
169
+ if (typeof config === 'object') {
170
+ const result = {};
171
+
172
+ // createdAt 配置
173
+ if (config.createdAt === true) {
174
+ result.createdAt = 'createdAt';
175
+ } else if (typeof config.createdAt === 'string') {
176
+ result.createdAt = config.createdAt;
177
+ }
178
+
179
+ // updatedAt 配置(默认启用)
180
+ if (config.updatedAt !== false) {
181
+ if (config.updatedAt === true || config.updatedAt === undefined) {
182
+ result.updatedAt = 'updatedAt';
183
+ } else if (typeof config.updatedAt === 'string') {
184
+ result.updatedAt = config.updatedAt;
185
+ }
186
+ }
187
+
188
+ return Object.keys(result).length > 0 ? result : null;
189
+ }
190
+
191
+ return null;
192
+ }
193
+
194
+ /**
195
+ * 获取已注册的 Model 定义
196
+ *
197
+ * @param {string} collectionName - 集合名称
198
+ * @returns {Object|undefined} Model 定义对象
199
+ *
200
+ * @example
201
+ * const userModelDef = Model.get('users');
202
+ */
203
+ static get(collectionName) {
204
+ return this._registry.get(collectionName);
205
+ }
206
+
207
+ /**
208
+ * 检查 Model 是否已注册
209
+ *
210
+ * @param {string} collectionName - 集合名称
211
+ * @returns {boolean}
212
+ *
213
+ * @example
214
+ * if (Model.has('users')) {
215
+ * // Model 已注册
216
+ * }
217
+ */
218
+ static has(collectionName) {
219
+ return this._registry.has(collectionName);
220
+ }
221
+
222
+ /**
223
+ * 获取所有已注册的 Model 名称
224
+ *
225
+ * @returns {string[]} Model 名称数组
226
+ *
227
+ * @example
228
+ * const modelNames = Model.list();
229
+ * // ['users', 'posts', 'comments']
230
+ */
231
+ static list() {
232
+ return Array.from(this._registry.keys());
233
+ }
234
+
235
+ /**
236
+ * 清空所有已注册的 Model(主要用于测试)
237
+ *
238
+ * @private
239
+ */
240
+ static _clear() {
241
+ this._registry.clear();
242
+ }
243
+ }
244
+
245
+ /**
246
+ * ModelInstance - Model 实例类
247
+ * 继承 collection 的所有方法,并扩展 Model 特性
248
+ */
249
+ class ModelInstance {
250
+ /**
251
+ * 创建 ModelInstance 实例
252
+ *
253
+ * @param {Object} collection - monSQLize collection 对象
254
+ * @param {Object} definition - Model 定义对象
255
+ * @param {Object} msq - monSQLize 实例
256
+ */
257
+ constructor(collection, definition, msq) {
258
+ this.collection = collection;
259
+ this.definition = definition;
260
+ this.msq = msq;
261
+
262
+ // ========== Schema 缓存优化 ==========
263
+ // 🚀 性能优化:编译 schema 并缓存,避免每次 validate 重新执行
264
+ this._schemaCache = null;
265
+ if (typeof definition.schema === 'function') {
266
+ try {
267
+ // 绑定 this 到 definition,支持访问 this.enums
268
+ this._schemaCache = definition.schema.call(definition, dsl);
269
+ } catch (err) {
270
+ // schema 函数执行失败时,保持为 null,在 validate 时重新尝试
271
+ this._schemaCache = null;
272
+ }
273
+ } else {
274
+ this._schemaCache = definition.schema;
275
+ }
276
+
277
+ // ========== 继承 collection 所有方法 ==========
278
+ // 将 collection 的所有方法代理到 ModelInstance
279
+ const collectionMethods = Object.getOwnPropertyNames(Object.getPrototypeOf(collection))
280
+ .concat(Object.keys(collection))
281
+ .filter(key => key !== 'constructor' && typeof collection[key] === 'function');
282
+
283
+ // 去重
284
+ const uniqueMethods = [...new Set(collectionMethods)];
285
+
286
+ uniqueMethods.forEach(method => {
287
+ if (!this[method]) {
288
+ this[method] = async (...args) => {
289
+ // 🔧 incrementOne 特殊处理timestamps
290
+ if (method === 'incrementOne' && this.definition._internalHooks?.timestamps?.updatedAt) {
291
+ // 调用 _applyTimestampsToIncrementOne 处理
292
+ args = this._applyTimestampsToIncrementOne(args);
293
+ }
294
+
295
+ // 🔧 Hook 拦截机制
296
+ const result = await this._interceptWithHooks(method, args);
297
+
298
+ // 🔧 实例方法注入:只在查询操作时注入(find/findOne/aggregate等)
299
+ const opType = this._getOperationType(method);
300
+ if (opType === 'find' && result) {
301
+ this._injectInstanceMethods(result);
302
+ }
303
+
304
+ return result;
305
+ };
306
+ }
307
+ });
308
+
309
+ // ========== 扩展自定义方法 ==========
310
+ if (typeof definition.methods === 'function') {
311
+ const customMethods = definition.methods(this);
312
+
313
+ // 1. instance 方法 - 保存引用,用于注入到查询结果
314
+ this._instanceMethods = customMethods.instance || {};
315
+
316
+ // 2. static 方法 - 挂载到 ModelInstance 本身
317
+ if (customMethods.static && typeof customMethods.static === 'object') {
318
+ Object.keys(customMethods.static).forEach(methodName => {
319
+ if (typeof customMethods.static[methodName] === 'function') {
320
+ // 挂载到 this(ModelInstance 实例)
321
+ this[methodName] = customMethods.static[methodName].bind(this);
322
+ }
323
+ });
324
+ }
325
+
326
+ // 3. 警告未识别的键(帮助用户发现错误)
327
+ if (customMethods && typeof customMethods === 'object') {
328
+ const validKeys = ['instance', 'static'];
329
+ const unknownKeys = Object.keys(customMethods).filter(k => !validKeys.includes(k));
330
+ if (unknownKeys.length > 0 && this.msq && this.msq.logger) {
331
+ this.msq.logger.warn(
332
+ `[Model] methods 只支持 'instance' 和 'static' 两个分组。` +
333
+ `发现未识别的键: ${unknownKeys.join(', ')}`
334
+ );
335
+ }
336
+ }
337
+ } else {
338
+ this._instanceMethods = {};
339
+ }
340
+
341
+ // ========== 初始化 indexes 数组 ==========
342
+ // 注意:必须在 setupSoftDelete 之前初始化,因为 softDelete 可能添加 TTL 索引
343
+ this.indexes = definition.indexes || [];
344
+
345
+ // ========== 软删除功能 ==========
346
+ const { setupSoftDelete } = require('./features/soft-delete');
347
+ setupSoftDelete(this, definition.options?.softDelete);
348
+
349
+ // ========== 乐观锁版本控制功能 ==========
350
+ const { setupVersion } = require('./features/version');
351
+ setupVersion(this, definition.options?.version);
352
+
353
+ // ========== 自动创建索引 ==========
354
+ if (Array.isArray(this.indexes) && this.indexes.length > 0) {
355
+ // 延迟执行,避免阻塞初始化
356
+ setImmediate(() => {
357
+ this._createIndexes().catch(err => {
358
+ // 索引创建失败仅记录警告,不中断流程
359
+ if (this.msq && this.msq.logger) {
360
+ this.msq.logger.warn(`[Model] Failed to create indexes for ${this.collection.collectionName}:`, err.message);
361
+ }
362
+ });
363
+ });
364
+ }
365
+ }
366
+
367
+ /**
368
+ * 将实例方法注入到文档对象
369
+ *
370
+ * @private
371
+ * @param {Object|Array} result - 查询返回的结果(文档对象或数组)
372
+ */
373
+ _injectInstanceMethods(result) {
374
+ if (!this._instanceMethods || Object.keys(this._instanceMethods).length === 0) {
375
+ return;
376
+ }
377
+
378
+ // 处理数组结果(find 返回的)
379
+ if (Array.isArray(result)) {
380
+ result.forEach(doc => {
381
+ if (doc && typeof doc === 'object') {
382
+ this._injectToDocument(doc);
383
+ }
384
+ });
385
+ }
386
+ // 处理单个文档(findOne 返回的)
387
+ else if (result && typeof result === 'object' && !Buffer.isBuffer(result)) {
388
+ this._injectToDocument(result);
389
+ }
390
+ }
391
+
392
+ /**
393
+ * 将实例方法注入到单个文档对象
394
+ *
395
+ * @private
396
+ * @param {Object} doc - 文档对象
397
+ */
398
+ _injectToDocument(doc) {
399
+ Object.keys(this._instanceMethods).forEach(methodName => {
400
+ if (typeof this._instanceMethods[methodName] === 'function') {
401
+ // 绑定 this 到文档对象
402
+ doc[methodName] = this._instanceMethods[methodName].bind(doc);
403
+ }
404
+ });
405
+ }
406
+
407
+ /**
408
+ * Hook 拦截机制
409
+ * 在方法执行前后触发 before/after 钩子
410
+ *
411
+ * @private
412
+ * @param {string} method - 方法名
413
+ * @param {Array} args - 方法参数
414
+ * @returns {Promise<*>} 方法执行结果
415
+ */
416
+ async _interceptWithHooks(method, args) {
417
+ const hooks = typeof this.definition.hooks === 'function'
418
+ ? this.definition.hooks(this)
419
+ : {};
420
+
421
+ // 提取操作类型(find/insert/update/delete)
422
+ const opType = this._getOperationType(method);
423
+ const opHooks = hooks[opType] || {};
424
+
425
+ // 上下文对象(用于在 before/after 之间传递数据,如事务 session)
426
+ const ctx = {};
427
+
428
+ // ========== Before Hook ==========
429
+ if (typeof opHooks.before === 'function') {
430
+ // 🔧 修复:before hook 错误必须中断操作
431
+ const modifiedArgs = await opHooks.before(ctx, ...args);
432
+
433
+ // 🔧 修复:正确应用 before hook 返回值
434
+ // 对于 insert 操作,第一个参数是待插入的文档
435
+ if (modifiedArgs !== undefined) {
436
+ if (opType === 'insert') {
437
+ // insertOne/insertMany: args[0] 是文档/文档数组
438
+ args[0] = modifiedArgs;
439
+ } else if (Array.isArray(modifiedArgs)) {
440
+ // 其他操作:如果返回数组,替换整个 args
441
+ args = modifiedArgs;
442
+ } else {
443
+ // 单个返回值,替换第一个参数
444
+ args[0] = modifiedArgs;
445
+ }
446
+ }
447
+ }
448
+
449
+ // ========== 自动注入时间戳(在用户 hook 之后执行)==========
450
+ if (this.definition._internalHooks?.timestamps) {
451
+ args = this._applyTimestamps(opType, method, args);
452
+ }
453
+
454
+ // ========== 执行原始方法 ==========
455
+ const result = await this.collection[method](...args);
456
+
457
+ // ========== After Hook ==========
458
+ if (typeof opHooks.after === 'function') {
459
+ try {
460
+ const modifiedResult = await opHooks.after(ctx, result);
461
+ // 如果 after 返回值,使用修改后的结果
462
+ if (modifiedResult !== undefined) {
463
+ return modifiedResult;
464
+ }
465
+ } catch (err) {
466
+ // 🔧 修复:after hook 失败记录警告,但不影响操作结果
467
+ if (this.msq && this.msq.logger) {
468
+ this.msq.logger.warn(`[Model] After hook failed for ${method}:`, err.message);
469
+ }
470
+ // 不抛出错误,返回原始结果
471
+ }
472
+ }
473
+
474
+ return result;
475
+ }
476
+
477
+ /**
478
+ * 应用时间戳到 incrementOne
479
+ *
480
+ * incrementOne 的参数: (filter, field, increment, options)
481
+ * 我们需要在底层的 findOneAndUpdate 调用中注入 $set.updatedAt
482
+ *
483
+ * @private
484
+ * @param {Array} args - incrementOne 参数数组
485
+ * @returns {Array} 修改后的参数
486
+ */
487
+ _applyTimestampsToIncrementOne(args) {
488
+ const config = this.definition._internalHooks.timestamps;
489
+ const now = new Date();
490
+
491
+ // 找到 options 参数(可能在 args[2] 或 args[3])
492
+ let optionsIndex = -1;
493
+ if (args[3] && typeof args[3] === 'object') {
494
+ optionsIndex = 3;
495
+ } else if (args[2] && typeof args[2] === 'object' && typeof args[2] !== 'number') {
496
+ // args[2] 是对象且不是数字(increment)
497
+ optionsIndex = 2;
498
+ }
499
+
500
+ // 创建或修改 options,添加 $set.updatedAt
501
+ if (optionsIndex === -1) {
502
+ // 没有 options,创建一个
503
+ args[3] = { $set: { [config.updatedAt]: now } };
504
+ } else {
505
+ // 有 options,合并 $set
506
+ const options = args[optionsIndex];
507
+ if (!options.$set) {
508
+ options.$set = {};
509
+ }
510
+ options.$set[config.updatedAt] = now;
511
+ }
512
+
513
+ return args;
514
+ }
515
+
516
+ /**
517
+ * 应用时间戳
518
+ *
519
+ * @private
520
+ * @param {string} opType - 操作类型
521
+ * @param {string} method - 方法名
522
+ * @param {Array} args - 参数
523
+ * @returns {Array} 修改后的参数
524
+ */
525
+ _applyTimestamps(opType, method, args) {
526
+ const config = this.definition._internalHooks.timestamps;
527
+ const now = new Date();
528
+
529
+ if (opType === 'insert') {
530
+ // insertOne/insertMany
531
+ const docs = args[0];
532
+
533
+ if (Array.isArray(docs)) {
534
+ // insertMany
535
+ args[0] = docs.map(doc => {
536
+ const newDoc = { ...doc };
537
+ // 🔧 修复:只在用户未手动设置时添加时间戳
538
+ if (config.createdAt && !doc[config.createdAt]) {
539
+ newDoc[config.createdAt] = now;
540
+ }
541
+ if (config.updatedAt && !doc[config.updatedAt]) {
542
+ newDoc[config.updatedAt] = now;
543
+ }
544
+ return newDoc;
545
+ });
546
+ } else {
547
+ // insertOne
548
+ const newDoc = { ...docs };
549
+ // 🔧 修复:只在用户未手动设置时添加时间戳
550
+ if (config.createdAt && !docs[config.createdAt]) {
551
+ newDoc[config.createdAt] = now;
552
+ }
553
+ if (config.updatedAt && !docs[config.updatedAt]) {
554
+ newDoc[config.updatedAt] = now;
555
+ }
556
+ args[0] = newDoc;
557
+ }
558
+ } else if (opType === 'update') {
559
+ // updateOne/updateMany/replaceOne/upsertOne/findOneAndUpdate/findOneAndReplace/incrementOne
560
+
561
+ if (method.startsWith('upsert')) {
562
+ // 🔧 upsert 特殊处理:插入时添加 createdAt,更新时只更新 updatedAt
563
+ const update = args[1] || {};
564
+
565
+ // $setOnInsert: 仅在插入新文档时执行
566
+ if (config.createdAt) {
567
+ if (!update.$setOnInsert) {
568
+ update.$setOnInsert = {};
569
+ }
570
+ update.$setOnInsert[config.createdAt] = now;
571
+ }
572
+
573
+ // $set: 每次都执行(插入和更新都会设置 updatedAt)
574
+ if (config.updatedAt) {
575
+ if (!update.$set) {
576
+ update.$set = {};
577
+ }
578
+ update.$set[config.updatedAt] = now;
579
+ }
580
+
581
+ args[1] = update;
582
+ } else if (method === 'replaceOne' || method === 'findOneAndReplace') {
583
+ // replaceOne/findOneAndReplace: 直接在文档中添加(不能使用操作符)
584
+ if (config.updatedAt) {
585
+ const replacement = args[1] || {};
586
+ // 🔧 修复:只在用户未手动设置时添加 updatedAt
587
+ if (!replacement[config.updatedAt]) {
588
+ replacement[config.updatedAt] = now;
589
+ }
590
+ args[1] = replacement;
591
+ }
592
+ } else if (method.startsWith('increment')) {
593
+ // 🔧 incrementOne 特殊处理
594
+ // incrementOne(filter, field, increment, options)
595
+ // Model 层无法直接修改内部的 $inc 对象,但可以通过 options 传递时间戳更新
596
+ // 注意:incrementOne 内部会调用 findOneAndUpdate,所以我们不在这里处理
597
+ // 而是让 incrementOne 自己处理(需要修改 increment-one.js)
598
+ // 暂时跳过
599
+ } else if (config.updatedAt) {
600
+ // 其他 update 操作(updateOne/updateMany/findOneAndUpdate):在 $set 中添加 updatedAt
601
+ const update = args[1] || {};
602
+
603
+ if (!update.$set) {
604
+ update.$set = {};
605
+ }
606
+
607
+ update.$set[config.updatedAt] = now;
608
+ args[1] = update;
609
+ }
610
+ }
611
+
612
+ return args;
613
+ }
614
+
615
+ /**
616
+ * 提取操作类型
617
+ *
618
+ * @private
619
+ * @param {string} method - 方法名
620
+ * @returns {string} 操作类型(find/insert/update/delete)
621
+ */
622
+ _getOperationType(method) {
623
+ // findOneAnd* 方法需要特殊处理(优先判断)
624
+ if (method === 'findOneAndUpdate' || method === 'findOneAndReplace' || method === 'findOneAndDelete') {
625
+ if (method === 'findOneAndDelete') {
626
+ return 'delete';
627
+ }
628
+ return 'update';
629
+ }
630
+
631
+ if (method.startsWith('find') || method === 'aggregate' || method === 'count') {
632
+ return 'find';
633
+ }
634
+ if (method.startsWith('insert')) {
635
+ return 'insert';
636
+ }
637
+ if (method.startsWith('update') || method.startsWith('replace') || method.startsWith('upsert') || method.startsWith('increment')) {
638
+ return 'update';
639
+ }
640
+ if (method.startsWith('delete')) {
641
+ return 'delete';
642
+ }
643
+ return 'unknown';
644
+ }
645
+
646
+ /**
647
+ * 数据验证方法
648
+ *
649
+ * @param {Object} data - 待验证的数据
650
+ * @param {Object} [options] - 验证选项
651
+ * @param {string} [options.locale] - 语言(zh-CN/en-US等)
652
+ * @returns {Object} 验证结果 { valid: boolean, errors: Array, data: Object }
653
+ *
654
+ * @example
655
+ * const result = model.validate({ username: 'test' });
656
+ * if (!result.valid) {
657
+ * console.error(result.errors);
658
+ * }
659
+ */
660
+ validate(data, options = {}) {
661
+ try {
662
+ // 获取 schema(优先使用缓存)
663
+ let schema = this._schemaCache;
664
+
665
+ // 如果缓存为空,重新编译
666
+ if (!schema) {
667
+ if (typeof this.definition.schema === 'function') {
668
+ schema = this.definition.schema.call(this.definition, dsl);
669
+ } else {
670
+ schema = this.definition.schema;
671
+ }
672
+ }
673
+
674
+ // 执行验证
675
+ const result = validate(schema, data);
676
+
677
+ // 返回统一格式
678
+ return {
679
+ valid: result.valid,
680
+ errors: result.errors || [],
681
+ data: result.data || data
682
+ };
683
+
684
+ } catch (err) {
685
+ // 验证过程失败
686
+ return {
687
+ valid: false,
688
+ errors: [{
689
+ field: '_schema',
690
+ message: `Schema validation failed: ${err.message}`,
691
+ code: 'SCHEMA_ERROR'
692
+ }],
693
+ data
694
+ };
695
+ }
696
+ }
697
+
698
+ /**
699
+ * 自动创建索引
700
+ *
701
+ * @private
702
+ * @returns {Promise<void>}
703
+ */
704
+ async _createIndexes() {
705
+ const indexes = this.definition.indexes || [];
706
+ if (!Array.isArray(indexes) || indexes.length === 0) {
707
+ return;
708
+ }
709
+
710
+ for (const indexSpec of indexes) {
711
+ try {
712
+ if (indexSpec.key && typeof indexSpec.key === 'object') {
713
+ const options = { ...indexSpec };
714
+ delete options.key;
715
+ await this.collection.createIndex(indexSpec.key, options);
716
+ }
717
+ } catch (err) {
718
+ // 索引已存在或其他错误,记录但不中断
719
+ if (this.msq && this.msq.logger) {
720
+ this.msq.logger.warn(`[Model] Index creation warning:`, err.message);
721
+ }
722
+ }
723
+ }
724
+ }
725
+
726
+ /**
727
+ * 获取关系定义
728
+ *
729
+ * @returns {Object} 关系定义对象
730
+ *
731
+ * @example
732
+ * const relations = model.getRelations();
733
+ * // { posts: { type: 'hasMany', target: 'Post', ... } }
734
+ */
735
+ getRelations() {
736
+ return this.definition.relations || {};
737
+ }
738
+
739
+ /**
740
+ * 获取 enums 配置
741
+ *
742
+ * @returns {Object} 枚举配置对象
743
+ *
744
+ * @example
745
+ * const enums = model.getEnums();
746
+ * // { role: 'admin|user', status: 'active|inactive' }
747
+ */
748
+ getEnums() {
749
+ return this.definition.enums || {};
750
+ }
751
+ }
752
+
753
+ // ========== 导出 ==========
754
+ module.exports = Model;
755
+ module.exports.ModelInstance = ModelInstance;
756
+