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