bonsaif 1.10.40 → 1.10.41

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,1102 @@
1
+ /**
2
+ * ::: B[]NSAIF() ::: => 2024
3
+ * Hookup para Mongoose (MongoDB ORM)
4
+ * Compatible con múltiples versiones de Node y Mongoose
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const utl = require('../utl');
10
+ let debug = false;
11
+ let tag = ` ::: B[]NSAIF() ::: => mongoose.js `;
12
+
13
+ // Validar disponibilidad de Mongoose
14
+ let mongoose = null;
15
+ let isMongooseAvailable = false;
16
+ let mongooseVersion = null;
17
+
18
+ try {
19
+ mongoose = require('mongoose');
20
+ isMongooseAvailable = true;
21
+ mongooseVersion = mongoose.version;
22
+
23
+ // Validar versión de Node
24
+ const nodeVersion = process.version;
25
+ const nodeMajor = parseInt(nodeVersion.split('.')[0].substring(1));
26
+
27
+ if (nodeMajor < 12) {
28
+ utl.log(`${tag} WARNING: Node ${nodeVersion} no es compatible con Mongoose. Se requiere Node >= 12.0.0`);
29
+ isMongooseAvailable = false;
30
+ }
31
+
32
+ } catch (e) {
33
+ utl.log(`${tag} Mongoose no está instalado. Para usar mongoose, instala: npm install mongoose@"^6.12.0" (Node 12-14) o mongoose@"^8.0.0" (Node >= 16.20.1)`);
34
+ isMongooseAvailable = false;
35
+ }
36
+
37
+ // Cache de modelos dinámicos por base de datos y colección
38
+ const modelCache = new Map();
39
+
40
+ /**
41
+ * Validar que Mongoose esté disponible
42
+ */
43
+ const checkMongooseAvailable = () => {
44
+ if (!isMongooseAvailable) {
45
+ return {
46
+ result: {
47
+ dml: 'error',
48
+ time: 0,
49
+ code: 503,
50
+ error: 1,
51
+ msg: 'Mongoose no está disponible en este sistema',
52
+ details: {
53
+ nodeVersion: process.version,
54
+ mongooseInstalled: mongoose !== null,
55
+ recommendation: process.version.startsWith('v12') || process.version.startsWith('v14')
56
+ ? 'Instala: npm install mongoose@^6.12.0'
57
+ : 'Instala: npm install mongoose@^8.0.0 (requiere Node >= 16.20.1)'
58
+ }
59
+ },
60
+ data: []
61
+ };
62
+ }
63
+ return null;
64
+ };
65
+
66
+ /**
67
+ * Parsear schema dinámico recibido del cliente
68
+ * Convierte definición simple a Schema de Mongoose
69
+ */
70
+ const parseSchema = (schemaDefinition) => {
71
+ const parsedSchema = {};
72
+
73
+ for (const [field, def] of Object.entries(schemaDefinition)) {
74
+ if (typeof def === 'string') {
75
+ // Formato simple: { "name": "String" }
76
+ parsedSchema[field] = { type: mongoose.Schema.Types[def] || String };
77
+ } else if (typeof def === 'object') {
78
+ // Formato completo: { "name": { "type": "String", "required": true } }
79
+ const fieldDef = { ...def };
80
+
81
+ // Convertir string del type a tipo real de Mongoose
82
+ if (def.type && typeof def.type === 'string') {
83
+ fieldDef.type = mongoose.Schema.Types[def.type] || String;
84
+ }
85
+
86
+ // Convertir enum si existe
87
+ if (def.enum && Array.isArray(def.enum)) {
88
+ fieldDef.enum = def.enum;
89
+ }
90
+
91
+ // Convertir default
92
+ if (def.default !== undefined) {
93
+ fieldDef.default = def.default;
94
+ }
95
+
96
+ // Validaciones
97
+ if (def.required !== undefined) fieldDef.required = def.required;
98
+ if (def.unique !== undefined) fieldDef.unique = def.unique;
99
+ if (def.min !== undefined) fieldDef.min = def.min;
100
+ if (def.max !== undefined) fieldDef.max = def.max;
101
+ if (def.minlength !== undefined) fieldDef.minlength = def.minlength;
102
+ if (def.maxlength !== undefined) fieldDef.maxlength = def.maxlength;
103
+ if (def.trim !== undefined) fieldDef.trim = def.trim;
104
+ if (def.lowercase !== undefined) fieldDef.lowercase = def.lowercase;
105
+ if (def.uppercase !== undefined) fieldDef.uppercase = def.uppercase;
106
+ if (def.match !== undefined) fieldDef.match = new RegExp(def.match);
107
+
108
+ // Referencias (populate)
109
+ if (def.ref) {
110
+ fieldDef.ref = def.ref;
111
+ fieldDef.type = mongoose.Schema.Types.ObjectId;
112
+ }
113
+
114
+ parsedSchema[field] = fieldDef;
115
+ }
116
+ }
117
+
118
+ return parsedSchema;
119
+ };
120
+
121
+ /**
122
+ * Conectar a MongoDB usando Mongoose
123
+ */
124
+ const connect = async (options) => {
125
+ const availabilityError = checkMongooseAvailable();
126
+ if (availabilityError) throw new Error(availabilityError.result.msg);
127
+
128
+ const { uri = '', poolSize = 10, debug: debugMode = false } = options.endpoint;
129
+
130
+ try {
131
+ if (mongoose.connection.readyState === 1) {
132
+ debugMode ? utl.log(`${tag} [Mongoose] ya conectado`) : '';
133
+ return mongoose.connection;
134
+ }
135
+
136
+ // Opciones compatibles con múltiples versiones de Mongoose
137
+ const connectOptions = {
138
+ serverSelectionTimeoutMS: 15000,
139
+ socketTimeoutMS: 45000,
140
+ connectTimeoutMS: 10000
141
+ };
142
+
143
+ // Mongoose 6.x y 7.x usan useNewUrlParser, useUnifiedTopology
144
+ // Mongoose 8.x los depreca
145
+ const majorVersion = parseInt(mongooseVersion.split('.')[0]);
146
+
147
+ if (majorVersion >= 6) {
148
+ // Mongoose 6.x, 7.x, 8.x
149
+ connectOptions.maxPoolSize = poolSize;
150
+ connectOptions.minPoolSize = 2;
151
+ } else {
152
+ // Mongoose 5.x
153
+ connectOptions.poolSize = poolSize;
154
+ connectOptions.useNewUrlParser = true;
155
+ connectOptions.useUnifiedTopology = true;
156
+ }
157
+
158
+ await mongoose.connect(uri, connectOptions);
159
+
160
+ debugMode ? utl.log(`${tag} [Mongoose ${mongooseVersion}] conectado a ${uri}`) : '';
161
+ return mongoose.connection;
162
+ } catch (e) {
163
+ utl.log(`${tag} [Mongoose] error de conexión:`, e);
164
+ throw e;
165
+ }
166
+ };
167
+
168
+ /**
169
+ * Obtener o crear modelo dinámico para una colección
170
+ */
171
+ const getModel = (db, collection, schemaDefinition = null) => {
172
+ const cacheKey = schemaDefinition
173
+ ? `${db}.${collection}.custom.${JSON.stringify(schemaDefinition)}`
174
+ : `${db}.${collection}.generic`;
175
+
176
+ if (modelCache.has(cacheKey)) {
177
+ return modelCache.get(cacheKey);
178
+ }
179
+
180
+ let schema;
181
+
182
+ if (schemaDefinition) {
183
+ // Schema personalizado con validaciones
184
+ const parsedSchema = parseSchema(schemaDefinition);
185
+ schema = new mongoose.Schema(parsedSchema, {
186
+ strict: true,
187
+ timestamps: true,
188
+ collection: collection
189
+ });
190
+ } else {
191
+ // Schema genérico (acepta cualquier campo)
192
+ schema = new mongoose.Schema({}, {
193
+ strict: false,
194
+ timestamps: true,
195
+ collection: collection
196
+ });
197
+ }
198
+
199
+ // Crear modelo en la conexión específica de la BD
200
+ const conn = mongoose.connection.useDb(db);
201
+
202
+ // Nombre único para evitar conflictos
203
+ const modelName = schemaDefinition
204
+ ? `${collection}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
205
+ : collection;
206
+
207
+ const Model = conn.model(modelName, schema);
208
+
209
+ modelCache.set(cacheKey, Model);
210
+ return Model;
211
+ };
212
+
213
+ /**
214
+ * FIND - Búsqueda dinámica
215
+ */
216
+ const find = async (options, db, collection, query) => {
217
+ const availabilityError = checkMongooseAvailable();
218
+ if (availabilityError) return availabilityError;
219
+
220
+ const start = Date.now();
221
+
222
+ try {
223
+ await connect(options);
224
+
225
+ const { filter = {}, select = '', sort = {}, limit = 0, skip = 0, populate = '', schema = null } = query;
226
+
227
+ const Model = getModel(db, collection, schema);
228
+ let mongoQuery = Model.find(filter);
229
+
230
+ if (select) mongoQuery = mongoQuery.select(select);
231
+ if (Object.keys(sort).length > 0) mongoQuery = mongoQuery.sort(sort);
232
+ if (limit > 0) mongoQuery = mongoQuery.limit(limit);
233
+ if (skip > 0) mongoQuery = mongoQuery.skip(skip);
234
+ if (populate) mongoQuery = mongoQuery.populate(populate);
235
+
236
+ const data = await mongoQuery.lean().exec();
237
+
238
+ const end = Date.now();
239
+ const time = end - start;
240
+
241
+ return {
242
+ result: {
243
+ dml: 'find',
244
+ collection,
245
+ db,
246
+ time: utl.milisegundosASegundos(time),
247
+ code: 200,
248
+ error: 0
249
+ },
250
+ data
251
+ };
252
+ } catch (e) {
253
+ const end = Date.now();
254
+ const time = end - start;
255
+ utl.log(`${tag} find error:`, e);
256
+ return {
257
+ result: {
258
+ dml: 'find',
259
+ time: utl.milisegundosASegundos(time),
260
+ code: 500,
261
+ error: 1,
262
+ msg: e.message
263
+ },
264
+ data: []
265
+ };
266
+ }
267
+ };
268
+
269
+ /**
270
+ * FINDONE - Buscar un solo documento
271
+ */
272
+ const findOne = async (options, db, collection, query) => {
273
+ const availabilityError = checkMongooseAvailable();
274
+ if (availabilityError) return availabilityError;
275
+
276
+ const start = Date.now();
277
+
278
+ try {
279
+ await connect(options);
280
+
281
+ const { filter = {}, select = '', populate = '', schema = null } = query;
282
+
283
+ const Model = getModel(db, collection, schema);
284
+ let mongoQuery = Model.findOne(filter);
285
+
286
+ if (select) mongoQuery = mongoQuery.select(select);
287
+ if (populate) mongoQuery = mongoQuery.populate(populate);
288
+
289
+ const data = await mongoQuery.lean().exec();
290
+
291
+ const end = Date.now();
292
+ const time = end - start;
293
+
294
+ return {
295
+ result: {
296
+ dml: 'findOne',
297
+ collection,
298
+ db,
299
+ time: utl.milisegundosASegundos(time),
300
+ code: 200,
301
+ error: 0
302
+ },
303
+ data: data ? [data] : []
304
+ };
305
+ } catch (e) {
306
+ const end = Date.now();
307
+ const time = end - start;
308
+ utl.log(`${tag} findOne error:`, e);
309
+ return {
310
+ result: {
311
+ dml: 'findOne',
312
+ time: utl.milisegundosASegundos(time),
313
+ code: 500,
314
+ error: 1,
315
+ msg: e.message
316
+ },
317
+ data: []
318
+ };
319
+ }
320
+ };
321
+
322
+ /**
323
+ * COUNT - Contar documentos
324
+ */
325
+ const count = async (options, db, collection, query) => {
326
+ const availabilityError = checkMongooseAvailable();
327
+ if (availabilityError) return availabilityError;
328
+
329
+ const start = Date.now();
330
+
331
+ try {
332
+ await connect(options);
333
+
334
+ const { filter = {}, schema = null } = query;
335
+
336
+ const Model = getModel(db, collection, schema);
337
+ const total = await Model.countDocuments(filter);
338
+
339
+ const end = Date.now();
340
+ const time = end - start;
341
+
342
+ return {
343
+ result: {
344
+ dml: 'count',
345
+ collection,
346
+ db,
347
+ time: utl.milisegundosASegundos(time),
348
+ code: 200,
349
+ error: 0
350
+ },
351
+ data: { count: total }
352
+ };
353
+ } catch (e) {
354
+ const end = Date.now();
355
+ const time = end - start;
356
+ utl.log(`${tag} count error:`, e);
357
+ return {
358
+ result: {
359
+ dml: 'count',
360
+ time: utl.milisegundosASegundos(time),
361
+ code: 500,
362
+ error: 1,
363
+ msg: e.message
364
+ },
365
+ data: { count: 0 }
366
+ };
367
+ }
368
+ };
369
+
370
+ /**
371
+ * INSERT - Crear documento
372
+ */
373
+ const insert = async (options, db, collection, query) => {
374
+ const availabilityError = checkMongooseAvailable();
375
+ if (availabilityError) return availabilityError;
376
+
377
+ const start = Date.now();
378
+
379
+ try {
380
+ await connect(options);
381
+
382
+ const { document = {}, schema = null } = query;
383
+
384
+ const Model = getModel(db, collection, schema);
385
+ const doc = new Model(document);
386
+ const result = await doc.save();
387
+
388
+ const end = Date.now();
389
+ const time = end - start;
390
+
391
+ return {
392
+ result: {
393
+ dml: 'insert',
394
+ collection,
395
+ db,
396
+ insertId: result._id,
397
+ time: utl.milisegundosASegundos(time),
398
+ code: 200,
399
+ error: 0
400
+ },
401
+ data: [result.toObject()]
402
+ };
403
+ } catch (e) {
404
+ const end = Date.now();
405
+ const time = end - start;
406
+ utl.log(`${tag} insert error:`, e);
407
+ return {
408
+ result: {
409
+ dml: 'insert',
410
+ time: utl.milisegundosASegundos(time),
411
+ code: 500,
412
+ error: 1,
413
+ msg: e.message,
414
+ validationErrors: e.errors ? Object.keys(e.errors).map(key => ({
415
+ field: key,
416
+ message: e.errors[key].message
417
+ })) : undefined
418
+ },
419
+ data: []
420
+ };
421
+ }
422
+ };
423
+
424
+ /**
425
+ * INSERTMANY - Crear múltiples documentos
426
+ */
427
+ const insertMany = async (options, db, collection, query) => {
428
+ const availabilityError = checkMongooseAvailable();
429
+ if (availabilityError) return availabilityError;
430
+
431
+ const start = Date.now();
432
+
433
+ try {
434
+ await connect(options);
435
+
436
+ const { documents = [], schema = null } = query;
437
+
438
+ const Model = getModel(db, collection, schema);
439
+ const results = await Model.insertMany(documents);
440
+
441
+ const end = Date.now();
442
+ const time = end - start;
443
+
444
+ return {
445
+ result: {
446
+ dml: 'insertMany',
447
+ collection,
448
+ db,
449
+ insertedCount: results.length,
450
+ time: utl.milisegundosASegundos(time),
451
+ code: 200,
452
+ error: 0
453
+ },
454
+ data: results.map(doc => doc.toObject())
455
+ };
456
+ } catch (e) {
457
+ const end = Date.now();
458
+ const time = end - start;
459
+ utl.log(`${tag} insertMany error:`, e);
460
+ return {
461
+ result: {
462
+ dml: 'insertMany',
463
+ time: utl.milisegundosASegundos(time),
464
+ code: 500,
465
+ error: 1,
466
+ msg: e.message
467
+ },
468
+ data: []
469
+ };
470
+ }
471
+ };
472
+
473
+ /**
474
+ * UPDATE - Actualizar documento(s)
475
+ */
476
+ const update = async (options, db, collection, query) => {
477
+ const availabilityError = checkMongooseAvailable();
478
+ if (availabilityError) return availabilityError;
479
+
480
+ const start = Date.now();
481
+
482
+ try {
483
+ await connect(options);
484
+
485
+ const { filter = {}, update: updateDoc = {}, options: updateOpts = {}, schema = null } = query;
486
+
487
+ const Model = getModel(db, collection, schema);
488
+ const result = await Model.updateOne(filter, updateDoc, updateOpts);
489
+
490
+ const end = Date.now();
491
+ const time = end - start;
492
+
493
+ return {
494
+ result: {
495
+ dml: 'update',
496
+ collection,
497
+ db,
498
+ matchedCount: result.matchedCount,
499
+ modifiedCount: result.modifiedCount,
500
+ time: utl.milisegundosASegundos(time),
501
+ code: 200,
502
+ error: 0
503
+ },
504
+ data: {
505
+ matchedCount: result.matchedCount,
506
+ modifiedCount: result.modifiedCount
507
+ }
508
+ };
509
+ } catch (e) {
510
+ const end = Date.now();
511
+ const time = end - start;
512
+ utl.log(`${tag} update error:`, e);
513
+ return {
514
+ result: {
515
+ dml: 'update',
516
+ time: utl.milisegundosASegundos(time),
517
+ code: 500,
518
+ error: 1,
519
+ msg: e.message
520
+ },
521
+ data: {}
522
+ };
523
+ }
524
+ };
525
+
526
+ /**
527
+ * UPDATEMANY - Actualizar múltiples documentos
528
+ */
529
+ const updateMany = async (options, db, collection, query) => {
530
+ const availabilityError = checkMongooseAvailable();
531
+ if (availabilityError) return availabilityError;
532
+
533
+ const start = Date.now();
534
+
535
+ try {
536
+ await connect(options);
537
+
538
+ const { filter = {}, update: updateDoc = {}, options: updateOpts = {}, schema = null } = query;
539
+
540
+ const Model = getModel(db, collection, schema);
541
+ const result = await Model.updateMany(filter, updateDoc, updateOpts);
542
+
543
+ const end = Date.now();
544
+ const time = end - start;
545
+
546
+ return {
547
+ result: {
548
+ dml: 'updateMany',
549
+ collection,
550
+ db,
551
+ matchedCount: result.matchedCount,
552
+ modifiedCount: result.modifiedCount,
553
+ time: utl.milisegundosASegundos(time),
554
+ code: 200,
555
+ error: 0
556
+ },
557
+ data: {
558
+ matchedCount: result.matchedCount,
559
+ modifiedCount: result.modifiedCount
560
+ }
561
+ };
562
+ } catch (e) {
563
+ const end = Date.now();
564
+ const time = end - start;
565
+ utl.log(`${tag} updateMany error:`, e);
566
+ return {
567
+ result: {
568
+ dml: 'updateMany',
569
+ time: utl.milisegundosASegundos(time),
570
+ code: 500,
571
+ error: 1,
572
+ msg: e.message
573
+ },
574
+ data: {}
575
+ };
576
+ }
577
+ };
578
+
579
+ /**
580
+ * UPSERT - Actualizar o insertar
581
+ */
582
+ const upsert = async (options, db, collection, query) => {
583
+ const availabilityError = checkMongooseAvailable();
584
+ if (availabilityError) return availabilityError;
585
+
586
+ const start = Date.now();
587
+
588
+ try {
589
+ await connect(options);
590
+
591
+ const { filter = {}, document = {}, schema = null } = query;
592
+
593
+ const Model = getModel(db, collection, schema);
594
+
595
+ // Buscar documento existente
596
+ const existing = await Model.findOne(filter);
597
+
598
+ let result, operation;
599
+
600
+ if (existing) {
601
+ // Actualizar
602
+ Object.assign(existing, document);
603
+ result = await existing.save();
604
+ operation = 'update';
605
+ } else {
606
+ // Insertar
607
+ const doc = new Model({ ...filter, ...document });
608
+ result = await doc.save();
609
+ operation = 'insert';
610
+ }
611
+
612
+ const end = Date.now();
613
+ const time = end - start;
614
+
615
+ return {
616
+ result: {
617
+ dml: 'upsert',
618
+ operation,
619
+ collection,
620
+ db,
621
+ time: utl.milisegundosASegundos(time),
622
+ code: 200,
623
+ error: 0
624
+ },
625
+ data: [result.toObject()]
626
+ };
627
+ } catch (e) {
628
+ const end = Date.now();
629
+ const time = end - start;
630
+ utl.log(`${tag} upsert error:`, e);
631
+ return {
632
+ result: {
633
+ dml: 'upsert',
634
+ time: utl.milisegundosASegundos(time),
635
+ code: 500,
636
+ error: 1,
637
+ msg: e.message
638
+ },
639
+ data: []
640
+ };
641
+ }
642
+ };
643
+
644
+ /**
645
+ * DELETE - Eliminar documento(s)
646
+ */
647
+ const deleteOne = async (options, db, collection, query) => {
648
+ const availabilityError = checkMongooseAvailable();
649
+ if (availabilityError) return availabilityError;
650
+
651
+ const start = Date.now();
652
+
653
+ try {
654
+ await connect(options);
655
+
656
+ const { filter = {}, schema = null } = query;
657
+
658
+ const Model = getModel(db, collection, schema);
659
+ const result = await Model.deleteOne(filter);
660
+
661
+ const end = Date.now();
662
+ const time = end - start;
663
+
664
+ return {
665
+ result: {
666
+ dml: 'delete',
667
+ collection,
668
+ db,
669
+ deletedCount: result.deletedCount,
670
+ time: utl.milisegundosASegundos(time),
671
+ code: 200,
672
+ error: 0
673
+ },
674
+ data: { deletedCount: result.deletedCount }
675
+ };
676
+ } catch (e) {
677
+ const end = Date.now();
678
+ const time = end - start;
679
+ utl.log(`${tag} delete error:`, e);
680
+ return {
681
+ result: {
682
+ dml: 'delete',
683
+ time: utl.milisegundosASegundos(time),
684
+ code: 500,
685
+ error: 1,
686
+ msg: e.message
687
+ },
688
+ data: { deletedCount: 0 }
689
+ };
690
+ }
691
+ };
692
+
693
+ /**
694
+ * DELETEMANY - Eliminar múltiples documentos
695
+ */
696
+ const deleteMany = async (options, db, collection, query) => {
697
+ const availabilityError = checkMongooseAvailable();
698
+ if (availabilityError) return availabilityError;
699
+
700
+ const start = Date.now();
701
+
702
+ try {
703
+ await connect(options);
704
+
705
+ const { filter = {}, schema = null } = query;
706
+
707
+ const Model = getModel(db, collection, schema);
708
+ const result = await Model.deleteMany(filter);
709
+
710
+ const end = Date.now();
711
+ const time = end - start;
712
+
713
+ return {
714
+ result: {
715
+ dml: 'deleteMany',
716
+ collection,
717
+ db,
718
+ deletedCount: result.deletedCount,
719
+ time: utl.milisegundosASegundos(time),
720
+ code: 200,
721
+ error: 0
722
+ },
723
+ data: { deletedCount: result.deletedCount }
724
+ };
725
+ } catch (e) {
726
+ const end = Date.now();
727
+ const time = end - start;
728
+ utl.log(`${tag} deleteMany error:`, e);
729
+ return {
730
+ result: {
731
+ dml: 'deleteMany',
732
+ time: utl.milisegundosASegundos(time),
733
+ code: 500,
734
+ error: 1,
735
+ msg: e.message
736
+ },
737
+ data: { deletedCount: 0 }
738
+ };
739
+ }
740
+ };
741
+
742
+ /**
743
+ * AGGREGATE - Pipeline de agregación
744
+ */
745
+ const aggregate = async (options, db, collection, query) => {
746
+ const availabilityError = checkMongooseAvailable();
747
+ if (availabilityError) return availabilityError;
748
+
749
+ const start = Date.now();
750
+
751
+ try {
752
+ await connect(options);
753
+
754
+ const { pipeline = [], schema = null } = query;
755
+
756
+ const Model = getModel(db, collection, schema);
757
+ const data = await Model.aggregate(pipeline);
758
+
759
+ const end = Date.now();
760
+ const time = end - start;
761
+
762
+ return {
763
+ result: {
764
+ dml: 'aggregate',
765
+ collection,
766
+ db,
767
+ time: utl.milisegundosASegundos(time),
768
+ code: 200,
769
+ error: 0
770
+ },
771
+ data
772
+ };
773
+ } catch (e) {
774
+ const end = Date.now();
775
+ const time = end - start;
776
+ utl.log(`${tag} aggregate error:`, e);
777
+ return {
778
+ result: {
779
+ dml: 'aggregate',
780
+ time: utl.milisegundosASegundos(time),
781
+ code: 500,
782
+ error: 1,
783
+ msg: e.message
784
+ },
785
+ data: []
786
+ };
787
+ }
788
+ };
789
+
790
+ /**
791
+ * DISTINCT - Valores únicos
792
+ */
793
+ const distinct = async (options, db, collection, query) => {
794
+ const availabilityError = checkMongooseAvailable();
795
+ if (availabilityError) return availabilityError;
796
+
797
+ const start = Date.now();
798
+
799
+ try {
800
+ await connect(options);
801
+
802
+ const { field = '', filter = {}, schema = null } = query;
803
+
804
+ const Model = getModel(db, collection, schema);
805
+ const data = await Model.distinct(field, filter);
806
+
807
+ const end = Date.now();
808
+ const time = end - start;
809
+
810
+ return {
811
+ result: {
812
+ dml: 'distinct',
813
+ collection,
814
+ db,
815
+ time: utl.milisegundosASegundos(time),
816
+ code: 200,
817
+ error: 0
818
+ },
819
+ data
820
+ };
821
+ } catch (e) {
822
+ const end = Date.now();
823
+ const time = end - start;
824
+ utl.log(`${tag} distinct error:`, e);
825
+ return {
826
+ result: {
827
+ dml: 'distinct',
828
+ time: utl.milisegundosASegundos(time),
829
+ code: 500,
830
+ error: 1,
831
+ msg: e.message
832
+ },
833
+ data: []
834
+ };
835
+ }
836
+ };
837
+
838
+ /**
839
+ * DBS - Listar todas las bases de datos
840
+ */
841
+ const dbs = async (options) => {
842
+ const availabilityError = checkMongooseAvailable();
843
+ if (availabilityError) return availabilityError;
844
+
845
+ const start = Date.now();
846
+
847
+ try {
848
+ await connect(options);
849
+
850
+ const adminDb = mongoose.connection.db.admin();
851
+ const result = await adminDb.listDatabases();
852
+
853
+ const end = Date.now();
854
+ const time = end - start;
855
+
856
+ return {
857
+ result: {
858
+ dml: 'dbs',
859
+ headers: 'name|sizeOnDisk|empty',
860
+ time: utl.milisegundosASegundos(time),
861
+ code: 200,
862
+ error: 0
863
+ },
864
+ results: { totalSize: result.totalSize || 0 },
865
+ data: result.databases || []
866
+ };
867
+ } catch (e) {
868
+ const end = Date.now();
869
+ const time = end - start;
870
+ utl.log(`${tag} dbs error:`, e);
871
+ return {
872
+ result: {
873
+ dml: 'dbs',
874
+ time: utl.milisegundosASegundos(time),
875
+ code: 500,
876
+ error: 1,
877
+ msg: e.message
878
+ },
879
+ data: []
880
+ };
881
+ }
882
+ };
883
+
884
+ /**
885
+ * COLLECTIONS - Listar todas las colecciones de una base de datos
886
+ */
887
+ const collections = async (options, db) => {
888
+ const availabilityError = checkMongooseAvailable();
889
+ if (availabilityError) return availabilityError;
890
+
891
+ const start = Date.now();
892
+
893
+ try {
894
+ await connect(options);
895
+
896
+ const dbConn = mongoose.connection.useDb(db);
897
+ const colls = await dbConn.db.listCollections().toArray();
898
+
899
+ const end = Date.now();
900
+ const time = end - start;
901
+
902
+ return {
903
+ result: {
904
+ dml: 'collections',
905
+ headers: 'name|type',
906
+ db,
907
+ time: utl.milisegundosASegundos(time),
908
+ code: 200,
909
+ error: 0
910
+ },
911
+ results: colls,
912
+ data: colls
913
+ };
914
+ } catch (e) {
915
+ const end = Date.now();
916
+ const time = end - start;
917
+ utl.log(`${tag} collections error:`, e);
918
+ return {
919
+ result: {
920
+ dml: 'collections',
921
+ time: utl.milisegundosASegundos(time),
922
+ code: 500,
923
+ error: 1,
924
+ msg: e.message
925
+ },
926
+ data: []
927
+ };
928
+ }
929
+ };
930
+
931
+ /**
932
+ * DROP - Eliminar una colección completa
933
+ */
934
+ const drop = async (options, db, collection) => {
935
+ const availabilityError = checkMongooseAvailable();
936
+ if (availabilityError) return availabilityError;
937
+
938
+ const start = Date.now();
939
+
940
+ try {
941
+ await connect(options);
942
+
943
+ const dbConn = mongoose.connection.useDb(db);
944
+ const result = await dbConn.db.dropCollection(collection);
945
+
946
+ const end = Date.now();
947
+ const time = end - start;
948
+
949
+ return {
950
+ result: {
951
+ dml: 'drop',
952
+ collection,
953
+ db,
954
+ time: utl.milisegundosASegundos(time),
955
+ code: 200,
956
+ error: 0
957
+ },
958
+ results: result,
959
+ data: { drop: result }
960
+ };
961
+ } catch (e) {
962
+ const end = Date.now();
963
+ const time = end - start;
964
+ utl.log(`${tag} drop error:`, e);
965
+ return {
966
+ result: {
967
+ dml: 'drop',
968
+ time: utl.milisegundosASegundos(time),
969
+ code: 500,
970
+ error: 1,
971
+ msg: e.message
972
+ },
973
+ data: { drop: false }
974
+ };
975
+ }
976
+ };
977
+
978
+ /**
979
+ * API - Router principal para todas las operaciones
980
+ */
981
+ const api = async (options, db, body) => {
982
+ const availabilityError = checkMongooseAvailable();
983
+ if (availabilityError) return availabilityError;
984
+
985
+ let { dml = '', collection = '' } = body;
986
+
987
+ // Si dml es 'api', buscar el DML real en el body
988
+ if (dml === 'api' || !dml) {
989
+ const possibleDmls = ['find', 'findOne', 'insert', 'insertMany', 'update', 'updateMany',
990
+ 'delete', 'deleteMany', 'upsert', 'count', 'aggregate', 'distinct',
991
+ 'dbs', 'collections', 'drop'];
992
+ for (const possibleDml of possibleDmls) {
993
+ if (body[possibleDml] !== undefined) {
994
+ dml = possibleDml;
995
+ // En formato MongoDB, la colección viene en body.[dml]
996
+ if (typeof body[possibleDml] === 'string') {
997
+ collection = body[possibleDml];
998
+ }
999
+ break;
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ // Compatibilidad con formato MongoDB: collection puede venir en json.[dml]
1005
+ if (!collection && dml && dml !== 'api') {
1006
+ collection = body[dml] || '';
1007
+ }
1008
+
1009
+ // Si aún no hay collection pero hay document/documents, usar dml por defecto
1010
+ if (!collection && body.document) {
1011
+ dml = dml || 'insert';
1012
+ }
1013
+
1014
+ // Validar que collection tenga un valor (excepto para dbs y collections que no lo necesitan)
1015
+ const requiresCollection = !['dbs', 'collections'].includes(dml);
1016
+
1017
+ if (requiresCollection && (!collection || collection === '')) {
1018
+ const errorMsg = collection === ''
1019
+ ? 'La "collection" está vacía. Asegúrate de que la variable tenga un valor válido.'
1020
+ : 'Se requiere especificar "collection" en body.collection o body.[dml]';
1021
+
1022
+ return {
1023
+ result: {
1024
+ dml: dml || 'unknown',
1025
+ time: 0,
1026
+ code: 400,
1027
+ error: 1,
1028
+ msg: errorMsg,
1029
+ debug: {
1030
+ receivedDml: body.dml,
1031
+ detectedDml: dml,
1032
+ collectionValue: collection,
1033
+ collectionIsEmpty: collection === '',
1034
+ bodyKeys: Object.keys(body)
1035
+ }
1036
+ },
1037
+ data: []
1038
+ };
1039
+ }
1040
+
1041
+ let r;
1042
+
1043
+ switch (dml) {
1044
+ case 'find': r = await find(options, db, collection, body); break;
1045
+ case 'findOne': r = await findOne(options, db, collection, body); break;
1046
+ case 'count': r = await count(options, db, collection, body); break;
1047
+ case 'insert': r = await insert(options, db, collection, body); break;
1048
+ case 'insertMany': r = await insertMany(options, db, collection, body); break;
1049
+ case 'update': r = await update(options, db, collection, body); break;
1050
+ case 'updateMany': r = await updateMany(options, db, collection, body); break;
1051
+ case 'upsert': r = await upsert(options, db, collection, body); break;
1052
+ case 'delete': r = await deleteOne(options, db, collection, body); break;
1053
+ case 'deleteMany': r = await deleteMany(options, db, collection, body); break;
1054
+ case 'aggregate': r = await aggregate(options, db, collection, body); break;
1055
+ case 'distinct': r = await distinct(options, db, collection, body); break;
1056
+ case 'dbs': r = await dbs(options); break;
1057
+ case 'collections': r = await collections(options, db); break;
1058
+ case 'drop': r = await drop(options, db, collection); break;
1059
+ default:
1060
+ r = {
1061
+ result: {
1062
+ dml: dml || 'unknown',
1063
+ time: 0,
1064
+ code: 400,
1065
+ error: 1,
1066
+ msg: `Operación "${dml}" no soportada`
1067
+ },
1068
+ supportedOperations: [
1069
+ 'find', 'findOne', 'count', 'insert', 'insertMany',
1070
+ 'update', 'updateMany', 'upsert', 'delete', 'deleteMany',
1071
+ 'aggregate', 'distinct', 'dbs', 'collections', 'drop'
1072
+ ]
1073
+ };
1074
+ }
1075
+
1076
+ return new Promise((resolve) => {
1077
+ resolve(r);
1078
+ });
1079
+ };
1080
+
1081
+ module.exports = {
1082
+ api,
1083
+ find,
1084
+ findOne,
1085
+ count,
1086
+ insert,
1087
+ insertMany,
1088
+ update,
1089
+ updateMany,
1090
+ upsert,
1091
+ deleteOne,
1092
+ deleteMany,
1093
+ aggregate,
1094
+ distinct,
1095
+ dbs,
1096
+ collections,
1097
+ drop,
1098
+ connect,
1099
+ getModel,
1100
+ isMongooseAvailable,
1101
+ mongooseVersion
1102
+ };