@objectstack/driver-mongodb 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,787 @@
1
+ // src/mongodb-driver.ts
2
+ import {
3
+ MongoClient
4
+ } from "mongodb";
5
+ import { nanoid } from "nanoid";
6
+
7
+ // src/mongodb-filter.ts
8
+ function translateFilter(where) {
9
+ if (!where) return {};
10
+ if (Array.isArray(where)) {
11
+ return translateArrayFilter(where);
12
+ }
13
+ if (typeof where !== "object") return {};
14
+ return translateCondition(where);
15
+ }
16
+ function translateCondition(condition) {
17
+ const mongoFilter = {};
18
+ const andClauses = [];
19
+ for (const [key, value] of Object.entries(condition)) {
20
+ switch (key) {
21
+ case "$and":
22
+ if (Array.isArray(value)) {
23
+ andClauses.push({
24
+ $and: value.map((sub) => translateCondition(sub))
25
+ });
26
+ }
27
+ break;
28
+ case "$or":
29
+ if (Array.isArray(value)) {
30
+ andClauses.push({
31
+ $or: value.map((sub) => translateCondition(sub))
32
+ });
33
+ }
34
+ break;
35
+ case "$not":
36
+ if (value && typeof value === "object") {
37
+ const inner = translateCondition(value);
38
+ andClauses.push({ $nor: [inner] });
39
+ }
40
+ break;
41
+ default:
42
+ if (["limit", "offset", "fields", "orderBy"].includes(key)) continue;
43
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
44
+ const objValue = value;
45
+ const hasOps = Object.keys(objValue).some((k) => k.startsWith("$"));
46
+ if (hasOps) {
47
+ mongoFilter[key] = translateFieldOperators(objValue);
48
+ } else {
49
+ mongoFilter[key] = value;
50
+ }
51
+ } else {
52
+ mongoFilter[key] = value;
53
+ }
54
+ }
55
+ }
56
+ if (andClauses.length > 0) {
57
+ if (Object.keys(mongoFilter).length > 0) {
58
+ return { $and: [mongoFilter, ...andClauses] };
59
+ }
60
+ if (andClauses.length === 1) {
61
+ return andClauses[0];
62
+ }
63
+ return { $and: andClauses };
64
+ }
65
+ return mongoFilter;
66
+ }
67
+ function translateFieldOperators(ops) {
68
+ const result = {};
69
+ for (const [op, value] of Object.entries(ops)) {
70
+ switch (op) {
71
+ // Direct mappings (ObjectStack → MongoDB are identical)
72
+ case "$eq":
73
+ case "$ne":
74
+ case "$gt":
75
+ case "$gte":
76
+ case "$lt":
77
+ case "$lte":
78
+ case "$in":
79
+ case "$nin":
80
+ case "$exists":
81
+ result[op] = value;
82
+ break;
83
+ // String operators → $regex
84
+ case "$contains":
85
+ result.$regex = escapeRegex(String(value));
86
+ result.$options = "i";
87
+ break;
88
+ case "$notContains":
89
+ result.$not = { $regex: escapeRegex(String(value)), $options: "i" };
90
+ break;
91
+ case "$startsWith":
92
+ result.$regex = `^${escapeRegex(String(value))}`;
93
+ result.$options = "i";
94
+ break;
95
+ case "$endsWith":
96
+ result.$regex = `${escapeRegex(String(value))}$`;
97
+ result.$options = "i";
98
+ break;
99
+ // Range operator → $gte + $lte
100
+ case "$between":
101
+ if (Array.isArray(value) && value.length === 2) {
102
+ result.$gte = value[0];
103
+ result.$lte = value[1];
104
+ }
105
+ break;
106
+ // Null check
107
+ case "$null":
108
+ if (value === true) {
109
+ result.$eq = null;
110
+ } else {
111
+ result.$ne = null;
112
+ }
113
+ break;
114
+ default:
115
+ result[op] = value;
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+ function translateArrayFilter(filters) {
121
+ if (filters.length === 0) return {};
122
+ if (filters.length === 3 && typeof filters[0] === "string" && typeof filters[1] === "string" && !Array.isArray(filters[0]) && (typeof filters[2] !== "object" || filters[2] === null || Array.isArray(filters[2]))) {
123
+ const possibleOp = filters[1];
124
+ const isOperator = [
125
+ "=",
126
+ "!=",
127
+ "<>",
128
+ ">",
129
+ ">=",
130
+ "<",
131
+ "<=",
132
+ "in",
133
+ "nin",
134
+ "eq",
135
+ "ne",
136
+ "gt",
137
+ "gte",
138
+ "lt",
139
+ "lte",
140
+ "contains",
141
+ "like"
142
+ ].includes(possibleOp) || possibleOp.startsWith("$");
143
+ if (isOperator) {
144
+ return translateComparison(filters[0], possibleOp, filters[2]);
145
+ }
146
+ }
147
+ const groups = [];
148
+ let nextLogic = "and";
149
+ for (const item of filters) {
150
+ if (typeof item === "string") {
151
+ const lower = item.toLowerCase();
152
+ if (lower === "or") nextLogic = "or";
153
+ else if (lower === "and") nextLogic = "and";
154
+ continue;
155
+ }
156
+ if (Array.isArray(item)) {
157
+ const isTuple = item.length === 3 && typeof item[0] === "string" && typeof item[1] === "string" && !Array.isArray(item[2]);
158
+ const translated = isTuple ? translateComparison(item[0], item[1], item[2]) : translateArrayFilter(item);
159
+ groups.push({ logic: nextLogic, filter: translated });
160
+ nextLogic = "and";
161
+ }
162
+ }
163
+ if (groups.length === 0) return {};
164
+ if (groups.length === 1) return groups[0].filter;
165
+ const hasOr = groups.some((g) => g.logic === "or");
166
+ if (!hasOr) {
167
+ return { $and: groups.map((g) => g.filter) };
168
+ }
169
+ const orGroups = [[]];
170
+ for (const g of groups) {
171
+ if (g.logic === "or") {
172
+ orGroups.push([g.filter]);
173
+ } else {
174
+ orGroups[orGroups.length - 1].push(g.filter);
175
+ }
176
+ }
177
+ const orClauses = orGroups.map((group) => {
178
+ if (group.length === 1) return group[0];
179
+ return { $and: group };
180
+ });
181
+ if (orClauses.length === 1) return orClauses[0];
182
+ return { $or: orClauses };
183
+ }
184
+ function translateComparison(field, op, value) {
185
+ const mappedField = mapFieldName(field);
186
+ switch (op) {
187
+ case "=":
188
+ case "eq":
189
+ return { [mappedField]: value };
190
+ case "!=":
191
+ case "<>":
192
+ case "ne":
193
+ return { [mappedField]: { $ne: value } };
194
+ case ">":
195
+ case "gt":
196
+ return { [mappedField]: { $gt: value } };
197
+ case ">=":
198
+ case "gte":
199
+ return { [mappedField]: { $gte: value } };
200
+ case "<":
201
+ case "lt":
202
+ return { [mappedField]: { $lt: value } };
203
+ case "<=":
204
+ case "lte":
205
+ return { [mappedField]: { $lte: value } };
206
+ case "in":
207
+ return { [mappedField]: { $in: value } };
208
+ case "nin":
209
+ return { [mappedField]: { $nin: value } };
210
+ case "contains":
211
+ case "like":
212
+ return { [mappedField]: { $regex: escapeRegex(String(value)), $options: "i" } };
213
+ default:
214
+ return { [mappedField]: { [`$${op}`]: value } };
215
+ }
216
+ }
217
+ function mapFieldName(field) {
218
+ if (field === "createdAt") return "created_at";
219
+ if (field === "updatedAt") return "updated_at";
220
+ return field;
221
+ }
222
+ function escapeRegex(str) {
223
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
224
+ }
225
+
226
+ // src/mongodb-aggregation.ts
227
+ function buildAggregationPipeline(opts) {
228
+ const pipeline = [];
229
+ if (opts.where) {
230
+ const matchFilter = translateFilter(opts.where);
231
+ if (Object.keys(matchFilter).length > 0) {
232
+ pipeline.push({ $match: matchFilter });
233
+ }
234
+ }
235
+ if (opts.aggregations && opts.aggregations.length > 0) {
236
+ const groupId = {};
237
+ const groupAccumulators = {};
238
+ if (opts.groupBy && opts.groupBy.length > 0) {
239
+ for (const field of opts.groupBy) {
240
+ groupId[field] = `$${field}`;
241
+ }
242
+ }
243
+ for (const agg of opts.aggregations) {
244
+ groupAccumulators[agg.alias] = buildAccumulator(agg);
245
+ }
246
+ pipeline.push({
247
+ $group: {
248
+ _id: Object.keys(groupId).length > 0 ? groupId : null,
249
+ ...groupAccumulators
250
+ }
251
+ });
252
+ if (opts.groupBy && opts.groupBy.length > 0) {
253
+ const project = { _id: 0 };
254
+ for (const field of opts.groupBy) {
255
+ project[field] = `$_id.${field}`;
256
+ }
257
+ for (const agg of opts.aggregations) {
258
+ project[agg.alias] = 1;
259
+ }
260
+ pipeline.push({ $project: project });
261
+ }
262
+ }
263
+ if (opts.orderBy && opts.orderBy.length > 0) {
264
+ const sort = {};
265
+ for (const item of opts.orderBy) {
266
+ sort[item.field] = item.order === "desc" ? -1 : 1;
267
+ }
268
+ pipeline.push({ $sort: sort });
269
+ }
270
+ if (opts.offset !== void 0 && opts.offset > 0) {
271
+ pipeline.push({ $skip: opts.offset });
272
+ }
273
+ if (opts.limit !== void 0) {
274
+ pipeline.push({ $limit: opts.limit });
275
+ }
276
+ return pipeline;
277
+ }
278
+ function buildAccumulator(agg) {
279
+ const fieldRef = agg.field ? `$${agg.field}` : null;
280
+ switch (agg.function) {
281
+ case "count":
282
+ return { $sum: 1 };
283
+ case "sum":
284
+ return { $sum: fieldRef ?? 0 };
285
+ case "avg":
286
+ return { $avg: fieldRef ?? 0 };
287
+ case "min":
288
+ return { $min: fieldRef ?? 0 };
289
+ case "max":
290
+ return { $max: fieldRef ?? 0 };
291
+ case "count_distinct":
292
+ return { $addToSet: fieldRef ?? null };
293
+ case "array_agg":
294
+ return { $push: fieldRef ?? "$$ROOT" };
295
+ case "string_agg":
296
+ return { $push: fieldRef ?? "" };
297
+ default:
298
+ return { $sum: fieldRef ?? 0 };
299
+ }
300
+ }
301
+ function postProcessAggregation(results, aggregations) {
302
+ const countDistinctFields = aggregations.filter((a) => a.function === "count_distinct").map((a) => a.alias);
303
+ const stringAggFields = aggregations.filter((a) => a.function === "string_agg").map((a) => a.alias);
304
+ if (countDistinctFields.length === 0 && stringAggFields.length === 0) {
305
+ return results;
306
+ }
307
+ return results.map((row) => {
308
+ const processed = { ...row };
309
+ for (const field of countDistinctFields) {
310
+ if (Array.isArray(processed[field])) {
311
+ processed[field] = processed[field].length;
312
+ }
313
+ }
314
+ for (const field of stringAggFields) {
315
+ if (Array.isArray(processed[field])) {
316
+ processed[field] = processed[field].join(", ");
317
+ }
318
+ }
319
+ return processed;
320
+ });
321
+ }
322
+
323
+ // src/mongodb-schema.ts
324
+ async function syncCollectionSchema(db, collectionName, schema) {
325
+ const collections = await db.listCollections({ name: collectionName }).toArray();
326
+ if (collections.length === 0) {
327
+ await db.createCollection(collectionName);
328
+ }
329
+ const collection = db.collection(collectionName);
330
+ const indexOps = [
331
+ { spec: { id: 1 }, options: { unique: true, name: "idx_id_unique" } },
332
+ { spec: { created_at: 1 }, options: { name: "idx_created_at" } },
333
+ { spec: { updated_at: 1 }, options: { name: "idx_updated_at" } }
334
+ ];
335
+ if (schema.fields) {
336
+ for (const [fieldName, field] of Object.entries(schema.fields)) {
337
+ if (field.unique) {
338
+ indexOps.push({
339
+ spec: { [fieldName]: 1 },
340
+ options: { unique: true, sparse: true, name: `idx_${fieldName}_unique` }
341
+ });
342
+ } else if (field.indexed) {
343
+ indexOps.push({
344
+ spec: { [fieldName]: 1 },
345
+ options: { name: `idx_${fieldName}` }
346
+ });
347
+ }
348
+ if (field.type === "lookup" && field.reference_to) {
349
+ indexOps.push({
350
+ spec: { [fieldName]: 1 },
351
+ options: { name: `idx_${fieldName}_lookup` }
352
+ });
353
+ }
354
+ }
355
+ }
356
+ for (const { spec, options } of indexOps) {
357
+ try {
358
+ await collection.createIndex(spec, options);
359
+ } catch (error) {
360
+ if (error.codeName === "IndexOptionsConflict" || error.code === 85) {
361
+ continue;
362
+ }
363
+ throw error;
364
+ }
365
+ }
366
+ }
367
+ async function dropCollection(db, collectionName) {
368
+ const collections = await db.listCollections({ name: collectionName }).toArray();
369
+ if (collections.length > 0) {
370
+ await db.dropCollection(collectionName);
371
+ }
372
+ }
373
+
374
+ // src/mongodb-driver.ts
375
+ var DEFAULT_ID_LENGTH = 16;
376
+ var MongoDBDriver = class {
377
+ constructor(config) {
378
+ this.name = "com.objectstack.driver.mongodb";
379
+ this.version = "1.0.0";
380
+ this.supports = {
381
+ // Basic CRUD Operations
382
+ create: true,
383
+ read: true,
384
+ update: true,
385
+ delete: true,
386
+ // Bulk Operations
387
+ bulkCreate: true,
388
+ bulkUpdate: true,
389
+ bulkDelete: true,
390
+ // Transaction & Connection Management
391
+ transactions: true,
392
+ savepoints: false,
393
+ // Query Operations
394
+ queryFilters: true,
395
+ queryAggregations: true,
396
+ querySorting: true,
397
+ queryPagination: true,
398
+ queryWindowFunctions: false,
399
+ querySubqueries: false,
400
+ queryCTE: false,
401
+ joins: false,
402
+ // Advanced Features
403
+ fullTextSearch: true,
404
+ jsonQuery: true,
405
+ geospatialQuery: true,
406
+ streaming: true,
407
+ jsonFields: true,
408
+ arrayFields: true,
409
+ vectorSearch: false,
410
+ // Schema Management
411
+ schemaSync: true,
412
+ batchSchemaSync: true,
413
+ migrations: false,
414
+ indexes: true,
415
+ // Performance & Optimization
416
+ connectionPooling: true,
417
+ preparedStatements: false,
418
+ queryCache: false
419
+ };
420
+ this.config = config;
421
+ const clientOptions = {
422
+ maxPoolSize: config.maxPoolSize ?? 10,
423
+ minPoolSize: config.minPoolSize ?? 1,
424
+ connectTimeoutMS: config.connectTimeoutMS ?? 1e4,
425
+ serverSelectionTimeoutMS: config.serverSelectionTimeoutMS ?? 5e3,
426
+ ...config.options
427
+ };
428
+ this.client = new MongoClient(config.url, clientOptions);
429
+ }
430
+ // ===========================================================================
431
+ // Lifecycle
432
+ // ===========================================================================
433
+ async connect() {
434
+ await this.client.connect();
435
+ const dbName = this.config.database || this.extractDatabaseName(this.config.url);
436
+ this.db = this.client.db(dbName);
437
+ }
438
+ async disconnect() {
439
+ await this.client.close();
440
+ }
441
+ async checkHealth() {
442
+ try {
443
+ await this.db.command({ ping: 1 });
444
+ return true;
445
+ } catch {
446
+ return false;
447
+ }
448
+ }
449
+ getPoolStats() {
450
+ return void 0;
451
+ }
452
+ // ===========================================================================
453
+ // Raw Execution
454
+ // ===========================================================================
455
+ async execute(command, _parameters, options) {
456
+ const session = this.getSession(options);
457
+ if (typeof command === "object" && command !== null) {
458
+ return await this.db.command(command, { session });
459
+ }
460
+ return command;
461
+ }
462
+ // ===========================================================================
463
+ // CRUD Operations
464
+ // ===========================================================================
465
+ async find(object, query, options) {
466
+ const collection = this.getCollection(object);
467
+ const session = this.getSession(options);
468
+ const filter = translateFilter(query.where);
469
+ const findOptions = { session };
470
+ if (query.fields && query.fields.length > 0) {
471
+ const projection = {};
472
+ for (const field of query.fields) {
473
+ projection[field] = 1;
474
+ }
475
+ projection.id = 1;
476
+ projection._id = 0;
477
+ findOptions.projection = projection;
478
+ } else {
479
+ findOptions.projection = { _id: 0 };
480
+ }
481
+ if (query.orderBy && Array.isArray(query.orderBy)) {
482
+ const sort = {};
483
+ for (const item of query.orderBy) {
484
+ if (item.field) {
485
+ sort[this.mapFieldName(item.field)] = item.order === "desc" ? -1 : 1;
486
+ }
487
+ }
488
+ findOptions.sort = sort;
489
+ }
490
+ if (query.offset !== void 0) findOptions.skip = query.offset;
491
+ if (query.limit !== void 0) findOptions.limit = query.limit;
492
+ const cursor = collection.find(filter, findOptions);
493
+ const results = await cursor.toArray();
494
+ return results;
495
+ }
496
+ async findOne(object, query, options) {
497
+ const collection = this.getCollection(object);
498
+ const session = this.getSession(options);
499
+ const filter = translateFilter(query.where);
500
+ const result = await collection.findOne(filter, {
501
+ session,
502
+ projection: { _id: 0 }
503
+ });
504
+ return result;
505
+ }
506
+ findStream(object, query, options) {
507
+ return this._findStream(object, query, options);
508
+ }
509
+ async *_findStream(object, query, options) {
510
+ const collection = this.getCollection(object);
511
+ const session = this.getSession(options);
512
+ const filter = translateFilter(query.where);
513
+ const findOptions = {
514
+ session,
515
+ projection: { _id: 0 }
516
+ };
517
+ if (query.orderBy && Array.isArray(query.orderBy)) {
518
+ const sort = {};
519
+ for (const item of query.orderBy) {
520
+ if (item.field) {
521
+ sort[this.mapFieldName(item.field)] = item.order === "desc" ? -1 : 1;
522
+ }
523
+ }
524
+ findOptions.sort = sort;
525
+ }
526
+ if (query.offset !== void 0) findOptions.skip = query.offset;
527
+ if (query.limit !== void 0) findOptions.limit = query.limit;
528
+ const cursor = collection.find(filter, findOptions);
529
+ for await (const doc of cursor) {
530
+ yield doc;
531
+ }
532
+ }
533
+ async create(object, data, options) {
534
+ const collection = this.getCollection(object);
535
+ const session = this.getSession(options);
536
+ const { _id, ...rest } = data;
537
+ const toInsert = { ...rest };
538
+ if (toInsert.id === void 0) {
539
+ toInsert.id = nanoid(DEFAULT_ID_LENGTH);
540
+ }
541
+ const now = /* @__PURE__ */ new Date();
542
+ if (toInsert.created_at === void 0) toInsert.created_at = now;
543
+ if (toInsert.updated_at === void 0) toInsert.updated_at = now;
544
+ await collection.insertOne(toInsert, { session });
545
+ const { _id: insertedId, ...result } = toInsert;
546
+ return result;
547
+ }
548
+ async update(object, id, data, options) {
549
+ const collection = this.getCollection(object);
550
+ const session = this.getSession(options);
551
+ const { _id, id: dataId, ...updateData } = data;
552
+ updateData.updated_at = /* @__PURE__ */ new Date();
553
+ await collection.updateOne(
554
+ { id: String(id) },
555
+ { $set: updateData },
556
+ { session }
557
+ );
558
+ const updated = await collection.findOne(
559
+ { id: String(id) },
560
+ { session, projection: { _id: 0 } }
561
+ );
562
+ return updated || { id: String(id), ...updateData };
563
+ }
564
+ async upsert(object, data, conflictKeys, options) {
565
+ const collection = this.getCollection(object);
566
+ const session = this.getSession(options);
567
+ const { _id, ...rest } = data;
568
+ const toUpsert = { ...rest };
569
+ if (toUpsert.id === void 0) {
570
+ toUpsert.id = nanoid(DEFAULT_ID_LENGTH);
571
+ }
572
+ const now = /* @__PURE__ */ new Date();
573
+ toUpsert.updated_at = now;
574
+ const mergeKeys = conflictKeys && conflictKeys.length > 0 ? conflictKeys : ["id"];
575
+ const filter = {};
576
+ for (const key of mergeKeys) {
577
+ if (toUpsert[key] !== void 0) {
578
+ filter[key] = toUpsert[key];
579
+ }
580
+ }
581
+ await collection.updateOne(
582
+ filter,
583
+ {
584
+ $set: toUpsert,
585
+ $setOnInsert: { created_at: now }
586
+ },
587
+ { upsert: true, session }
588
+ );
589
+ const result = await collection.findOne(
590
+ { id: toUpsert.id },
591
+ { session, projection: { _id: 0 } }
592
+ );
593
+ return result || toUpsert;
594
+ }
595
+ async delete(object, id, options) {
596
+ const collection = this.getCollection(object);
597
+ const session = this.getSession(options);
598
+ const result = await collection.deleteOne({ id: String(id) }, { session });
599
+ return result.deletedCount > 0;
600
+ }
601
+ async count(object, query, options) {
602
+ const collection = this.getCollection(object);
603
+ const session = this.getSession(options);
604
+ const filter = query?.where ? translateFilter(query.where) : {};
605
+ return await collection.countDocuments(filter, { session });
606
+ }
607
+ // ===========================================================================
608
+ // Bulk Operations
609
+ // ===========================================================================
610
+ async bulkCreate(object, dataArray, options) {
611
+ const collection = this.getCollection(object);
612
+ const session = this.getSession(options);
613
+ const now = /* @__PURE__ */ new Date();
614
+ const docs = dataArray.map((data) => {
615
+ const { _id, ...rest } = data;
616
+ const doc = { ...rest };
617
+ if (doc.id === void 0) doc.id = nanoid(DEFAULT_ID_LENGTH);
618
+ if (doc.created_at === void 0) doc.created_at = now;
619
+ if (doc.updated_at === void 0) doc.updated_at = now;
620
+ return doc;
621
+ });
622
+ await collection.insertMany(docs, { session });
623
+ return docs.map(({ _id, ...rest }) => rest);
624
+ }
625
+ async bulkUpdate(object, updates, options) {
626
+ const collection = this.getCollection(object);
627
+ const session = this.getSession(options);
628
+ const now = /* @__PURE__ */ new Date();
629
+ const bulkOps = updates.map(({ id, data }) => {
630
+ const { _id, id: dataId, ...updateData } = data;
631
+ updateData.updated_at = now;
632
+ return {
633
+ updateOne: {
634
+ filter: { id: String(id) },
635
+ update: { $set: updateData }
636
+ }
637
+ };
638
+ });
639
+ await collection.bulkWrite(bulkOps, { session });
640
+ const ids = updates.map((u) => String(u.id));
641
+ const results = await collection.find(
642
+ { id: { $in: ids } },
643
+ { session, projection: { _id: 0 } }
644
+ ).toArray();
645
+ return results;
646
+ }
647
+ async bulkDelete(object, ids, options) {
648
+ const collection = this.getCollection(object);
649
+ const session = this.getSession(options);
650
+ await collection.deleteMany(
651
+ { id: { $in: ids.map(String) } },
652
+ { session }
653
+ );
654
+ }
655
+ async updateMany(object, query, data, options) {
656
+ const collection = this.getCollection(object);
657
+ const session = this.getSession(options);
658
+ const filter = translateFilter(query.where);
659
+ const { _id, id, ...updateData } = data;
660
+ updateData.updated_at = /* @__PURE__ */ new Date();
661
+ const result = await collection.updateMany(
662
+ filter,
663
+ { $set: updateData },
664
+ { session }
665
+ );
666
+ return result.modifiedCount;
667
+ }
668
+ async deleteMany(object, query, options) {
669
+ const collection = this.getCollection(object);
670
+ const session = this.getSession(options);
671
+ const filter = translateFilter(query.where);
672
+ const result = await collection.deleteMany(filter, { session });
673
+ return result.deletedCount;
674
+ }
675
+ // ===========================================================================
676
+ // Aggregation
677
+ // ===========================================================================
678
+ async aggregate(object, query, options) {
679
+ const collection = this.getCollection(object);
680
+ const session = this.getSession(options);
681
+ const aggregations = query.aggregations || query.aggregate || [];
682
+ const pipeline = buildAggregationPipeline({
683
+ where: query.where,
684
+ aggregations,
685
+ groupBy: query.groupBy,
686
+ orderBy: query.orderBy,
687
+ limit: query.limit,
688
+ offset: query.offset
689
+ });
690
+ const results = await collection.aggregate(pipeline, { session }).toArray();
691
+ return postProcessAggregation(results, aggregations);
692
+ }
693
+ // ===========================================================================
694
+ // Transactions
695
+ // ===========================================================================
696
+ async beginTransaction(_options) {
697
+ const session = this.client.startSession();
698
+ session.startTransaction();
699
+ return session;
700
+ }
701
+ async commit(transaction) {
702
+ const session = transaction;
703
+ await session.commitTransaction();
704
+ await session.endSession();
705
+ }
706
+ async rollback(transaction) {
707
+ const session = transaction;
708
+ await session.abortTransaction();
709
+ await session.endSession();
710
+ }
711
+ // ===========================================================================
712
+ // Schema Management
713
+ // ===========================================================================
714
+ async syncSchema(object, schema, _options) {
715
+ const objectDef = schema;
716
+ await syncCollectionSchema(this.db, object, objectDef);
717
+ }
718
+ async syncSchemasBatch(schemas, options) {
719
+ for (const { object, schema } of schemas) {
720
+ await this.syncSchema(object, schema, options);
721
+ }
722
+ }
723
+ async dropTable(object, _options) {
724
+ await dropCollection(this.db, object);
725
+ }
726
+ // ===========================================================================
727
+ // Query Plan Analysis
728
+ // ===========================================================================
729
+ async explain(object, query, _options) {
730
+ const collection = this.getCollection(object);
731
+ const filter = translateFilter(query.where);
732
+ const explanation = await collection.find(filter).explain("executionStats");
733
+ return explanation;
734
+ }
735
+ // ===========================================================================
736
+ // Helpers
737
+ // ===========================================================================
738
+ /** Get the underlying Db instance for advanced usage. */
739
+ getDb() {
740
+ return this.db;
741
+ }
742
+ /** Get the underlying MongoClient for advanced usage. */
743
+ getClient() {
744
+ return this.client;
745
+ }
746
+ getCollection(name) {
747
+ return this.db.collection(name);
748
+ }
749
+ getSession(options) {
750
+ return options?.transaction;
751
+ }
752
+ mapFieldName(field) {
753
+ if (field === "createdAt") return "created_at";
754
+ if (field === "updatedAt") return "updated_at";
755
+ return field;
756
+ }
757
+ extractDatabaseName(url) {
758
+ const match = url.match(/\/\/[^/]*\/([^?/]+)/);
759
+ if (match) return match[1];
760
+ return "objectstack";
761
+ }
762
+ };
763
+
764
+ // src/index.ts
765
+ var index_default = {
766
+ id: "com.objectstack.driver.mongodb",
767
+ version: "1.0.0",
768
+ onEnable: async (context) => {
769
+ const { logger, config, drivers } = context;
770
+ logger.info("[MongoDB Driver] Initializing...");
771
+ if (drivers) {
772
+ const driver = new MongoDBDriver(config);
773
+ drivers.register(driver);
774
+ logger.info(`[MongoDB Driver] Registered driver: ${driver.name}`);
775
+ } else {
776
+ logger.warn("[MongoDB Driver] No driver registry found in context.");
777
+ }
778
+ }
779
+ };
780
+ export {
781
+ MongoDBDriver,
782
+ buildAggregationPipeline,
783
+ index_default as default,
784
+ postProcessAggregation,
785
+ translateFilter
786
+ };
787
+ //# sourceMappingURL=index.mjs.map