@classytic/mongokit 3.0.6 → 3.1.1
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 +625 -463
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.js +3 -484
- package/dist/chunks/chunk-2ZN65ZOP.js +93 -0
- package/dist/chunks/chunk-CF6FLC2G.js +46 -0
- package/dist/chunks/chunk-CSLJ2PL2.js +1092 -0
- package/dist/chunks/chunk-IT7DCOKR.js +299 -0
- package/dist/chunks/chunk-M2XHQGZB.js +361 -0
- package/dist/chunks/chunk-SAKSLT47.js +470 -0
- package/dist/chunks/chunk-VJXDGP3C.js +14 -0
- package/dist/{index-CkwbNdpJ.d.ts → index-C2NCVxJK.d.ts} +170 -3
- package/dist/index.d.ts +997 -8
- package/dist/index.js +1143 -2476
- package/dist/{queryParser-Do3SgsyJ.d.ts → mongooseToJsonSchema-BKMxPbPp.d.ts} +8 -111
- package/dist/pagination/PaginationEngine.d.ts +1 -1
- package/dist/pagination/PaginationEngine.js +2 -368
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +4 -1170
- package/dist/{types-DDDYo18H.d.ts → types-DA0rs2Jh.d.ts} +109 -35
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +3 -711
- package/package.json +8 -3
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { create_exports } from './chunk-CF6FLC2G.js';
|
|
2
|
+
import { __export, createError } from './chunk-VJXDGP3C.js';
|
|
3
|
+
|
|
4
|
+
// src/actions/index.ts
|
|
5
|
+
var actions_exports = {};
|
|
6
|
+
__export(actions_exports, {
|
|
7
|
+
aggregate: () => aggregate_exports,
|
|
8
|
+
create: () => create_exports,
|
|
9
|
+
deleteActions: () => delete_exports,
|
|
10
|
+
read: () => read_exports,
|
|
11
|
+
update: () => update_exports
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// src/actions/read.ts
|
|
15
|
+
var read_exports = {};
|
|
16
|
+
__export(read_exports, {
|
|
17
|
+
count: () => count,
|
|
18
|
+
exists: () => exists,
|
|
19
|
+
getAll: () => getAll,
|
|
20
|
+
getById: () => getById,
|
|
21
|
+
getByQuery: () => getByQuery,
|
|
22
|
+
getOrCreate: () => getOrCreate,
|
|
23
|
+
tryGetByQuery: () => tryGetByQuery
|
|
24
|
+
});
|
|
25
|
+
function parsePopulate(populate) {
|
|
26
|
+
if (!populate) return [];
|
|
27
|
+
if (typeof populate === "string") {
|
|
28
|
+
return populate.split(",").map((p) => p.trim());
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(populate)) {
|
|
31
|
+
return populate.map((p) => typeof p === "string" ? p.trim() : p);
|
|
32
|
+
}
|
|
33
|
+
return [populate];
|
|
34
|
+
}
|
|
35
|
+
async function getById(Model, id, options = {}) {
|
|
36
|
+
const query = options.query ? Model.findOne({ _id: id, ...options.query }) : Model.findById(id);
|
|
37
|
+
if (options.select) query.select(options.select);
|
|
38
|
+
if (options.populate) query.populate(parsePopulate(options.populate));
|
|
39
|
+
if (options.lean) query.lean();
|
|
40
|
+
if (options.session) query.session(options.session);
|
|
41
|
+
const document = await query.exec();
|
|
42
|
+
if (!document && options.throwOnNotFound !== false) {
|
|
43
|
+
throw createError(404, "Document not found");
|
|
44
|
+
}
|
|
45
|
+
return document;
|
|
46
|
+
}
|
|
47
|
+
async function getByQuery(Model, query, options = {}) {
|
|
48
|
+
const mongoQuery = Model.findOne(query);
|
|
49
|
+
if (options.select) mongoQuery.select(options.select);
|
|
50
|
+
if (options.populate) mongoQuery.populate(parsePopulate(options.populate));
|
|
51
|
+
if (options.lean) mongoQuery.lean();
|
|
52
|
+
if (options.session) mongoQuery.session(options.session);
|
|
53
|
+
const document = await mongoQuery.exec();
|
|
54
|
+
if (!document && options.throwOnNotFound !== false) {
|
|
55
|
+
throw createError(404, "Document not found");
|
|
56
|
+
}
|
|
57
|
+
return document;
|
|
58
|
+
}
|
|
59
|
+
async function tryGetByQuery(Model, query, options = {}) {
|
|
60
|
+
return getByQuery(Model, query, { ...options, throwOnNotFound: false });
|
|
61
|
+
}
|
|
62
|
+
async function getAll(Model, query = {}, options = {}) {
|
|
63
|
+
let mongoQuery = Model.find(query);
|
|
64
|
+
if (options.select) mongoQuery = mongoQuery.select(options.select);
|
|
65
|
+
if (options.populate) mongoQuery = mongoQuery.populate(parsePopulate(options.populate));
|
|
66
|
+
if (options.sort) mongoQuery = mongoQuery.sort(options.sort);
|
|
67
|
+
if (options.limit) mongoQuery = mongoQuery.limit(options.limit);
|
|
68
|
+
if (options.skip) mongoQuery = mongoQuery.skip(options.skip);
|
|
69
|
+
mongoQuery = mongoQuery.lean(options.lean !== false);
|
|
70
|
+
if (options.session) mongoQuery = mongoQuery.session(options.session);
|
|
71
|
+
return mongoQuery.exec();
|
|
72
|
+
}
|
|
73
|
+
async function getOrCreate(Model, query, createData, options = {}) {
|
|
74
|
+
return Model.findOneAndUpdate(
|
|
75
|
+
query,
|
|
76
|
+
{ $setOnInsert: createData },
|
|
77
|
+
{
|
|
78
|
+
upsert: true,
|
|
79
|
+
new: true,
|
|
80
|
+
runValidators: true,
|
|
81
|
+
session: options.session,
|
|
82
|
+
...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
async function count(Model, query = {}, options = {}) {
|
|
87
|
+
return Model.countDocuments(query).session(options.session ?? null);
|
|
88
|
+
}
|
|
89
|
+
async function exists(Model, query, options = {}) {
|
|
90
|
+
return Model.exists(query).session(options.session ?? null);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/actions/update.ts
|
|
94
|
+
var update_exports = {};
|
|
95
|
+
__export(update_exports, {
|
|
96
|
+
increment: () => increment,
|
|
97
|
+
pullFromArray: () => pullFromArray,
|
|
98
|
+
pushToArray: () => pushToArray,
|
|
99
|
+
update: () => update,
|
|
100
|
+
updateByQuery: () => updateByQuery,
|
|
101
|
+
updateMany: () => updateMany,
|
|
102
|
+
updateWithConstraints: () => updateWithConstraints,
|
|
103
|
+
updateWithValidation: () => updateWithValidation
|
|
104
|
+
});
|
|
105
|
+
function assertUpdatePipelineAllowed(update2, updatePipeline) {
|
|
106
|
+
if (Array.isArray(update2) && updatePipeline !== true) {
|
|
107
|
+
throw createError(
|
|
108
|
+
400,
|
|
109
|
+
"Update pipelines (array updates) are disabled by default; pass `{ updatePipeline: true }` to explicitly allow pipeline-style updates."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function parsePopulate2(populate) {
|
|
114
|
+
if (!populate) return [];
|
|
115
|
+
if (typeof populate === "string") {
|
|
116
|
+
return populate.split(",").map((p) => p.trim());
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(populate)) {
|
|
119
|
+
return populate.map((p) => typeof p === "string" ? p.trim() : p);
|
|
120
|
+
}
|
|
121
|
+
return [populate];
|
|
122
|
+
}
|
|
123
|
+
async function update(Model, id, data, options = {}) {
|
|
124
|
+
assertUpdatePipelineAllowed(data, options.updatePipeline);
|
|
125
|
+
const document = await Model.findByIdAndUpdate(id, data, {
|
|
126
|
+
new: true,
|
|
127
|
+
runValidators: true,
|
|
128
|
+
session: options.session,
|
|
129
|
+
...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
|
|
130
|
+
}).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
|
|
131
|
+
if (!document) {
|
|
132
|
+
throw createError(404, "Document not found");
|
|
133
|
+
}
|
|
134
|
+
return document;
|
|
135
|
+
}
|
|
136
|
+
async function updateWithConstraints(Model, id, data, constraints = {}, options = {}) {
|
|
137
|
+
assertUpdatePipelineAllowed(data, options.updatePipeline);
|
|
138
|
+
const query = { _id: id, ...constraints };
|
|
139
|
+
const document = await Model.findOneAndUpdate(query, data, {
|
|
140
|
+
new: true,
|
|
141
|
+
runValidators: true,
|
|
142
|
+
session: options.session,
|
|
143
|
+
...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
|
|
144
|
+
}).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
|
|
145
|
+
return document;
|
|
146
|
+
}
|
|
147
|
+
async function updateWithValidation(Model, id, data, validationOptions = {}, options = {}) {
|
|
148
|
+
const { buildConstraints, validateUpdate } = validationOptions;
|
|
149
|
+
assertUpdatePipelineAllowed(data, options.updatePipeline);
|
|
150
|
+
if (buildConstraints) {
|
|
151
|
+
const constraints = buildConstraints(data);
|
|
152
|
+
const document = await updateWithConstraints(Model, id, data, constraints, options);
|
|
153
|
+
if (document) {
|
|
154
|
+
return { success: true, data: document };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const existing = await Model.findById(id).select(options.select || "").lean();
|
|
158
|
+
if (!existing) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: {
|
|
162
|
+
code: 404,
|
|
163
|
+
message: "Document not found"
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (validateUpdate) {
|
|
168
|
+
const validation = validateUpdate(existing, data);
|
|
169
|
+
if (!validation.valid) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: {
|
|
173
|
+
code: 403,
|
|
174
|
+
message: validation.message || "Update not allowed",
|
|
175
|
+
violations: validation.violations
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const updated = await update(Model, id, data, options);
|
|
181
|
+
return { success: true, data: updated };
|
|
182
|
+
}
|
|
183
|
+
async function updateMany(Model, query, data, options = {}) {
|
|
184
|
+
assertUpdatePipelineAllowed(data, options.updatePipeline);
|
|
185
|
+
const result = await Model.updateMany(query, data, {
|
|
186
|
+
runValidators: true,
|
|
187
|
+
session: options.session,
|
|
188
|
+
...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
matchedCount: result.matchedCount,
|
|
192
|
+
modifiedCount: result.modifiedCount
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function updateByQuery(Model, query, data, options = {}) {
|
|
196
|
+
assertUpdatePipelineAllowed(data, options.updatePipeline);
|
|
197
|
+
const document = await Model.findOneAndUpdate(query, data, {
|
|
198
|
+
new: true,
|
|
199
|
+
runValidators: true,
|
|
200
|
+
session: options.session,
|
|
201
|
+
...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
|
|
202
|
+
}).select(options.select || "").populate(parsePopulate2(options.populate)).lean(options.lean ?? false);
|
|
203
|
+
if (!document && options.throwOnNotFound !== false) {
|
|
204
|
+
throw createError(404, "Document not found");
|
|
205
|
+
}
|
|
206
|
+
return document;
|
|
207
|
+
}
|
|
208
|
+
async function increment(Model, id, field, value = 1, options = {}) {
|
|
209
|
+
return update(Model, id, { $inc: { [field]: value } }, options);
|
|
210
|
+
}
|
|
211
|
+
async function pushToArray(Model, id, field, value, options = {}) {
|
|
212
|
+
return update(Model, id, { $push: { [field]: value } }, options);
|
|
213
|
+
}
|
|
214
|
+
async function pullFromArray(Model, id, field, value, options = {}) {
|
|
215
|
+
return update(Model, id, { $pull: { [field]: value } }, options);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/actions/delete.ts
|
|
219
|
+
var delete_exports = {};
|
|
220
|
+
__export(delete_exports, {
|
|
221
|
+
deleteById: () => deleteById,
|
|
222
|
+
deleteByQuery: () => deleteByQuery,
|
|
223
|
+
deleteMany: () => deleteMany,
|
|
224
|
+
restore: () => restore,
|
|
225
|
+
softDelete: () => softDelete
|
|
226
|
+
});
|
|
227
|
+
async function deleteById(Model, id, options = {}) {
|
|
228
|
+
const document = await Model.findByIdAndDelete(id).session(options.session ?? null);
|
|
229
|
+
if (!document) {
|
|
230
|
+
throw createError(404, "Document not found");
|
|
231
|
+
}
|
|
232
|
+
return { success: true, message: "Deleted successfully" };
|
|
233
|
+
}
|
|
234
|
+
async function deleteMany(Model, query, options = {}) {
|
|
235
|
+
const result = await Model.deleteMany(query).session(options.session ?? null);
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
count: result.deletedCount,
|
|
239
|
+
message: "Deleted successfully"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function deleteByQuery(Model, query, options = {}) {
|
|
243
|
+
const document = await Model.findOneAndDelete(query).session(options.session ?? null);
|
|
244
|
+
if (!document && options.throwOnNotFound !== false) {
|
|
245
|
+
throw createError(404, "Document not found");
|
|
246
|
+
}
|
|
247
|
+
return { success: true, message: "Deleted successfully" };
|
|
248
|
+
}
|
|
249
|
+
async function softDelete(Model, id, options = {}) {
|
|
250
|
+
const document = await Model.findByIdAndUpdate(
|
|
251
|
+
id,
|
|
252
|
+
{
|
|
253
|
+
deleted: true,
|
|
254
|
+
deletedAt: /* @__PURE__ */ new Date(),
|
|
255
|
+
deletedBy: options.userId
|
|
256
|
+
},
|
|
257
|
+
{ new: true, session: options.session }
|
|
258
|
+
);
|
|
259
|
+
if (!document) {
|
|
260
|
+
throw createError(404, "Document not found");
|
|
261
|
+
}
|
|
262
|
+
return { success: true, message: "Soft deleted successfully" };
|
|
263
|
+
}
|
|
264
|
+
async function restore(Model, id, options = {}) {
|
|
265
|
+
const document = await Model.findByIdAndUpdate(
|
|
266
|
+
id,
|
|
267
|
+
{
|
|
268
|
+
deleted: false,
|
|
269
|
+
deletedAt: null,
|
|
270
|
+
deletedBy: null
|
|
271
|
+
},
|
|
272
|
+
{ new: true, session: options.session }
|
|
273
|
+
);
|
|
274
|
+
if (!document) {
|
|
275
|
+
throw createError(404, "Document not found");
|
|
276
|
+
}
|
|
277
|
+
return { success: true, message: "Restored successfully" };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/actions/aggregate.ts
|
|
281
|
+
var aggregate_exports = {};
|
|
282
|
+
__export(aggregate_exports, {
|
|
283
|
+
aggregate: () => aggregate,
|
|
284
|
+
aggregatePaginate: () => aggregatePaginate,
|
|
285
|
+
average: () => average,
|
|
286
|
+
countBy: () => countBy,
|
|
287
|
+
distinct: () => distinct,
|
|
288
|
+
facet: () => facet,
|
|
289
|
+
groupBy: () => groupBy,
|
|
290
|
+
lookup: () => lookup,
|
|
291
|
+
minMax: () => minMax,
|
|
292
|
+
sum: () => sum,
|
|
293
|
+
unwind: () => unwind
|
|
294
|
+
});
|
|
295
|
+
async function aggregate(Model, pipeline, options = {}) {
|
|
296
|
+
const aggregation = Model.aggregate(pipeline);
|
|
297
|
+
if (options.session) {
|
|
298
|
+
aggregation.session(options.session);
|
|
299
|
+
}
|
|
300
|
+
return aggregation.exec();
|
|
301
|
+
}
|
|
302
|
+
async function aggregatePaginate(Model, pipeline, options = {}) {
|
|
303
|
+
const page = parseInt(String(options.page || 1), 10);
|
|
304
|
+
const limit = parseInt(String(options.limit || 10), 10);
|
|
305
|
+
const skip = (page - 1) * limit;
|
|
306
|
+
const SAFE_LIMIT = 1e3;
|
|
307
|
+
if (limit > SAFE_LIMIT) {
|
|
308
|
+
console.warn(
|
|
309
|
+
`[mongokit] Large aggregation limit (${limit}). $facet results must be <16MB. Consider using Repository.aggregatePaginate() for safer handling of large datasets.`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const facetPipeline = [
|
|
313
|
+
...pipeline,
|
|
314
|
+
{
|
|
315
|
+
$facet: {
|
|
316
|
+
docs: [{ $skip: skip }, { $limit: limit }],
|
|
317
|
+
total: [{ $count: "count" }]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
];
|
|
321
|
+
const aggregation = Model.aggregate(facetPipeline);
|
|
322
|
+
if (options.session) {
|
|
323
|
+
aggregation.session(options.session);
|
|
324
|
+
}
|
|
325
|
+
const [result] = await aggregation.exec();
|
|
326
|
+
const docs = result.docs || [];
|
|
327
|
+
const total = result.total[0]?.count || 0;
|
|
328
|
+
const pages = Math.ceil(total / limit);
|
|
329
|
+
return {
|
|
330
|
+
docs,
|
|
331
|
+
total,
|
|
332
|
+
page,
|
|
333
|
+
limit,
|
|
334
|
+
pages,
|
|
335
|
+
hasNext: page < pages,
|
|
336
|
+
hasPrev: page > 1
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async function groupBy(Model, field, options = {}) {
|
|
340
|
+
const pipeline = [
|
|
341
|
+
{ $group: { _id: `$${field}`, count: { $sum: 1 } } },
|
|
342
|
+
{ $sort: { count: -1 } }
|
|
343
|
+
];
|
|
344
|
+
if (options.limit) {
|
|
345
|
+
pipeline.push({ $limit: options.limit });
|
|
346
|
+
}
|
|
347
|
+
return aggregate(Model, pipeline, options);
|
|
348
|
+
}
|
|
349
|
+
async function countBy(Model, field, query = {}, options = {}) {
|
|
350
|
+
const pipeline = [];
|
|
351
|
+
if (Object.keys(query).length > 0) {
|
|
352
|
+
pipeline.push({ $match: query });
|
|
353
|
+
}
|
|
354
|
+
pipeline.push(
|
|
355
|
+
{ $group: { _id: `$${field}`, count: { $sum: 1 } } },
|
|
356
|
+
{ $sort: { count: -1 } }
|
|
357
|
+
);
|
|
358
|
+
return aggregate(Model, pipeline, options);
|
|
359
|
+
}
|
|
360
|
+
async function lookup(Model, lookupOptions) {
|
|
361
|
+
const { from, localField, foreignField, as, pipeline = [], let: letVars, query = {}, options = {} } = lookupOptions;
|
|
362
|
+
const aggPipeline = [];
|
|
363
|
+
if (Object.keys(query).length > 0) {
|
|
364
|
+
aggPipeline.push({ $match: query });
|
|
365
|
+
}
|
|
366
|
+
const usePipelineForm = pipeline.length > 0 || letVars;
|
|
367
|
+
if (usePipelineForm) {
|
|
368
|
+
if (pipeline.length === 0 && localField && foreignField) {
|
|
369
|
+
const autoPipeline = [
|
|
370
|
+
{
|
|
371
|
+
$match: {
|
|
372
|
+
$expr: {
|
|
373
|
+
$eq: [`$${foreignField}`, `$$${localField}`]
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
aggPipeline.push({
|
|
379
|
+
$lookup: {
|
|
380
|
+
from,
|
|
381
|
+
let: { [localField]: `$${localField}`, ...letVars || {} },
|
|
382
|
+
pipeline: autoPipeline,
|
|
383
|
+
as
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
aggPipeline.push({
|
|
388
|
+
$lookup: {
|
|
389
|
+
from,
|
|
390
|
+
...letVars && { let: letVars },
|
|
391
|
+
pipeline,
|
|
392
|
+
as
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
aggPipeline.push({
|
|
398
|
+
$lookup: {
|
|
399
|
+
from,
|
|
400
|
+
localField,
|
|
401
|
+
foreignField,
|
|
402
|
+
as
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return aggregate(Model, aggPipeline, options);
|
|
407
|
+
}
|
|
408
|
+
async function unwind(Model, field, options = {}) {
|
|
409
|
+
const pipeline = [
|
|
410
|
+
{
|
|
411
|
+
$unwind: {
|
|
412
|
+
path: `$${field}`,
|
|
413
|
+
preserveNullAndEmptyArrays: options.preserveEmpty !== false
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
];
|
|
417
|
+
return aggregate(Model, pipeline, { session: options.session });
|
|
418
|
+
}
|
|
419
|
+
async function facet(Model, facets, options = {}) {
|
|
420
|
+
const pipeline = [{ $facet: facets }];
|
|
421
|
+
return aggregate(Model, pipeline, options);
|
|
422
|
+
}
|
|
423
|
+
async function distinct(Model, field, query = {}, options = {}) {
|
|
424
|
+
return Model.distinct(field, query).session(options.session ?? null);
|
|
425
|
+
}
|
|
426
|
+
async function sum(Model, field, query = {}, options = {}) {
|
|
427
|
+
const pipeline = [];
|
|
428
|
+
if (Object.keys(query).length > 0) {
|
|
429
|
+
pipeline.push({ $match: query });
|
|
430
|
+
}
|
|
431
|
+
pipeline.push({
|
|
432
|
+
$group: {
|
|
433
|
+
_id: null,
|
|
434
|
+
total: { $sum: `$${field}` }
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const result = await aggregate(Model, pipeline, options);
|
|
438
|
+
return result[0]?.total || 0;
|
|
439
|
+
}
|
|
440
|
+
async function average(Model, field, query = {}, options = {}) {
|
|
441
|
+
const pipeline = [];
|
|
442
|
+
if (Object.keys(query).length > 0) {
|
|
443
|
+
pipeline.push({ $match: query });
|
|
444
|
+
}
|
|
445
|
+
pipeline.push({
|
|
446
|
+
$group: {
|
|
447
|
+
_id: null,
|
|
448
|
+
average: { $avg: `$${field}` }
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const result = await aggregate(Model, pipeline, options);
|
|
452
|
+
return result[0]?.average || 0;
|
|
453
|
+
}
|
|
454
|
+
async function minMax(Model, field, query = {}, options = {}) {
|
|
455
|
+
const pipeline = [];
|
|
456
|
+
if (Object.keys(query).length > 0) {
|
|
457
|
+
pipeline.push({ $match: query });
|
|
458
|
+
}
|
|
459
|
+
pipeline.push({
|
|
460
|
+
$group: {
|
|
461
|
+
_id: null,
|
|
462
|
+
min: { $min: `$${field}` },
|
|
463
|
+
max: { $max: `$${field}` }
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
const result = await aggregate(Model, pipeline, options);
|
|
467
|
+
return result[0] || { min: null, max: null };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export { actions_exports, aggregate, aggregate_exports, count, deleteById, delete_exports, distinct, exists, getById, getByQuery, getOrCreate, read_exports, update, update_exports };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/utils/error.ts
|
|
8
|
+
function createError(status, message) {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.status = status;
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { __export, createError };
|
|
@@ -1,5 +1,166 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as AnyDocument,
|
|
1
|
+
import { PipelineStage, ClientSession, Model } from 'mongoose';
|
|
2
|
+
import { A as AnyDocument, y as CreateOptions, k as ObjectId, x as OperationOptions, S as SelectSpec, e as PopulateSpec, f as SortSpec, l as UpdateOptions, E as UpdateWithValidationResult, B as UpdateManyResult, z as DeleteResult, a6 as GroupResult, a7 as MinMaxResult } from './types-DA0rs2Jh.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LookupBuilder - MongoDB $lookup Utility
|
|
6
|
+
*
|
|
7
|
+
* Standalone builder for efficient custom field joins using MongoDB $lookup aggregation.
|
|
8
|
+
* Optimized for millions of records with proper index usage.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Join on custom fields (slugs, SKUs, codes, etc.)
|
|
12
|
+
* - Pipeline support for complex transformations
|
|
13
|
+
* - Index-aware query building
|
|
14
|
+
* - Single vs Array result handling
|
|
15
|
+
* - Nested lookups
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Simple lookup - join employees with departments by slug
|
|
20
|
+
* const lookup = new LookupBuilder('departments')
|
|
21
|
+
* .localField('departmentSlug')
|
|
22
|
+
* .foreignField('slug')
|
|
23
|
+
* .as('department')
|
|
24
|
+
* .single(); // Unwrap array to single object
|
|
25
|
+
*
|
|
26
|
+
* const pipeline = lookup.build();
|
|
27
|
+
* const results = await Employee.aggregate(pipeline);
|
|
28
|
+
*
|
|
29
|
+
* // Advanced lookup with pipeline
|
|
30
|
+
* const lookup = new LookupBuilder('products')
|
|
31
|
+
* .localField('productIds')
|
|
32
|
+
* .foreignField('sku')
|
|
33
|
+
* .pipeline([
|
|
34
|
+
* { $match: { status: 'active' } },
|
|
35
|
+
* { $project: { name: 1, price: 1 } }
|
|
36
|
+
* ])
|
|
37
|
+
* .as('products');
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
interface LookupOptions {
|
|
42
|
+
/** Collection to join with */
|
|
43
|
+
from: string;
|
|
44
|
+
/** Field from the input documents */
|
|
45
|
+
localField: string;
|
|
46
|
+
/** Field from the documents of the "from" collection */
|
|
47
|
+
foreignField: string;
|
|
48
|
+
/** Name of the new array field to add to the input documents */
|
|
49
|
+
as?: string;
|
|
50
|
+
/** Whether to unwrap array to single object */
|
|
51
|
+
single?: boolean;
|
|
52
|
+
/** Additional pipeline to run on the joined collection */
|
|
53
|
+
pipeline?: PipelineStage[];
|
|
54
|
+
/** Optional let variables for pipeline */
|
|
55
|
+
let?: Record<string, string>;
|
|
56
|
+
/** Query filter to apply before join (legacy, for aggregate.ts compatibility) */
|
|
57
|
+
query?: Record<string, unknown>;
|
|
58
|
+
/** Query options (legacy, for aggregate.ts compatibility) */
|
|
59
|
+
options?: {
|
|
60
|
+
session?: ClientSession;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fluent builder for MongoDB $lookup aggregation stage
|
|
65
|
+
* Optimized for custom field joins at scale
|
|
66
|
+
*/
|
|
67
|
+
declare class LookupBuilder {
|
|
68
|
+
private options;
|
|
69
|
+
constructor(from?: string);
|
|
70
|
+
/**
|
|
71
|
+
* Set the collection to join with
|
|
72
|
+
*/
|
|
73
|
+
from(collection: string): this;
|
|
74
|
+
/**
|
|
75
|
+
* Set the local field (source collection)
|
|
76
|
+
* IMPORTANT: This field should be indexed for optimal performance
|
|
77
|
+
*/
|
|
78
|
+
localField(field: string): this;
|
|
79
|
+
/**
|
|
80
|
+
* Set the foreign field (target collection)
|
|
81
|
+
* IMPORTANT: This field should be indexed (preferably unique) for optimal performance
|
|
82
|
+
*/
|
|
83
|
+
foreignField(field: string): this;
|
|
84
|
+
/**
|
|
85
|
+
* Set the output field name
|
|
86
|
+
* Defaults to the collection name if not specified
|
|
87
|
+
*/
|
|
88
|
+
as(fieldName: string): this;
|
|
89
|
+
/**
|
|
90
|
+
* Mark this lookup as returning a single document
|
|
91
|
+
* Automatically unwraps the array result to a single object or null
|
|
92
|
+
*/
|
|
93
|
+
single(isSingle?: boolean): this;
|
|
94
|
+
/**
|
|
95
|
+
* Add a pipeline to filter/transform joined documents
|
|
96
|
+
* Useful for filtering, sorting, or limiting joined results
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* lookup.pipeline([
|
|
101
|
+
* { $match: { status: 'active' } },
|
|
102
|
+
* { $sort: { priority: -1 } },
|
|
103
|
+
* { $limit: 5 }
|
|
104
|
+
* ]);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
pipeline(stages: PipelineStage[]): this;
|
|
108
|
+
/**
|
|
109
|
+
* Set let variables for use in pipeline
|
|
110
|
+
* Allows referencing local document fields in the pipeline
|
|
111
|
+
*/
|
|
112
|
+
let(variables: Record<string, string>): this;
|
|
113
|
+
/**
|
|
114
|
+
* Build the $lookup aggregation stage(s)
|
|
115
|
+
* Returns an array of pipeline stages including $lookup and optional $unwind
|
|
116
|
+
*
|
|
117
|
+
* IMPORTANT: MongoDB $lookup has two mutually exclusive forms:
|
|
118
|
+
* 1. Simple form: { from, localField, foreignField, as }
|
|
119
|
+
* 2. Pipeline form: { from, let, pipeline, as }
|
|
120
|
+
*
|
|
121
|
+
* When pipeline or let is specified, we use the pipeline form.
|
|
122
|
+
* Otherwise, we use the simpler localField/foreignField form.
|
|
123
|
+
*/
|
|
124
|
+
build(): PipelineStage[];
|
|
125
|
+
/**
|
|
126
|
+
* Build and return only the $lookup stage (without $unwind)
|
|
127
|
+
* Useful when you want to handle unwrapping yourself
|
|
128
|
+
*/
|
|
129
|
+
buildLookupOnly(): PipelineStage.Lookup;
|
|
130
|
+
/**
|
|
131
|
+
* Static helper: Create a simple lookup in one line
|
|
132
|
+
*/
|
|
133
|
+
static simple(from: string, localField: string, foreignField: string, options?: {
|
|
134
|
+
as?: string;
|
|
135
|
+
single?: boolean;
|
|
136
|
+
}): PipelineStage[];
|
|
137
|
+
/**
|
|
138
|
+
* Static helper: Create multiple lookups at once
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const pipeline = LookupBuilder.multiple([
|
|
143
|
+
* { from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true },
|
|
144
|
+
* { from: 'managers', localField: 'managerId', foreignField: '_id', single: true }
|
|
145
|
+
* ]);
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
static multiple(lookups: LookupOptions[]): PipelineStage[];
|
|
149
|
+
/**
|
|
150
|
+
* Static helper: Create a nested lookup (lookup within lookup)
|
|
151
|
+
* Useful for multi-level joins like Order -> Product -> Category
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* // Join orders with products, then products with categories
|
|
156
|
+
* const pipeline = LookupBuilder.nested([
|
|
157
|
+
* { from: 'products', localField: 'productSku', foreignField: 'sku', as: 'product', single: true },
|
|
158
|
+
* { from: 'categories', localField: 'product.categorySlug', foreignField: 'slug', as: 'product.category', single: true }
|
|
159
|
+
* ]);
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
static nested(lookups: LookupOptions[]): PipelineStage[];
|
|
163
|
+
}
|
|
3
164
|
|
|
4
165
|
/**
|
|
5
166
|
* Create Actions
|
|
@@ -269,6 +430,12 @@ declare function countBy(Model: Model<any>, field: string, query?: Record<string
|
|
|
269
430
|
}): Promise<GroupResult[]>;
|
|
270
431
|
/**
|
|
271
432
|
* Lookup (join) with another collection
|
|
433
|
+
*
|
|
434
|
+
* MongoDB $lookup has two mutually exclusive forms:
|
|
435
|
+
* 1. Simple form: { from, localField, foreignField, as }
|
|
436
|
+
* 2. Pipeline form: { from, let, pipeline, as }
|
|
437
|
+
*
|
|
438
|
+
* This function automatically selects the appropriate form based on parameters.
|
|
272
439
|
*/
|
|
273
440
|
declare function lookup<TDoc = AnyDocument>(Model: Model<TDoc>, lookupOptions: LookupOptions): Promise<TDoc[]>;
|
|
274
441
|
/**
|
|
@@ -334,4 +501,4 @@ declare namespace index {
|
|
|
334
501
|
export { aggregate$1 as aggregate, create$1 as create, _delete as deleteActions, index_read as read, update$1 as update };
|
|
335
502
|
}
|
|
336
503
|
|
|
337
|
-
export { _delete as _, aggregate$1 as
|
|
504
|
+
export { type LookupOptions as L, _delete as _, LookupBuilder as a, aggregate$1 as b, create$1 as c, index as i, read as r, update$1 as u };
|