@objectql/driver-mongo 1.6.1 → 1.7.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/CHANGELOG.md +17 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +94 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +100 -28
- package/test/index.test.ts +128 -0
- package/test/integration.test.ts +583 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @objectql/driver-mongo
|
|
2
2
|
|
|
3
|
+
## 1.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Release small version update with latest improvements
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @objectql/types@1.7.1
|
|
10
|
+
|
|
11
|
+
## 1.7.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- Release version 1.7.0 with improvements and bug fixes:
|
|
16
|
+
- Updated default port for ObjectQL Studio to 5555
|
|
17
|
+
- Improved port listening logic in Studio
|
|
18
|
+
- Enhanced stability and performance
|
|
19
|
+
|
|
3
20
|
## 1.6.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ export declare class MongoDriver implements Driver {
|
|
|
11
11
|
private connect;
|
|
12
12
|
private getCollection;
|
|
13
13
|
private normalizeId;
|
|
14
|
+
/**
|
|
15
|
+
* Map API document (with 'id') to MongoDB document (with '_id')
|
|
16
|
+
*/
|
|
17
|
+
private mapToMongo;
|
|
18
|
+
/**
|
|
19
|
+
* Map MongoDB document (with '_id') to API document (with 'id')
|
|
20
|
+
*/
|
|
21
|
+
private mapFromMongo;
|
|
22
|
+
/**
|
|
23
|
+
* Map array of MongoDB documents to API documents
|
|
24
|
+
*/
|
|
25
|
+
private mapFromMongoArray;
|
|
14
26
|
private mapFilters;
|
|
15
27
|
find(objectName: string, query: any, options?: any): Promise<any[]>;
|
|
16
28
|
findOne(objectName: string, id: string | number, query?: any, options?: any): Promise<any>;
|
|
@@ -22,4 +34,5 @@ export declare class MongoDriver implements Driver {
|
|
|
22
34
|
updateMany(objectName: string, filters: any, data: any, options?: any): Promise<any>;
|
|
23
35
|
deleteMany(objectName: string, filters: any, options?: any): Promise<any>;
|
|
24
36
|
aggregate(objectName: string, pipeline: any[], options?: any): Promise<any[]>;
|
|
37
|
+
disconnect(): Promise<void>;
|
|
25
38
|
}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,36 @@ class MongoDriver {
|
|
|
23
23
|
// We only pass through what is given.
|
|
24
24
|
return id;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Map API document (with 'id') to MongoDB document (with '_id')
|
|
28
|
+
*/
|
|
29
|
+
mapToMongo(doc) {
|
|
30
|
+
if (!doc)
|
|
31
|
+
return doc;
|
|
32
|
+
const { id, ...rest } = doc;
|
|
33
|
+
if (id !== undefined) {
|
|
34
|
+
return { _id: id, ...rest };
|
|
35
|
+
}
|
|
36
|
+
return doc;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map MongoDB document (with '_id') to API document (with 'id')
|
|
40
|
+
*/
|
|
41
|
+
mapFromMongo(doc) {
|
|
42
|
+
if (!doc)
|
|
43
|
+
return doc;
|
|
44
|
+
const { _id, ...rest } = doc;
|
|
45
|
+
if (_id !== undefined) {
|
|
46
|
+
return { id: _id, ...rest };
|
|
47
|
+
}
|
|
48
|
+
return doc;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Map array of MongoDB documents to API documents
|
|
52
|
+
*/
|
|
53
|
+
mapFromMongoArray(docs) {
|
|
54
|
+
return docs.map(doc => this.mapFromMongo(doc));
|
|
55
|
+
}
|
|
26
56
|
mapFilters(filters) {
|
|
27
57
|
if (!filters || filters.length === 0)
|
|
28
58
|
return {};
|
|
@@ -34,40 +64,43 @@ class MongoDriver {
|
|
|
34
64
|
if (Array.isArray(item)) {
|
|
35
65
|
const [field, op, value] = item;
|
|
36
66
|
let mongoCondition = {};
|
|
37
|
-
|
|
38
|
-
|
|
67
|
+
// Map both 'id' and '_id' to '_id' for MongoDB compatibility
|
|
68
|
+
// This ensures backward compatibility for queries using '_id'
|
|
69
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
70
|
+
if (dbField === '_id') {
|
|
71
|
+
mongoCondition[dbField] = this.normalizeId(value);
|
|
39
72
|
}
|
|
40
73
|
else {
|
|
41
74
|
switch (op) {
|
|
42
75
|
case '=':
|
|
43
|
-
mongoCondition[
|
|
76
|
+
mongoCondition[dbField] = { $eq: value };
|
|
44
77
|
break;
|
|
45
78
|
case '!=':
|
|
46
|
-
mongoCondition[
|
|
79
|
+
mongoCondition[dbField] = { $ne: value };
|
|
47
80
|
break;
|
|
48
81
|
case '>':
|
|
49
|
-
mongoCondition[
|
|
82
|
+
mongoCondition[dbField] = { $gt: value };
|
|
50
83
|
break;
|
|
51
84
|
case '>=':
|
|
52
|
-
mongoCondition[
|
|
85
|
+
mongoCondition[dbField] = { $gte: value };
|
|
53
86
|
break;
|
|
54
87
|
case '<':
|
|
55
|
-
mongoCondition[
|
|
88
|
+
mongoCondition[dbField] = { $lt: value };
|
|
56
89
|
break;
|
|
57
90
|
case '<=':
|
|
58
|
-
mongoCondition[
|
|
91
|
+
mongoCondition[dbField] = { $lte: value };
|
|
59
92
|
break;
|
|
60
93
|
case 'in':
|
|
61
|
-
mongoCondition[
|
|
94
|
+
mongoCondition[dbField] = { $in: value };
|
|
62
95
|
break;
|
|
63
96
|
case 'nin':
|
|
64
|
-
mongoCondition[
|
|
97
|
+
mongoCondition[dbField] = { $nin: value };
|
|
65
98
|
break;
|
|
66
99
|
case 'contains':
|
|
67
100
|
// Basic regex escape should be added for safety
|
|
68
|
-
mongoCondition[
|
|
101
|
+
mongoCondition[dbField] = { $regex: value, $options: 'i' };
|
|
69
102
|
break;
|
|
70
|
-
default: mongoCondition[
|
|
103
|
+
default: mongoCondition[dbField] = { $eq: value };
|
|
71
104
|
}
|
|
72
105
|
}
|
|
73
106
|
conditions.push(mongoCondition);
|
|
@@ -108,22 +141,29 @@ class MongoDriver {
|
|
|
108
141
|
// map [['field', 'desc']] to { field: -1 }
|
|
109
142
|
findOptions.sort = {};
|
|
110
143
|
for (const [field, order] of query.sort) {
|
|
111
|
-
|
|
144
|
+
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
145
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
146
|
+
findOptions.sort[dbField] = order === 'desc' ? -1 : 1;
|
|
112
147
|
}
|
|
113
148
|
}
|
|
114
149
|
if (query.fields && query.fields.length > 0) {
|
|
115
150
|
findOptions.projection = {};
|
|
116
151
|
for (const field of query.fields) {
|
|
117
|
-
|
|
152
|
+
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
153
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
154
|
+
findOptions.projection[dbField] = 1;
|
|
118
155
|
}
|
|
119
156
|
}
|
|
120
157
|
const results = await collection.find(filter, findOptions).toArray();
|
|
121
|
-
|
|
158
|
+
// Map MongoDB documents to API format (convert _id to id)
|
|
159
|
+
return this.mapFromMongoArray(results);
|
|
122
160
|
}
|
|
123
161
|
async findOne(objectName, id, query, options) {
|
|
124
162
|
const collection = await this.getCollection(objectName);
|
|
125
163
|
if (id) {
|
|
126
|
-
|
|
164
|
+
const result = await collection.findOne({ _id: this.normalizeId(id) });
|
|
165
|
+
// Map MongoDB document to API format (convert _id to id)
|
|
166
|
+
return this.mapFromMongo(result);
|
|
127
167
|
}
|
|
128
168
|
if (query) {
|
|
129
169
|
const results = await this.find(objectName, { ...query, limit: 1 }, options);
|
|
@@ -133,18 +173,24 @@ class MongoDriver {
|
|
|
133
173
|
}
|
|
134
174
|
async create(objectName, data, options) {
|
|
135
175
|
const collection = await this.getCollection(objectName);
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
176
|
+
// Map API document (id) to MongoDB document (_id)
|
|
177
|
+
const mongoDoc = this.mapToMongo(data);
|
|
178
|
+
// If no _id is provided, generate a String ID instead of allowing Mongo to generate an ObjectId
|
|
179
|
+
if (!mongoDoc._id) {
|
|
180
|
+
mongoDoc._id = new mongodb_1.ObjectId().toHexString();
|
|
139
181
|
}
|
|
140
|
-
const result = await collection.insertOne(
|
|
141
|
-
|
|
182
|
+
const result = await collection.insertOne(mongoDoc);
|
|
183
|
+
// Return API format document (convert _id to id)
|
|
184
|
+
return this.mapFromMongo({ ...mongoDoc, _id: result.insertedId });
|
|
142
185
|
}
|
|
143
186
|
async update(objectName, id, data, options) {
|
|
144
187
|
const collection = await this.getCollection(objectName);
|
|
188
|
+
// Map API document (id) to MongoDB document (_id) for update data
|
|
189
|
+
// But we should not allow updating the _id field itself
|
|
190
|
+
const { id: _ignoredId, ...updateData } = data; // intentionally ignore id to prevent updating primary key
|
|
145
191
|
// Handle atomic operators if present
|
|
146
|
-
const isAtomic = Object.keys(
|
|
147
|
-
const update = isAtomic ?
|
|
192
|
+
const isAtomic = Object.keys(updateData).some(k => k.startsWith('$'));
|
|
193
|
+
const update = isAtomic ? updateData : { $set: updateData };
|
|
148
194
|
const result = await collection.updateOne({ _id: this.normalizeId(id) }, update);
|
|
149
195
|
return result.modifiedCount; // or return updated document?
|
|
150
196
|
}
|
|
@@ -160,15 +206,28 @@ class MongoDriver {
|
|
|
160
206
|
}
|
|
161
207
|
// Bulk Operations
|
|
162
208
|
async createMany(objectName, data, options) {
|
|
209
|
+
if (!data || data.length === 0)
|
|
210
|
+
return [];
|
|
163
211
|
const collection = await this.getCollection(objectName);
|
|
164
|
-
|
|
165
|
-
|
|
212
|
+
// Map all API documents to MongoDB format
|
|
213
|
+
const mongoDocs = data.map(doc => {
|
|
214
|
+
const mongoDoc = this.mapToMongo(doc);
|
|
215
|
+
if (!mongoDoc._id) {
|
|
216
|
+
mongoDoc._id = new mongodb_1.ObjectId().toHexString();
|
|
217
|
+
}
|
|
218
|
+
return mongoDoc;
|
|
219
|
+
});
|
|
220
|
+
const result = await collection.insertMany(mongoDocs);
|
|
221
|
+
// Return API format (convert _id to id)
|
|
222
|
+
return Object.values(result.insertedIds).map(id => ({ id }));
|
|
166
223
|
}
|
|
167
224
|
async updateMany(objectName, filters, data, options) {
|
|
168
225
|
const collection = await this.getCollection(objectName);
|
|
169
226
|
const filter = this.mapFilters(filters);
|
|
170
|
-
|
|
171
|
-
const
|
|
227
|
+
// Remove 'id' field from update data as it shouldn't be updated
|
|
228
|
+
const { id: idToIgnore, ...updateData } = data;
|
|
229
|
+
const isAtomic = Object.keys(updateData).some(k => k.startsWith('$'));
|
|
230
|
+
const update = isAtomic ? updateData : { $set: updateData };
|
|
172
231
|
const result = await collection.updateMany(filter, update);
|
|
173
232
|
return result.modifiedCount;
|
|
174
233
|
}
|
|
@@ -180,7 +239,14 @@ class MongoDriver {
|
|
|
180
239
|
}
|
|
181
240
|
async aggregate(objectName, pipeline, options) {
|
|
182
241
|
const collection = await this.getCollection(objectName);
|
|
183
|
-
|
|
242
|
+
const results = await collection.aggregate(pipeline).toArray();
|
|
243
|
+
// Map MongoDB documents to API format (convert _id to id)
|
|
244
|
+
return this.mapFromMongoArray(results);
|
|
245
|
+
}
|
|
246
|
+
async disconnect() {
|
|
247
|
+
if (this.client) {
|
|
248
|
+
await this.client.close();
|
|
249
|
+
}
|
|
184
250
|
}
|
|
185
251
|
}
|
|
186
252
|
exports.MongoDriver = MongoDriver;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,qCAAyE;AAEzE,MAAa,WAAW;IAMpB,YAAY,MAAwC;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,IAAI,qBAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;IAEO,KAAK,CAAC,OAAO;QACjB,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,UAAkB;QAC1C,MAAM,IAAI,CAAC,SAAS,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAM,UAAU,CAAC,CAAC;IAC/C,CAAC;IAEO,WAAW,CAAC,EAA8B;QAC9C,+EAA+E;QAC/E,sCAAsC;QACtC,OAAO,EAAE,CAAC;IACd,CAAC;IAEO,UAAU,CAAC,OAAY;QAC3B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEhD,6EAA6E;QAC7E,kDAAkD;QAElD,MAAM,UAAU,GAAU,EAAE,CAAC;QAC7B,IAAI,YAAY,GAAG,MAAM,CAAC,CAAC,yBAAyB;QAEpD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;gBAChC,IAAI,cAAc,GAAQ,EAAE,CAAC;gBAE7B,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectql/driver-mongo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"mongodb": "^5.9.2",
|
|
8
|
-
"@objectql/types": "1.
|
|
8
|
+
"@objectql/types": "1.7.1"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"mongodb-memory-server": "^11.0.1"
|
|
9
12
|
},
|
|
10
13
|
"scripts": {
|
|
11
14
|
"build": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,37 @@ export class MongoDriver implements Driver {
|
|
|
30
30
|
return id;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Map API document (with 'id') to MongoDB document (with '_id')
|
|
35
|
+
*/
|
|
36
|
+
private mapToMongo(doc: any): any {
|
|
37
|
+
if (!doc) return doc;
|
|
38
|
+
const { id, ...rest } = doc;
|
|
39
|
+
if (id !== undefined) {
|
|
40
|
+
return { _id: id, ...rest };
|
|
41
|
+
}
|
|
42
|
+
return doc;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Map MongoDB document (with '_id') to API document (with 'id')
|
|
47
|
+
*/
|
|
48
|
+
private mapFromMongo(doc: any): any {
|
|
49
|
+
if (!doc) return doc;
|
|
50
|
+
const { _id, ...rest } = doc;
|
|
51
|
+
if (_id !== undefined) {
|
|
52
|
+
return { id: _id, ...rest };
|
|
53
|
+
}
|
|
54
|
+
return doc;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Map array of MongoDB documents to API documents
|
|
59
|
+
*/
|
|
60
|
+
private mapFromMongoArray(docs: any[]): any[] {
|
|
61
|
+
return docs.map(doc => this.mapFromMongo(doc));
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
private mapFilters(filters: any): Filter<any> {
|
|
34
65
|
if (!filters || filters.length === 0) return {};
|
|
35
66
|
|
|
@@ -44,23 +75,27 @@ export class MongoDriver implements Driver {
|
|
|
44
75
|
const [field, op, value] = item;
|
|
45
76
|
let mongoCondition: any = {};
|
|
46
77
|
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
// Map both 'id' and '_id' to '_id' for MongoDB compatibility
|
|
79
|
+
// This ensures backward compatibility for queries using '_id'
|
|
80
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
81
|
+
|
|
82
|
+
if (dbField === '_id') {
|
|
83
|
+
mongoCondition[dbField] = this.normalizeId(value);
|
|
49
84
|
} else {
|
|
50
85
|
switch (op) {
|
|
51
|
-
case '=': mongoCondition[
|
|
52
|
-
case '!=': mongoCondition[
|
|
53
|
-
case '>': mongoCondition[
|
|
54
|
-
case '>=': mongoCondition[
|
|
55
|
-
case '<': mongoCondition[
|
|
56
|
-
case '<=': mongoCondition[
|
|
57
|
-
case 'in': mongoCondition[
|
|
58
|
-
case 'nin': mongoCondition[
|
|
86
|
+
case '=': mongoCondition[dbField] = { $eq: value }; break;
|
|
87
|
+
case '!=': mongoCondition[dbField] = { $ne: value }; break;
|
|
88
|
+
case '>': mongoCondition[dbField] = { $gt: value }; break;
|
|
89
|
+
case '>=': mongoCondition[dbField] = { $gte: value }; break;
|
|
90
|
+
case '<': mongoCondition[dbField] = { $lt: value }; break;
|
|
91
|
+
case '<=': mongoCondition[dbField] = { $lte: value }; break;
|
|
92
|
+
case 'in': mongoCondition[dbField] = { $in: value }; break;
|
|
93
|
+
case 'nin': mongoCondition[dbField] = { $nin: value }; break;
|
|
59
94
|
case 'contains':
|
|
60
95
|
// Basic regex escape should be added for safety
|
|
61
|
-
mongoCondition[
|
|
96
|
+
mongoCondition[dbField] = { $regex: value, $options: 'i' };
|
|
62
97
|
break;
|
|
63
|
-
default: mongoCondition[
|
|
98
|
+
default: mongoCondition[dbField] = { $eq: value };
|
|
64
99
|
}
|
|
65
100
|
}
|
|
66
101
|
conditions.push(mongoCondition);
|
|
@@ -101,24 +136,31 @@ export class MongoDriver implements Driver {
|
|
|
101
136
|
// map [['field', 'desc']] to { field: -1 }
|
|
102
137
|
findOptions.sort = {};
|
|
103
138
|
for (const [field, order] of query.sort) {
|
|
104
|
-
|
|
139
|
+
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
140
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
141
|
+
(findOptions.sort as any)[dbField] = order === 'desc' ? -1 : 1;
|
|
105
142
|
}
|
|
106
143
|
}
|
|
107
144
|
if (query.fields && query.fields.length > 0) {
|
|
108
145
|
findOptions.projection = {};
|
|
109
146
|
for (const field of query.fields) {
|
|
110
|
-
|
|
147
|
+
// Map both 'id' and '_id' to '_id' for backward compatibility
|
|
148
|
+
const dbField = (field === 'id' || field === '_id') ? '_id' : field;
|
|
149
|
+
(findOptions.projection as any)[dbField] = 1;
|
|
111
150
|
}
|
|
112
151
|
}
|
|
113
152
|
|
|
114
153
|
const results = await collection.find(filter, findOptions).toArray();
|
|
115
|
-
|
|
154
|
+
// Map MongoDB documents to API format (convert _id to id)
|
|
155
|
+
return this.mapFromMongoArray(results);
|
|
116
156
|
}
|
|
117
157
|
|
|
118
158
|
async findOne(objectName: string, id: string | number, query?: any, options?: any) {
|
|
119
159
|
const collection = await this.getCollection(objectName);
|
|
120
160
|
if (id) {
|
|
121
|
-
|
|
161
|
+
const result = await collection.findOne({ _id: this.normalizeId(id) });
|
|
162
|
+
// Map MongoDB document to API format (convert _id to id)
|
|
163
|
+
return this.mapFromMongo(result);
|
|
122
164
|
}
|
|
123
165
|
if (query) {
|
|
124
166
|
const results = await this.find(objectName, { ...query, limit: 1 }, options);
|
|
@@ -130,21 +172,29 @@ export class MongoDriver implements Driver {
|
|
|
130
172
|
async create(objectName: string, data: any, options?: any) {
|
|
131
173
|
const collection = await this.getCollection(objectName);
|
|
132
174
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
// Map API document (id) to MongoDB document (_id)
|
|
176
|
+
const mongoDoc = this.mapToMongo(data);
|
|
177
|
+
|
|
178
|
+
// If no _id is provided, generate a String ID instead of allowing Mongo to generate an ObjectId
|
|
179
|
+
if (!mongoDoc._id) {
|
|
180
|
+
mongoDoc._id = new ObjectId().toHexString();
|
|
136
181
|
}
|
|
137
182
|
|
|
138
|
-
const result = await collection.insertOne(
|
|
139
|
-
|
|
183
|
+
const result = await collection.insertOne(mongoDoc);
|
|
184
|
+
// Return API format document (convert _id to id)
|
|
185
|
+
return this.mapFromMongo({ ...mongoDoc, _id: result.insertedId });
|
|
140
186
|
}
|
|
141
187
|
|
|
142
188
|
async update(objectName: string, id: string | number, data: any, options?: any) {
|
|
143
189
|
const collection = await this.getCollection(objectName);
|
|
144
190
|
|
|
191
|
+
// Map API document (id) to MongoDB document (_id) for update data
|
|
192
|
+
// But we should not allow updating the _id field itself
|
|
193
|
+
const { id: _ignoredId, ...updateData } = data; // intentionally ignore id to prevent updating primary key
|
|
194
|
+
|
|
145
195
|
// Handle atomic operators if present
|
|
146
|
-
const isAtomic = Object.keys(
|
|
147
|
-
const update = isAtomic ?
|
|
196
|
+
const isAtomic = Object.keys(updateData).some(k => k.startsWith('$'));
|
|
197
|
+
const update = isAtomic ? updateData : { $set: updateData };
|
|
148
198
|
|
|
149
199
|
const result = await collection.updateOne({ _id: this.normalizeId(id) }, update);
|
|
150
200
|
return result.modifiedCount; // or return updated document?
|
|
@@ -164,16 +214,30 @@ export class MongoDriver implements Driver {
|
|
|
164
214
|
|
|
165
215
|
// Bulk Operations
|
|
166
216
|
async createMany(objectName: string, data: any[], options?: any): Promise<any> {
|
|
217
|
+
if (!data || data.length === 0) return [];
|
|
167
218
|
const collection = await this.getCollection(objectName);
|
|
168
|
-
|
|
169
|
-
|
|
219
|
+
// Map all API documents to MongoDB format
|
|
220
|
+
const mongoDocs = data.map(doc => {
|
|
221
|
+
const mongoDoc = this.mapToMongo(doc);
|
|
222
|
+
if (!mongoDoc._id) {
|
|
223
|
+
mongoDoc._id = new ObjectId().toHexString();
|
|
224
|
+
}
|
|
225
|
+
return mongoDoc;
|
|
226
|
+
});
|
|
227
|
+
const result = await collection.insertMany(mongoDocs);
|
|
228
|
+
// Return API format (convert _id to id)
|
|
229
|
+
return Object.values(result.insertedIds).map(id => ({ id }));
|
|
170
230
|
}
|
|
171
231
|
|
|
172
232
|
async updateMany(objectName: string, filters: any, data: any, options?: any): Promise<any> {
|
|
173
233
|
const collection = await this.getCollection(objectName);
|
|
174
234
|
const filter = this.mapFilters(filters);
|
|
175
|
-
|
|
176
|
-
|
|
235
|
+
|
|
236
|
+
// Remove 'id' field from update data as it shouldn't be updated
|
|
237
|
+
const { id: idToIgnore, ...updateData } = data;
|
|
238
|
+
|
|
239
|
+
const isAtomic = Object.keys(updateData).some(k => k.startsWith('$'));
|
|
240
|
+
const update = isAtomic ? updateData : { $set: updateData };
|
|
177
241
|
|
|
178
242
|
const result = await collection.updateMany(filter, update);
|
|
179
243
|
return result.modifiedCount;
|
|
@@ -188,7 +252,15 @@ export class MongoDriver implements Driver {
|
|
|
188
252
|
|
|
189
253
|
async aggregate(objectName: string, pipeline: any[], options?: any): Promise<any[]> {
|
|
190
254
|
const collection = await this.getCollection(objectName);
|
|
191
|
-
|
|
255
|
+
const results = await collection.aggregate(pipeline).toArray();
|
|
256
|
+
// Map MongoDB documents to API format (convert _id to id)
|
|
257
|
+
return this.mapFromMongoArray(results);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async disconnect() {
|
|
261
|
+
if (this.client) {
|
|
262
|
+
await this.client.close();
|
|
263
|
+
}
|
|
192
264
|
}
|
|
193
265
|
}
|
|
194
266
|
|
package/test/index.test.ts
CHANGED
|
@@ -92,4 +92,132 @@ describe('MongoDriver', () => {
|
|
|
92
92
|
);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it('should map "id" field to "_id" in filters', async () => {
|
|
96
|
+
const query = {
|
|
97
|
+
filters: [['id', '=', '12345']]
|
|
98
|
+
};
|
|
99
|
+
await driver.find('users', query);
|
|
100
|
+
|
|
101
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
102
|
+
{ _id: '12345' },
|
|
103
|
+
expect.any(Object)
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should map "id" to "_id" in sorting', async () => {
|
|
108
|
+
const query = {
|
|
109
|
+
sort: [['id', 'desc']]
|
|
110
|
+
};
|
|
111
|
+
await driver.find('users', query);
|
|
112
|
+
|
|
113
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
114
|
+
{},
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
sort: { _id: -1 }
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should map "id" to "_id" in field projection', async () => {
|
|
122
|
+
mockCollection.toArray.mockResolvedValueOnce([{ _id: '123', name: 'Test' }]);
|
|
123
|
+
|
|
124
|
+
const query = {
|
|
125
|
+
fields: ['id', 'name']
|
|
126
|
+
};
|
|
127
|
+
const results = await driver.find('users', query);
|
|
128
|
+
|
|
129
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
130
|
+
{},
|
|
131
|
+
expect.objectContaining({
|
|
132
|
+
projection: { _id: 1, name: 1 }
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Should return 'id' instead of '_id'
|
|
137
|
+
expect(results[0]).toEqual({ id: '123', name: 'Test' });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return documents with "id" field instead of "_id"', async () => {
|
|
141
|
+
mockCollection.toArray.mockResolvedValueOnce([
|
|
142
|
+
{ _id: 'abc123', name: 'Alice', age: 30 }
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const results = await driver.find('users', {});
|
|
146
|
+
|
|
147
|
+
expect(results).toHaveLength(1);
|
|
148
|
+
expect(results[0]).toHaveProperty('id', 'abc123');
|
|
149
|
+
expect(results[0]).not.toHaveProperty('_id');
|
|
150
|
+
expect(results[0]).toHaveProperty('name', 'Alice');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should accept "id" field in create and map to "_id"', async () => {
|
|
154
|
+
mockCollection.insertOne.mockResolvedValueOnce({ insertedId: 'custom-id' });
|
|
155
|
+
|
|
156
|
+
const result = await driver.create('users', { id: 'custom-id', name: 'Bob' });
|
|
157
|
+
|
|
158
|
+
expect(mockCollection.insertOne).toHaveBeenCalledWith(
|
|
159
|
+
expect.objectContaining({ _id: 'custom-id', name: 'Bob' })
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Should return 'id' instead of '_id'
|
|
163
|
+
expect(result).toHaveProperty('id', 'custom-id');
|
|
164
|
+
expect(result).not.toHaveProperty('_id');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should map "_id" to "id" in findOne result', async () => {
|
|
168
|
+
mockCollection.findOne.mockResolvedValueOnce({ _id: '123', name: 'Charlie' });
|
|
169
|
+
|
|
170
|
+
const result = await driver.findOne('users', '123');
|
|
171
|
+
|
|
172
|
+
expect(result).toHaveProperty('id', '123');
|
|
173
|
+
expect(result).not.toHaveProperty('_id');
|
|
174
|
+
expect(result).toHaveProperty('name', 'Charlie');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Backward compatibility tests for legacy '_id' usage
|
|
178
|
+
it('should accept "_id" field in filters for backward compatibility', async () => {
|
|
179
|
+
const query = {
|
|
180
|
+
filters: [['_id', '=', '12345']]
|
|
181
|
+
};
|
|
182
|
+
await driver.find('users', query);
|
|
183
|
+
|
|
184
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
185
|
+
{ _id: '12345' },
|
|
186
|
+
expect.any(Object)
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should accept "_id" in sorting for backward compatibility', async () => {
|
|
191
|
+
const query = {
|
|
192
|
+
sort: [['_id', 'asc']]
|
|
193
|
+
};
|
|
194
|
+
await driver.find('users', query);
|
|
195
|
+
|
|
196
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
197
|
+
{},
|
|
198
|
+
expect.objectContaining({
|
|
199
|
+
sort: { _id: 1 }
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should accept "_id" in field projection for backward compatibility', async () => {
|
|
205
|
+
mockCollection.toArray.mockResolvedValueOnce([{ _id: '456', name: 'Dave' }]);
|
|
206
|
+
|
|
207
|
+
const query = {
|
|
208
|
+
fields: ['_id', 'name']
|
|
209
|
+
};
|
|
210
|
+
const results = await driver.find('users', query);
|
|
211
|
+
|
|
212
|
+
expect(mockCollection.find).toHaveBeenCalledWith(
|
|
213
|
+
{},
|
|
214
|
+
expect.objectContaining({
|
|
215
|
+
projection: { _id: 1, name: 1 }
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Should still return 'id' instead of '_id' in results
|
|
220
|
+
expect(results[0]).toEqual({ id: '456', name: 'Dave' });
|
|
221
|
+
});
|
|
222
|
+
|
|
95
223
|
});
|