@hazejs/orm 0.0.0-stage → 0.1.2

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,21 @@
1
+ import { B as DefinedMongoModel, F as MongoModelQuery, G as MongoModelSettings, H as MongoModel, I as MongoQuery, J as MongoSession, K as hydrateMongoModel, L as MongoQueryOptions, R as MongoWhere, U as MongoModelClass, V as DocOf, W as MongoModelMeta, X as resolveMongo, Y as mongoTransaction, q as metaOfMongo, z as toObjectId } from "../model-CBky_ILK.mjs";
2
+ import { ClientSession, Db, Document, ObjectId } from "mongodb";
3
+ export interface ScopedMongo {
4
+ /** 集合查询构造器:`Mongo.collection<UserDoc>('users').where('status', 1).select()` */
5
+ collection: <T extends Document = Document>(name: string) => MongoQuery<T>;
6
+ /** 事务(需要副本集):回调内的 Mongo / MongoModel 操作自动加入事务 */
7
+ transaction: <R>(callback: (session: ClientSession) => Promise<R>) => Promise<R>;
8
+ /** 底层 mongodb Db 实例(索引管理、变更流等) */
9
+ db: () => Promise<Db>;
10
+ }
11
+ /** MongoDB 门面:默认连接的快捷入口,`Mongo.connection(name)` 切换连接 */
12
+ export declare const Mongo: {
13
+ connection: (name: string) => ScopedMongo;
14
+ /** 集合查询构造器:`Mongo.collection<UserDoc>('users').where('status', 1).select()` */
15
+ collection: <T extends Document = Document>(name: string) => MongoQuery<T>;
16
+ /** 事务(需要副本集):回调内的 Mongo / MongoModel 操作自动加入事务 */
17
+ transaction: <R>(callback: (session: ClientSession) => Promise<R>) => Promise<R>;
18
+ /** 底层 mongodb Db 实例(索引管理、变更流等) */
19
+ db: () => Promise<Db>;
20
+ };
21
+ export { type DefinedMongoModel, type DocOf, MongoModel, type MongoModelClass, type MongoModelMeta, MongoModelQuery, type MongoModelSettings, MongoQuery, type MongoQueryOptions, type MongoSession, type MongoWhere, ObjectId, hydrateMongoModel, metaOfMongo, mongoTransaction, resolveMongo, toObjectId };
@@ -0,0 +1,630 @@
1
+ import { _ as DatabaseError, b as translateDatabaseError, d as pascalCase, g as useDatabaseManager, l as isPlainObject, s as loadRelations, t as ActiveRecord, v as ModelNotFoundException } from "../record-DtkUCWRf.mjs";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { ObjectId, ObjectId as ObjectId$1 } from "mongodb";
4
+ // src/mongo/context.ts
5
+ /** 事务会话:连接名 → ClientSession(与 SQL 事务一样隐式传播) */
6
+ const sessions = new AsyncLocalStorage();
7
+ async function resolveMongo(connection) {
8
+ const resolved = await useDatabaseManager().connection(connection);
9
+ if (resolved.dialect !== "mongodb") throw new DatabaseError(`Connection "${resolved.name}" uses driver "${resolved.driver}", not mongodb`);
10
+ return {
11
+ name: resolved.name,
12
+ db: resolved.db,
13
+ client: resolved.client,
14
+ session: sessions.getStore()?.get(resolved.name)
15
+ };
16
+ }
17
+ /**
18
+ * MongoDB 事务(需要副本集或分片集群)。回调内的 Mongo / MongoModel 操作自动加入事务;
19
+ * MongoDB 不支持嵌套事务,嵌套调用直接复用外层事务。
20
+ */
21
+ async function mongoTransaction(callback, connection) {
22
+ const mongo = await resolveMongo(connection);
23
+ if (mongo.session) return callback(mongo.session);
24
+ const session = mongo.client.startSession();
25
+ try {
26
+ let result;
27
+ await session.withTransaction(async () => {
28
+ result = await sessions.run(new Map(sessions.getStore()).set(mongo.name, session), () => callback(session));
29
+ });
30
+ return result;
31
+ } finally {
32
+ await session.endSession();
33
+ }
34
+ }
35
+ // src/mongo/model.ts
36
+ /** 解析软删除与时间戳字段(文档无固定结构,按配置决定) */
37
+ function metaOfMongo(model) {
38
+ const timestamps = model.timestamps === false ? {
39
+ createdAt: false,
40
+ updatedAt: false
41
+ } : model.timestamps === true ? {} : model.timestamps;
42
+ return {
43
+ softDelete: model.softDelete ? typeof model.softDelete === "string" ? model.softDelete : "deletedAt" : void 0,
44
+ createdAt: timestamps.createdAt === false ? void 0 : timestamps.createdAt ?? "createdAt",
45
+ updatedAt: timestamps.updatedAt === false ? void 0 : timestamps.updatedAt ?? "updatedAt"
46
+ };
47
+ }
48
+ /**
49
+ * MongoDB 模型,API 与 SQL 的 Model 一致:
50
+ *
51
+ * ```ts
52
+ * interface UserDoc { name: string, email: string, status: number }
53
+ *
54
+ * export class User extends MongoModel.define<UserDoc>('users') {
55
+ * static override softDelete = true
56
+ * posts() {
57
+ * return this.hasMany(Post, 'userId')
58
+ * }
59
+ * }
60
+ *
61
+ * const user = await User.findOrFail('6650…') // 字符串 _id 自动转为 ObjectId
62
+ * await User.where('status', 1).with('posts').paginate(1, 20)
63
+ * ```
64
+ */
65
+ var MongoModel = class extends ActiveRecord {
66
+ static connection = void 0;
67
+ /** 软删除:true 使用 deletedAt 字段,或指定字段名 */
68
+ static softDelete = false;
69
+ /** 自动维护 createdAt / updatedAt,可指定字段名或设为 false */
70
+ static timestamps = true;
71
+ constructor() {
72
+ super();
73
+ return new Proxy(this, {
74
+ get(target, property, receiver) {
75
+ if (typeof property === "symbol" || property in target) return Reflect.get(target, property, receiver);
76
+ return target.getAttr(property);
77
+ },
78
+ set(target, property, value, receiver) {
79
+ if (typeof property === "symbol" || property in target) return Reflect.set(target, property, value, receiver);
80
+ target.setAttr(property, value);
81
+ return true;
82
+ }
83
+ });
84
+ }
85
+ /** 声明模型对应的集合:`class User extends MongoModel.define<UserDoc>('users') {}` */
86
+ static define(collection) {
87
+ class Defined extends MongoModel {
88
+ static collection = collection;
89
+ }
90
+ return Defined;
91
+ }
92
+ static primaryKeyName() {
93
+ return "_id";
94
+ }
95
+ static query() {
96
+ return new MongoModelQuery(this);
97
+ }
98
+ static where(...args) {
99
+ return new MongoModelQuery(this).where(...args);
100
+ }
101
+ static whereIn(field, values) {
102
+ return new MongoModelQuery(this).whereIn(field, values);
103
+ }
104
+ static order(field, direction = "asc") {
105
+ return new MongoModelQuery(this).order(field, direction);
106
+ }
107
+ static with(...relations) {
108
+ return new MongoModelQuery(this).with(...relations);
109
+ }
110
+ static scope(name, ...args) {
111
+ return new MongoModelQuery(this).scope(name, ...args);
112
+ }
113
+ static withTrashed() {
114
+ return new MongoModelQuery(this).withTrashed();
115
+ }
116
+ static onlyTrashed() {
117
+ return new MongoModelQuery(this).onlyTrashed();
118
+ }
119
+ static find(id) {
120
+ return new MongoModelQuery(this).find(id);
121
+ }
122
+ static findOrFail(id) {
123
+ return new MongoModelQuery(this).findOrFail(id);
124
+ }
125
+ static all() {
126
+ return new MongoModelQuery(this).select();
127
+ }
128
+ static paginate(page, perPage) {
129
+ return new MongoModelQuery(this).paginate(page, perPage);
130
+ }
131
+ static count() {
132
+ return new MongoModelQuery(this).count();
133
+ }
134
+ static async create(data) {
135
+ const model = new this();
136
+ const record = model;
137
+ record.fill(data);
138
+ await record.save();
139
+ return model;
140
+ }
141
+ static async destroy(ids) {
142
+ const list = Array.isArray(ids) ? ids : [ids];
143
+ const models = list.length ? await new MongoModelQuery(this).whereIn("_id", list).select() : [];
144
+ for (const model of models) await model.delete();
145
+ return models.length;
146
+ }
147
+ async save() {
148
+ const meta = this.$meta();
149
+ await this.runHook("onBeforeWrite");
150
+ if (!this.$exists) {
151
+ await this.runHook("onBeforeInsert");
152
+ const now = /* @__PURE__ */ new Date();
153
+ for (const key of [meta.createdAt, meta.updatedAt]) if (key && this.$attributes[key] == null) this.$attributes[key] = now;
154
+ const id = await this.$query().insertGetId(this.$attributes);
155
+ this.$attributes._id = id;
156
+ this.$exists = true;
157
+ this.syncOriginal();
158
+ await this.runHook("onAfterInsert");
159
+ } else {
160
+ const { _id: _ignored, ...dirty } = this.getDirty();
161
+ if (Object.keys(dirty).length) {
162
+ await this.runHook("onBeforeUpdate");
163
+ if (meta.updatedAt && !(meta.updatedAt in dirty)) dirty[meta.updatedAt] = this.$attributes[meta.updatedAt] = /* @__PURE__ */ new Date();
164
+ await this.$whereKey().update(dirty);
165
+ this.syncOriginal();
166
+ await this.runHook("onAfterUpdate");
167
+ }
168
+ }
169
+ await this.runHook("onAfterWrite");
170
+ return this;
171
+ }
172
+ async delete() {
173
+ if (!this.$exists) return false;
174
+ const { softDelete } = this.$meta();
175
+ await this.runHook("onBeforeDelete");
176
+ if (softDelete) {
177
+ const now = /* @__PURE__ */ new Date();
178
+ await this.$whereKey().update({ [softDelete]: now });
179
+ this.$attributes[softDelete] = now;
180
+ this.$original[softDelete] = now;
181
+ } else {
182
+ await this.$whereKey().delete();
183
+ this.$exists = false;
184
+ }
185
+ await this.runHook("onAfterDelete");
186
+ return true;
187
+ }
188
+ async forceDelete() {
189
+ if (!this.$exists) return false;
190
+ await this.runHook("onBeforeDelete");
191
+ await this.$whereKey().delete();
192
+ this.$exists = false;
193
+ await this.runHook("onAfterDelete");
194
+ return true;
195
+ }
196
+ async restore() {
197
+ const { softDelete } = this.$meta();
198
+ if (!softDelete) return this;
199
+ await this.runHook("onBeforeRestore");
200
+ await this.$whereKey().update({ [softDelete]: null });
201
+ this.$attributes[softDelete] = null;
202
+ this.$original[softDelete] = null;
203
+ await this.runHook("onAfterRestore");
204
+ return this;
205
+ }
206
+ trashed() {
207
+ const { softDelete } = this.$meta();
208
+ return softDelete ? this.$attributes[softDelete] != null : false;
209
+ }
210
+ async refresh() {
211
+ const doc = await this.$whereKey().find();
212
+ if (!doc) throw new DatabaseError(`${this.constructor.name} ${String(this.getKey())} no longer exists`);
213
+ this.$attributes = { ...doc };
214
+ this.syncOriginal();
215
+ return this;
216
+ }
217
+ /** 文档主键(ObjectId) */
218
+ get id() {
219
+ return this.$attributes._id;
220
+ }
221
+ $meta() {
222
+ return metaOfMongo(this.constructor);
223
+ }
224
+ $query() {
225
+ const model = this.constructor;
226
+ return new MongoQuery(model.collection, { connection: model.connection });
227
+ }
228
+ $whereKey() {
229
+ return this.$query().where("_id", this.$attributes._id);
230
+ }
231
+ };
232
+ /** 由文档构造模型(供 MongoModelQuery 使用) */
233
+ function hydrateMongoModel(model, doc) {
234
+ return MongoModel.hydrate.call(model, doc);
235
+ }
236
+ // src/mongo/query.ts
237
+ /** 把字符串形式的 _id 转为 ObjectId(其它字段原样保留) */
238
+ function toObjectId(value) {
239
+ return typeof value === "string" && ObjectId$1.isValid(value) && value.length === 24 ? new ObjectId$1(value) : value;
240
+ }
241
+ /** SQL LIKE 模式 → 正则:% 任意长度,_ 单个字符 */
242
+ function likeToRegex(pattern) {
243
+ const source = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
244
+ return new RegExp(`^${source}$`);
245
+ }
246
+ /**
247
+ * MongoDB 查询构造器,方法与 SQL 的 Db.table() 一致:
248
+ *
249
+ * ```ts
250
+ * await Mongo.collection<UserDoc>('users').where('status', 1).order('createdAt', 'desc').paginate(1, 20)
251
+ * ```
252
+ *
253
+ * Mongo 特有能力:`aggregate(pipeline)`、`where({ tags: { $all: ['a'] } })` 直接使用查询运算符。
254
+ */
255
+ var MongoQuery = class MongoQuery {
256
+ collectionName;
257
+ connectionName;
258
+ parts = [];
259
+ sorts = [];
260
+ limitValue;
261
+ offsetValue;
262
+ fields;
263
+ constructor(collection, options = {}) {
264
+ this.collectionName = collection;
265
+ this.connectionName = options.connection;
266
+ }
267
+ connection(name) {
268
+ this.connectionName = name;
269
+ return this;
270
+ }
271
+ where(...args) {
272
+ return this.addCondition("and", args);
273
+ }
274
+ orWhere(...args) {
275
+ return this.addCondition("or", args);
276
+ }
277
+ whereIn(field, values) {
278
+ return this.pushCondition("and", { [field]: { $in: this.normalizeList(field, values) } });
279
+ }
280
+ whereNotIn(field, values) {
281
+ return this.pushCondition("and", { [field]: { $nin: this.normalizeList(field, values) } });
282
+ }
283
+ /** 字段为 null 或不存在 */
284
+ whereNull(field) {
285
+ return this.pushCondition("and", { [field]: null });
286
+ }
287
+ whereNotNull(field) {
288
+ return this.pushCondition("and", { [field]: { $ne: null } });
289
+ }
290
+ whereBetween(field, range) {
291
+ return this.pushCondition("and", { [field]: {
292
+ $gte: range[0],
293
+ $lte: range[1]
294
+ } });
295
+ }
296
+ whereNotBetween(field, range) {
297
+ return this.pushCondition("and", { $or: [{ [field]: { $lt: range[0] } }, { [field]: { $gt: range[1] } }] });
298
+ }
299
+ /** SQL 风格的 LIKE 模式(% 与 _),区分大小写 */
300
+ whereLike(field, pattern) {
301
+ return this.pushCondition("and", { [field]: { $regex: likeToRegex(pattern) } });
302
+ }
303
+ whereNotLike(field, pattern) {
304
+ return this.pushCondition("and", { [field]: { $not: likeToRegex(pattern) } });
305
+ }
306
+ when(value, callback) {
307
+ if (value !== void 0 && value !== null && value !== false && value !== "") callback(this, value);
308
+ return this;
309
+ }
310
+ /** 当前条件组成的过滤器 */
311
+ buildFilter() {
312
+ let result;
313
+ for (const part of this.parts) result = result === void 0 ? part.filter : { [part.bool === "and" ? "$and" : "$or"]: [result, part.filter] };
314
+ return result ?? {};
315
+ }
316
+ get hasConditions() {
317
+ return this.parts.length > 0;
318
+ }
319
+ order(field, direction = "asc") {
320
+ if (typeof field === "string") this.sorts.push([field, direction === "desc" ? -1 : 1]);
321
+ else for (const [key, dir] of Object.entries(field)) this.order(key, dir ?? "asc");
322
+ return this;
323
+ }
324
+ limit(value) {
325
+ this.limitValue = value;
326
+ return this;
327
+ }
328
+ offset(value) {
329
+ this.offsetValue = value;
330
+ return this;
331
+ }
332
+ page(page, perPage = 15) {
333
+ return this.limit(perPage).offset((Math.max(1, page) - 1) * perPage);
334
+ }
335
+ field(...fields) {
336
+ this.fields = fields;
337
+ return this;
338
+ }
339
+ async select() {
340
+ const mongo = await this.session();
341
+ const cursor = mongo.db.collection(this.collectionName).find(this.scopedFilter(), {
342
+ session: mongo.session,
343
+ projection: this.fields ? Object.fromEntries(this.fields.map((field) => [field, 1])) : void 0,
344
+ sort: this.sorts.length ? this.sorts : void 0,
345
+ skip: this.offsetValue,
346
+ limit: this.limitValue
347
+ });
348
+ return this.transform(await cursor.toArray());
349
+ }
350
+ async find() {
351
+ const [first] = await this.clone().limit(1).select();
352
+ return first;
353
+ }
354
+ async findOrFail() {
355
+ const result = await this.find();
356
+ if (result === void 0) throw new ModelNotFoundException(this.label());
357
+ return result;
358
+ }
359
+ async value(field) {
360
+ return (await this.clone().field(field).find())?.[field];
361
+ }
362
+ async column(field) {
363
+ return (await this.clone().field(field).select()).map((doc) => doc[field]);
364
+ }
365
+ async count() {
366
+ const mongo = await this.session();
367
+ return mongo.db.collection(this.collectionName).countDocuments(this.scopedFilter(), { session: mongo.session });
368
+ }
369
+ async sum(field) {
370
+ return Number(await this.group({ $sum: `$${field}` }) ?? 0);
371
+ }
372
+ async avg(field) {
373
+ const value = await this.group({ $avg: `$${field}` });
374
+ return value === null || value === void 0 ? null : Number(value);
375
+ }
376
+ async max(field) {
377
+ return await this.group({ $max: `$${field}` }) ?? null;
378
+ }
379
+ async min(field) {
380
+ return await this.group({ $min: `$${field}` }) ?? null;
381
+ }
382
+ async exists() {
383
+ return await this.count() > 0;
384
+ }
385
+ async paginate(page = 1, perPage = 15) {
386
+ const current = Math.max(1, Math.floor(page));
387
+ const total = await this.count();
388
+ return {
389
+ data: await this.clone().page(current, perPage).select(),
390
+ total,
391
+ page: current,
392
+ perPage,
393
+ lastPage: Math.max(1, Math.ceil(total / perPage))
394
+ };
395
+ }
396
+ async chunk(size, callback) {
397
+ for (let page = 1;; page++) {
398
+ const docs = await this.clone().page(page, size).select();
399
+ if (!docs.length || await callback(docs, page) === false || docs.length < size) return;
400
+ }
401
+ }
402
+ /** 聚合管道(自动在开头加上当前条件的 $match) */
403
+ async aggregate(pipeline) {
404
+ const mongo = await this.session();
405
+ const filter = this.scopedFilter();
406
+ const stages = Object.keys(filter).length ? [{ $match: filter }, ...pipeline] : pipeline;
407
+ return mongo.db.collection(this.collectionName).aggregate(stages, { session: mongo.session }).toArray();
408
+ }
409
+ /** 插入一条或多条,返回带 _id 的文档 */
410
+ async insert(docs) {
411
+ const list = (Array.isArray(docs) ? docs : [docs]).map((doc) => ({ ...doc }));
412
+ if (!list.length) return [];
413
+ const mongo = await this.session();
414
+ const result = await this.write(() => mongo.db.collection(this.collectionName).insertMany(list, { session: mongo.session }));
415
+ return list.map((doc, index) => ({
416
+ ...doc,
417
+ _id: result.insertedIds[index]
418
+ }));
419
+ }
420
+ async insertGetId(doc) {
421
+ const [inserted] = await this.insert(doc);
422
+ return inserted._id;
423
+ }
424
+ /** 按条件更新(普通字段使用 $set,也可直接传 $inc / $push 等运算符),返回匹配数量 */
425
+ async update(values, options = {}) {
426
+ const filter = this.writableFilter("update", options);
427
+ const update = Object.keys(values).some((key) => key.startsWith("$")) ? values : { $set: values };
428
+ const mongo = await this.session();
429
+ return (await this.write(() => mongo.db.collection(this.collectionName).updateMany(filter, update, { session: mongo.session }))).matchedCount;
430
+ }
431
+ async delete(options = {}) {
432
+ const filter = this.writableFilter("delete", options);
433
+ const mongo = await this.session();
434
+ return (await this.write(() => mongo.db.collection(this.collectionName).deleteMany(filter, { session: mongo.session }))).deletedCount;
435
+ }
436
+ increment(field, step = 1) {
437
+ return this.update({ $inc: { [field]: step } });
438
+ }
439
+ decrement(field, step = 1) {
440
+ return this.update({ $inc: { [field]: -step } });
441
+ }
442
+ /** 生成的过滤器(调试用) */
443
+ toFilter() {
444
+ return this.scopedFilter();
445
+ }
446
+ clone() {
447
+ const copy = Object.create(Object.getPrototypeOf(this));
448
+ Object.assign(copy, this);
449
+ copy.parts = [...this.parts];
450
+ copy.sorts = [...this.sorts];
451
+ copy.fields = this.fields && [...this.fields];
452
+ return copy;
453
+ }
454
+ scopedFilter() {
455
+ return this.buildFilter();
456
+ }
457
+ async transform(docs) {
458
+ return docs;
459
+ }
460
+ label() {
461
+ return this.collectionName;
462
+ }
463
+ session() {
464
+ return resolveMongo(this.connectionName);
465
+ }
466
+ pushCondition(bool, filter) {
467
+ this.parts.push({
468
+ bool,
469
+ filter
470
+ });
471
+ return this;
472
+ }
473
+ addCondition(bool, args) {
474
+ const filter = this.parseCondition(args);
475
+ return filter ? this.pushCondition(bool, filter) : this;
476
+ }
477
+ parseCondition(args) {
478
+ if (args.length === 1) {
479
+ const [condition] = args;
480
+ if (typeof condition === "function") {
481
+ const group = new MongoQuery(this.collectionName);
482
+ condition(group);
483
+ return group.hasConditions ? group.buildFilter() : void 0;
484
+ }
485
+ if (isPlainObject(condition)) {
486
+ const entries = Object.entries(condition).map(([field, value]) => this.compare(field, "=", value));
487
+ return entries.length > 1 ? { $and: entries } : entries[0];
488
+ }
489
+ throw new TypeError("where() expects a field, an object or a callback");
490
+ }
491
+ if (args.length === 2) return this.compare(args[0], "=", args[1]);
492
+ return this.compare(args[0], args[1], args[2]);
493
+ }
494
+ compare(field, operator, raw) {
495
+ const value = this.normalize(field, raw);
496
+ switch (operator) {
497
+ case "=":
498
+ if (Array.isArray(value)) return { [field]: { $in: this.normalizeList(field, value) } };
499
+ return { [field]: value };
500
+ case "!=":
501
+ case "<>": return { [field]: Array.isArray(value) ? { $nin: this.normalizeList(field, value) } : { $ne: value } };
502
+ case ">": return { [field]: { $gt: value } };
503
+ case ">=": return { [field]: { $gte: value } };
504
+ case "<": return { [field]: { $lt: value } };
505
+ case "<=": return { [field]: { $lte: value } };
506
+ case "like": return { [field]: { $regex: likeToRegex(String(raw)) } };
507
+ case "not like": return { [field]: { $not: likeToRegex(String(raw)) } };
508
+ case "in": return { [field]: { $in: this.normalizeList(field, raw) } };
509
+ case "not in": return { [field]: { $nin: this.normalizeList(field, raw) } };
510
+ case "between": return { [field]: {
511
+ $gte: raw[0],
512
+ $lte: raw[1]
513
+ } };
514
+ case "not between": return { $or: [{ [field]: { $lt: raw[0] } }, { [field]: { $gt: raw[1] } }] };
515
+ default: throw new Error(`Unsupported operator "${String(operator)}"`);
516
+ }
517
+ }
518
+ normalize(field, value) {
519
+ return field === "_id" ? toObjectId(value) : value;
520
+ }
521
+ normalizeList(field, values) {
522
+ return values.map((value) => this.normalize(field, value));
523
+ }
524
+ async write(run) {
525
+ try {
526
+ return await run();
527
+ } catch (error) {
528
+ throw translateDatabaseError(error);
529
+ }
530
+ }
531
+ writableFilter(action, options) {
532
+ const filter = this.scopedFilter();
533
+ if (!Object.keys(filter).length && !options.all) throw new DatabaseError(`Refusing to ${action} all documents of "${this.collectionName}" without conditions. Pass { all: true } to confirm.`);
534
+ return filter;
535
+ }
536
+ async group(accumulator) {
537
+ const [row] = await this.aggregate([{ $group: {
538
+ _id: null,
539
+ value: accumulator
540
+ } }]);
541
+ return row?.value;
542
+ }
543
+ };
544
+ /** Mongo 模型查询:结果为模型实例,应用软删除范围,支持预加载与作用域 */
545
+ var MongoModelQuery = class extends MongoQuery {
546
+ model;
547
+ trashed = "without";
548
+ eager = [];
549
+ constructor(model) {
550
+ super(model.collection, { connection: model.connection });
551
+ this.model = model;
552
+ }
553
+ withTrashed() {
554
+ this.trashed = "with";
555
+ return this;
556
+ }
557
+ onlyTrashed() {
558
+ this.trashed = "only";
559
+ return this;
560
+ }
561
+ with(...relations) {
562
+ this.eager.push(...relations);
563
+ return this;
564
+ }
565
+ scope(name, ...args) {
566
+ const method = this.model[`scope${pascalCase(name)}`];
567
+ if (typeof method !== "function") throw new TypeError(`Scope "${name}" is not defined on ${this.model.name} (expected static scope${pascalCase(name)}(query))`);
568
+ method.call(this.model, this, ...args);
569
+ return this;
570
+ }
571
+ /** 按 _id 查询(字符串自动转为 ObjectId);不传时取第一条 */
572
+ async find(id) {
573
+ if (id === void 0) return super.find();
574
+ const [first] = await this.clone().where("_id", id).limit(1).select();
575
+ return first;
576
+ }
577
+ async findOrFail(id) {
578
+ const model = await this.find(id);
579
+ if (!model) throw new ModelNotFoundException(this.model.name, id);
580
+ return model;
581
+ }
582
+ /** 批量删除:启用软删除时只标记删除时间 */
583
+ async delete(options = {}) {
584
+ const { softDelete } = metaOfMongo(this.model);
585
+ if (softDelete && this.trashed !== "only") return this.update({ [softDelete]: /* @__PURE__ */ new Date() }, options);
586
+ return super.delete(options);
587
+ }
588
+ async forceDelete(options = {}) {
589
+ return super.delete(options);
590
+ }
591
+ async restore(options = {}) {
592
+ const { softDelete } = metaOfMongo(this.model);
593
+ if (!softDelete) return 0;
594
+ return this.withTrashed().update({ [softDelete]: null }, options);
595
+ }
596
+ scopedFilter() {
597
+ const filter = this.buildFilter();
598
+ const { softDelete } = metaOfMongo(this.model);
599
+ if (!softDelete || this.trashed === "with") return filter;
600
+ const scope = { [softDelete]: this.trashed === "only" ? { $ne: null } : null };
601
+ return Object.keys(filter).length ? { $and: [filter, scope] } : scope;
602
+ }
603
+ async transform(docs) {
604
+ const models = docs.map((doc) => hydrateMongoModel(this.model, doc));
605
+ if (this.eager.length) await loadRelations(models, this.eager);
606
+ return models;
607
+ }
608
+ label() {
609
+ return this.model.name;
610
+ }
611
+ clone() {
612
+ const copy = super.clone();
613
+ copy.eager = [...this.eager];
614
+ return copy;
615
+ }
616
+ };
617
+ // src/mongo/index.ts
618
+ function scoped(connection) {
619
+ return {
620
+ collection: (name) => new MongoQuery(name, { connection }),
621
+ transaction: (callback) => mongoTransaction(callback, connection),
622
+ db: async () => (await resolveMongo(connection)).db
623
+ };
624
+ }
625
+ /** MongoDB 门面:默认连接的快捷入口,`Mongo.connection(name)` 切换连接 */
626
+ const Mongo = {
627
+ ...scoped(),
628
+ connection: (name) => scoped(name)
629
+ };
630
+ export { Mongo, MongoModel, MongoModelQuery, MongoQuery, ObjectId, hydrateMongoModel, metaOfMongo, mongoTransaction, resolveMongo, toObjectId };