@mikro-orm/mongodb 7.0.17 → 7.0.18-dev.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/MongoConnection.d.ts +13 -7
- package/MongoConnection.js +29 -13
- package/MongoDriver.d.ts +7 -3
- package/MongoDriver.js +88 -9
- package/MongoEntityManager.d.ts +6 -2
- package/MongoEntityManager.js +33 -2
- package/MongoMikroORM.d.ts +4 -4
- package/MongoPlatform.js +4 -0
- package/MongoSchemaGenerator.d.ts +5 -0
- package/MongoSchemaGenerator.js +43 -10
- package/README.md +2 -1
- package/package.json +2 -2
package/MongoConnection.d.ts
CHANGED
|
@@ -28,13 +28,13 @@ export declare class MongoConnection extends Connection {
|
|
|
28
28
|
find<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, opts?: MongoFindOptions<T>): Promise<EntityData<T>[]>;
|
|
29
29
|
stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, opts?: MongoFindOptions<T>): AsyncIterableIterator<T>;
|
|
30
30
|
private _find;
|
|
31
|
-
insertOne<T extends object>(entityName: EntityName<T>, data: Partial<T>, ctx?: Transaction<ClientSession
|
|
32
|
-
insertMany<T extends object>(entityName: EntityName<T>, data: Partial<T>[], ctx?: Transaction<ClientSession
|
|
33
|
-
updateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: Partial<T>, ctx?: Transaction<ClientSession>, upsert?: boolean, upsertOptions?: UpsertOptions<T
|
|
34
|
-
bulkUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: Partial<T>[], ctx?: Transaction<ClientSession>, upsert?: boolean, upsertOptions?: UpsertManyOptions<T
|
|
35
|
-
deleteMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, ctx?: Transaction<ClientSession
|
|
36
|
-
aggregate<T extends object = any>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession>, loggerContext?: LoggingOptions): Promise<T[]>;
|
|
37
|
-
streamAggregate<T extends object>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession>, loggerContext?: LoggingOptions, stream?: boolean): AsyncIterableIterator<T>;
|
|
31
|
+
insertOne<T extends object>(entityName: EntityName<T>, data: Partial<T>, ctx?: Transaction<ClientSession>, signal?: AbortSignal): Promise<QueryResult<T>>;
|
|
32
|
+
insertMany<T extends object>(entityName: EntityName<T>, data: Partial<T>[], ctx?: Transaction<ClientSession>, signal?: AbortSignal): Promise<QueryResult<T>>;
|
|
33
|
+
updateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: Partial<T>, ctx?: Transaction<ClientSession>, upsert?: boolean, upsertOptions?: UpsertOptions<T>, signal?: AbortSignal): Promise<QueryResult<T>>;
|
|
34
|
+
bulkUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: Partial<T>[], ctx?: Transaction<ClientSession>, upsert?: boolean, upsertOptions?: UpsertManyOptions<T>, signal?: AbortSignal): Promise<QueryResult<T>>;
|
|
35
|
+
deleteMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, ctx?: Transaction<ClientSession>, signal?: AbortSignal): Promise<QueryResult<T>>;
|
|
36
|
+
aggregate<T extends object = any>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession>, loggerContext?: LoggingOptions, signal?: AbortSignal): Promise<T[]>;
|
|
37
|
+
streamAggregate<T extends object>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession>, loggerContext?: LoggingOptions, stream?: boolean, signal?: AbortSignal): AsyncIterableIterator<T>;
|
|
38
38
|
countDocuments<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, opts?: MongoCountOptions): Promise<number>;
|
|
39
39
|
transactional<T>(cb: (trx: Transaction<ClientSession>) => Promise<T>, options?: {
|
|
40
40
|
isolationLevel?: IsolationLevel;
|
|
@@ -61,6 +61,11 @@ export interface MongoQueryOptions {
|
|
|
61
61
|
indexHint?: string | Dictionary;
|
|
62
62
|
maxTimeMS?: number;
|
|
63
63
|
allowDiskUse?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Forwarded to the MongoDB driver. When fired, the driver aborts the in-flight operation
|
|
66
|
+
* by closing the underlying socket; the rejection reason is `signal.reason`.
|
|
67
|
+
*/
|
|
68
|
+
signal?: AbortSignal;
|
|
64
69
|
}
|
|
65
70
|
/** Options for MongoDB find operations. */
|
|
66
71
|
export interface MongoFindOptions<T extends object> extends MongoQueryOptions {
|
|
@@ -68,6 +73,7 @@ export interface MongoFindOptions<T extends object> extends MongoQueryOptions {
|
|
|
68
73
|
limit?: number;
|
|
69
74
|
offset?: number;
|
|
70
75
|
fields?: string[];
|
|
76
|
+
chunkSize?: number;
|
|
71
77
|
ctx?: Transaction<ClientSession>;
|
|
72
78
|
loggerContext?: LoggingOptions;
|
|
73
79
|
}
|
package/MongoConnection.js
CHANGED
|
@@ -143,6 +143,12 @@ export class MongoConnection extends Connection {
|
|
|
143
143
|
if (opts.allowDiskUse != null) {
|
|
144
144
|
options.allowDiskUse = opts.allowDiskUse;
|
|
145
145
|
}
|
|
146
|
+
if (opts.chunkSize != null) {
|
|
147
|
+
options.batchSize = opts.chunkSize;
|
|
148
|
+
}
|
|
149
|
+
if (opts.signal) {
|
|
150
|
+
options.signal = opts.signal;
|
|
151
|
+
}
|
|
146
152
|
const resultSet = this.getCollection(entityName).find(where, options);
|
|
147
153
|
let query = `db.getCollection('${collection}').find(${this.logObject(where)}, ${this.logObject(options)})`;
|
|
148
154
|
const orderBy = Utils.asArray(opts.orderBy);
|
|
@@ -174,37 +180,43 @@ export class MongoConnection extends Connection {
|
|
|
174
180
|
}
|
|
175
181
|
return { cursor: resultSet, query };
|
|
176
182
|
}
|
|
177
|
-
async insertOne(entityName, data, ctx) {
|
|
178
|
-
return this.runQuery('insertOne', entityName, data, undefined, ctx);
|
|
183
|
+
async insertOne(entityName, data, ctx, signal) {
|
|
184
|
+
return this.runQuery('insertOne', entityName, data, undefined, ctx, { signal });
|
|
179
185
|
}
|
|
180
|
-
async insertMany(entityName, data, ctx) {
|
|
181
|
-
return this.runQuery('insertMany', entityName, data, undefined, ctx);
|
|
186
|
+
async insertMany(entityName, data, ctx, signal) {
|
|
187
|
+
return this.runQuery('insertMany', entityName, data, undefined, ctx, { signal });
|
|
182
188
|
}
|
|
183
|
-
async updateMany(entityName, where, data, ctx, upsert, upsertOptions) {
|
|
184
|
-
return this.runQuery('updateMany', entityName, data, where, ctx, { upsert, upsertOptions });
|
|
189
|
+
async updateMany(entityName, where, data, ctx, upsert, upsertOptions, signal) {
|
|
190
|
+
return this.runQuery('updateMany', entityName, data, where, ctx, { upsert, upsertOptions, signal });
|
|
185
191
|
}
|
|
186
|
-
async bulkUpdateMany(entityName, where, data, ctx, upsert, upsertOptions) {
|
|
187
|
-
return this.runQuery('bulkUpdateMany', entityName, data, where, ctx, { upsert, upsertOptions });
|
|
192
|
+
async bulkUpdateMany(entityName, where, data, ctx, upsert, upsertOptions, signal) {
|
|
193
|
+
return this.runQuery('bulkUpdateMany', entityName, data, where, ctx, { upsert, upsertOptions, signal });
|
|
188
194
|
}
|
|
189
|
-
async deleteMany(entityName, where, ctx) {
|
|
190
|
-
return this.runQuery('deleteMany', entityName, undefined, where, ctx);
|
|
195
|
+
async deleteMany(entityName, where, ctx, signal) {
|
|
196
|
+
return this.runQuery('deleteMany', entityName, undefined, where, ctx, { signal });
|
|
191
197
|
}
|
|
192
|
-
async aggregate(entityName, pipeline, ctx, loggerContext) {
|
|
198
|
+
async aggregate(entityName, pipeline, ctx, loggerContext, signal) {
|
|
193
199
|
await this.ensureConnection();
|
|
194
200
|
const collection = this.getCollectionName(entityName);
|
|
195
201
|
/* v8 ignore next */
|
|
196
202
|
const options = ctx ? { session: ctx } : {};
|
|
203
|
+
if (signal) {
|
|
204
|
+
options.signal = signal;
|
|
205
|
+
}
|
|
197
206
|
const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)}).toArray();`;
|
|
198
207
|
const now = Date.now();
|
|
199
208
|
const res = await this.getCollection(entityName).aggregate(pipeline, options).toArray();
|
|
200
209
|
this.logQuery(query, { took: Date.now() - now, results: res.length, ...loggerContext });
|
|
201
210
|
return res;
|
|
202
211
|
}
|
|
203
|
-
async *streamAggregate(entityName, pipeline, ctx, loggerContext, stream = false) {
|
|
212
|
+
async *streamAggregate(entityName, pipeline, ctx, loggerContext, stream = false, signal) {
|
|
204
213
|
await this.ensureConnection();
|
|
205
214
|
const collection = this.getCollectionName(entityName);
|
|
206
215
|
/* v8 ignore next */
|
|
207
216
|
const options = ctx ? { session: ctx } : {};
|
|
217
|
+
if (signal) {
|
|
218
|
+
options.signal = signal;
|
|
219
|
+
}
|
|
208
220
|
const query = `db.getCollection('${collection}').aggregate(${this.logObject(pipeline)}, ${this.logObject(options)})};`;
|
|
209
221
|
const cursor = this.getCollection(entityName).aggregate(pipeline, options);
|
|
210
222
|
this.logQuery(query, { ...loggerContext });
|
|
@@ -216,6 +228,7 @@ export class MongoConnection extends Connection {
|
|
|
216
228
|
collation: opts.collation,
|
|
217
229
|
indexHint: opts.indexHint,
|
|
218
230
|
maxTimeMS: opts.maxTimeMS,
|
|
231
|
+
signal: opts.signal,
|
|
219
232
|
});
|
|
220
233
|
}
|
|
221
234
|
async transactional(cb, options = {}) {
|
|
@@ -262,13 +275,16 @@ export class MongoConnection extends Connection {
|
|
|
262
275
|
}
|
|
263
276
|
async runQuery(method, entityName, data, where, ctx, opts) {
|
|
264
277
|
await this.ensureConnection();
|
|
265
|
-
const { upsert, upsertOptions, loggerContext, collation, indexHint, maxTimeMS } = opts ?? {};
|
|
278
|
+
const { upsert, upsertOptions, loggerContext, collation, indexHint, maxTimeMS, signal } = opts ?? {};
|
|
266
279
|
const collection = this.getCollectionName(entityName);
|
|
267
280
|
const logger = this.config.getLogger();
|
|
268
281
|
const options = ctx ? { session: ctx, upsert } : { upsert };
|
|
269
282
|
if (options.upsert === undefined) {
|
|
270
283
|
delete options.upsert;
|
|
271
284
|
}
|
|
285
|
+
if (signal) {
|
|
286
|
+
options.signal = signal;
|
|
287
|
+
}
|
|
272
288
|
const now = Date.now();
|
|
273
289
|
let res;
|
|
274
290
|
let query;
|
package/MongoDriver.d.ts
CHANGED
|
@@ -15,22 +15,26 @@ export declare class MongoDriver extends DatabaseDriver<MongoConnection> {
|
|
|
15
15
|
rawResults?: boolean;
|
|
16
16
|
}): AsyncIterableIterator<T>;
|
|
17
17
|
find<T extends object, P extends string = never, F extends string = never, E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
|
|
18
|
+
private findWithPartitionLimit;
|
|
18
19
|
findOne<T extends object, P extends string = never, F extends string = never, E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
|
|
19
20
|
findVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
|
|
20
21
|
streamVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): AsyncIterableIterator<EntityData<T>>;
|
|
21
22
|
count<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: CountOptions<T>): Promise<number>;
|
|
22
23
|
nativeInsert<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
24
|
+
nativeClone<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, overrides?: EntityData<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
23
25
|
nativeInsertMany<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
|
|
24
26
|
nativeUpdate<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T> & UpsertOptions<T>): Promise<QueryResult<T>>;
|
|
25
27
|
nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateOptions<T> & UpsertManyOptions<T>): Promise<QueryResult<T>>;
|
|
26
28
|
nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: {
|
|
27
29
|
ctx?: Transaction<ClientSession>;
|
|
30
|
+
signal?: AbortSignal;
|
|
28
31
|
}): Promise<QueryResult<T>>;
|
|
29
|
-
aggregate(entityName: EntityName, pipeline: any[], ctx?: Transaction<ClientSession
|
|
30
|
-
streamAggregate<T extends object>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession
|
|
32
|
+
aggregate(entityName: EntityName, pipeline: any[], ctx?: Transaction<ClientSession>, signal?: AbortSignal): Promise<any[]>;
|
|
33
|
+
streamAggregate<T extends object>(entityName: EntityName<T>, pipeline: any[], ctx?: Transaction<ClientSession>, signal?: AbortSignal): AsyncIterableIterator<T>;
|
|
31
34
|
getPlatform(): MongoPlatform;
|
|
32
35
|
private buildQueryOptions;
|
|
33
|
-
|
|
36
|
+
/** @internal */
|
|
37
|
+
renameFields<T extends object>(entityName: EntityName<T>, data: T, dotPaths?: boolean, object?: boolean, root?: boolean): T;
|
|
34
38
|
private convertObjectIds;
|
|
35
39
|
private buildFilterById;
|
|
36
40
|
protected buildFields<T extends object, P extends string = never>(entityName: EntityName<T>, populate: PopulateOptions<T>[], fields?: readonly EntityField<T, P>[], exclude?: string[]): string[] | undefined;
|
package/MongoDriver.js
CHANGED
|
@@ -30,6 +30,7 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
30
30
|
offset: options.offset,
|
|
31
31
|
fields,
|
|
32
32
|
ctx: options.ctx,
|
|
33
|
+
chunkSize: options.chunkSize ?? 100,
|
|
33
34
|
...this.buildQueryOptions(options),
|
|
34
35
|
});
|
|
35
36
|
for await (const item of res) {
|
|
@@ -78,6 +79,10 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
78
79
|
return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
|
|
79
80
|
}
|
|
80
81
|
const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
|
|
82
|
+
const partitionLimit = options._partitionLimit;
|
|
83
|
+
if (partitionLimit) {
|
|
84
|
+
return this.findWithPartitionLimit(entityName, where, orderBy, fields ?? [], partitionLimit, options);
|
|
85
|
+
}
|
|
81
86
|
const res = await this.rethrow(this.getConnection('read').find(entityName, where, {
|
|
82
87
|
orderBy,
|
|
83
88
|
limit: options.limit,
|
|
@@ -88,6 +93,48 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
88
93
|
}));
|
|
89
94
|
return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
|
|
90
95
|
}
|
|
96
|
+
async findWithPartitionLimit(entityName, where, orderBy, fields, partitionLimit, options) {
|
|
97
|
+
const meta = this.metadata.find(entityName);
|
|
98
|
+
const { limit, offset = 0 } = partitionLimit;
|
|
99
|
+
// Resolve the partition property to its DB field name; callers always pass
|
|
100
|
+
// a declared property on the target meta (FK of the owning side).
|
|
101
|
+
const prop = meta.properties[partitionLimit.partitionBy];
|
|
102
|
+
const partitionField = prop.fieldNames[0];
|
|
103
|
+
const pipeline = [];
|
|
104
|
+
pipeline.push({ $match: where });
|
|
105
|
+
if (orderBy.length > 0) {
|
|
106
|
+
const sortSpec = {};
|
|
107
|
+
for (const order of orderBy) {
|
|
108
|
+
for (const [key, dir] of Object.entries(order)) {
|
|
109
|
+
sortSpec[key] = dir === 'ASC' || dir === 'asc' || dir === 1 ? 1 : -1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
pipeline.push({ $sort: sortSpec });
|
|
113
|
+
}
|
|
114
|
+
// $sort before $group ensures $push collects in correct order
|
|
115
|
+
pipeline.push({
|
|
116
|
+
$group: {
|
|
117
|
+
_id: `$${partitionField}`,
|
|
118
|
+
__docs: { $push: '$$ROOT' },
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
pipeline.push({
|
|
122
|
+
$project: {
|
|
123
|
+
__docs: { $slice: ['$__docs', offset, limit] },
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
pipeline.push({ $unwind: '$__docs' });
|
|
127
|
+
pipeline.push({ $replaceRoot: { newRoot: '$__docs' } });
|
|
128
|
+
if (fields.length > 0) {
|
|
129
|
+
const projection = {};
|
|
130
|
+
for (const field of fields) {
|
|
131
|
+
projection[field] = 1;
|
|
132
|
+
}
|
|
133
|
+
pipeline.push({ $project: projection });
|
|
134
|
+
}
|
|
135
|
+
const res = await this.rethrow(this.getConnection('read').aggregate(entityName, pipeline, options.ctx, options.logging, options.signal));
|
|
136
|
+
return res.map(r => this.mapResult(r, meta));
|
|
137
|
+
}
|
|
91
138
|
async findOne(entityName, where, options = { populate: [], orderBy: {} }) {
|
|
92
139
|
if (this.metadata.find(entityName)?.virtual) {
|
|
93
140
|
const [item] = await this.findVirtual(entityName, where, options);
|
|
@@ -146,7 +193,28 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
146
193
|
async nativeInsert(entityName, data, options = {}) {
|
|
147
194
|
this.handleVersionProperty(entityName, data);
|
|
148
195
|
data = this.renameFields(entityName, data);
|
|
149
|
-
return this.rethrow(this.getConnection('write').insertOne(entityName, data, options.ctx));
|
|
196
|
+
return this.rethrow(this.getConnection('write').insertOne(entityName, data, options.ctx, options.signal));
|
|
197
|
+
}
|
|
198
|
+
async nativeClone(entityName, where, overrides, options = {}) {
|
|
199
|
+
const meta = this.metadata.find(entityName);
|
|
200
|
+
const pk = meta.getPrimaryProps()[0].fieldNames[0] ?? '_id';
|
|
201
|
+
const normalizedWhere = Utils.isPrimaryKey(where) ? { [pk]: where } : where;
|
|
202
|
+
const renameWhere = this.renameFields(entityName, normalizedWhere);
|
|
203
|
+
const source = await this.rethrow(this.getConnection('read').find(entityName, renameWhere, { ctx: options.ctx, limit: 1 }));
|
|
204
|
+
if (!source.length) {
|
|
205
|
+
throw new Error('Cannot clone: no entity found matching the given condition');
|
|
206
|
+
}
|
|
207
|
+
const doc = source[0];
|
|
208
|
+
delete doc[pk];
|
|
209
|
+
if (overrides) {
|
|
210
|
+
const mapped = this.renameFields(entityName, overrides);
|
|
211
|
+
Object.assign(doc, mapped);
|
|
212
|
+
}
|
|
213
|
+
if (meta.versionProperty) {
|
|
214
|
+
const vProp = meta.properties[meta.versionProperty];
|
|
215
|
+
doc[vProp.fieldNames[0]] = vProp.runtimeType === 'Date' ? new Date() : 1;
|
|
216
|
+
}
|
|
217
|
+
return this.rethrow(this.getConnection('write').insertOne(entityName, doc, options.ctx));
|
|
150
218
|
}
|
|
151
219
|
async nativeInsertMany(entityName, data, options = {}) {
|
|
152
220
|
data = data.map(item => {
|
|
@@ -156,7 +224,7 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
156
224
|
const meta = this.metadata.find(entityName);
|
|
157
225
|
/* v8 ignore next */
|
|
158
226
|
const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
|
|
159
|
-
const res = await this.rethrow(this.getConnection('write').insertMany(entityName, data, options.ctx));
|
|
227
|
+
const res = await this.rethrow(this.getConnection('write').insertMany(entityName, data, options.ctx, options.signal));
|
|
160
228
|
res.rows = res.insertedIds.map(id => ({ [pk]: id }));
|
|
161
229
|
return res;
|
|
162
230
|
}
|
|
@@ -180,7 +248,7 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
180
248
|
if (options.onConflictExcludeFields) {
|
|
181
249
|
options.onConflictExcludeFields = options.onConflictExcludeFields.map(rename);
|
|
182
250
|
}
|
|
183
|
-
return this.rethrow(this.getConnection('write').updateMany(entityName, where, data, options.ctx, options.upsert, options));
|
|
251
|
+
return this.rethrow(this.getConnection('write').updateMany(entityName, where, data, options.ctx, options.upsert, options, options.signal));
|
|
184
252
|
}
|
|
185
253
|
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
186
254
|
where = where.map(row => {
|
|
@@ -208,7 +276,7 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
208
276
|
}
|
|
209
277
|
/* v8 ignore next */
|
|
210
278
|
const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
|
|
211
|
-
const res = await this.rethrow(this.getConnection('write').bulkUpdateMany(entityName, where, data, options.ctx, options.upsert, options));
|
|
279
|
+
const res = await this.rethrow(this.getConnection('write').bulkUpdateMany(entityName, where, data, options.ctx, options.upsert, options, options.signal));
|
|
212
280
|
if (res.insertedIds) {
|
|
213
281
|
let i = 0;
|
|
214
282
|
res.rows = where.map(cond => {
|
|
@@ -225,13 +293,13 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
225
293
|
where = this.buildFilterById(entityName, where);
|
|
226
294
|
}
|
|
227
295
|
where = this.renameFields(entityName, where, true);
|
|
228
|
-
return this.rethrow(this.getConnection('write').deleteMany(entityName, where, options.ctx));
|
|
296
|
+
return this.rethrow(this.getConnection('write').deleteMany(entityName, where, options.ctx, options.signal));
|
|
229
297
|
}
|
|
230
|
-
async aggregate(entityName, pipeline, ctx) {
|
|
231
|
-
return this.rethrow(this.getConnection('read').aggregate(entityName, pipeline, ctx));
|
|
298
|
+
async aggregate(entityName, pipeline, ctx, signal) {
|
|
299
|
+
return this.rethrow(this.getConnection('read').aggregate(entityName, pipeline, ctx, undefined, signal));
|
|
232
300
|
}
|
|
233
|
-
async *streamAggregate(entityName, pipeline, ctx) {
|
|
234
|
-
yield* this.getConnection('read').streamAggregate(entityName, pipeline, ctx);
|
|
301
|
+
async *streamAggregate(entityName, pipeline, ctx, signal) {
|
|
302
|
+
yield* this.getConnection('read').streamAggregate(entityName, pipeline, ctx, undefined, false, signal);
|
|
235
303
|
}
|
|
236
304
|
getPlatform() {
|
|
237
305
|
return this.platform;
|
|
@@ -247,14 +315,25 @@ export class MongoDriver extends DatabaseDriver {
|
|
|
247
315
|
if (options.indexHint != null) {
|
|
248
316
|
ret.indexHint = options.indexHint;
|
|
249
317
|
}
|
|
318
|
+
else if (options.using != null) {
|
|
319
|
+
const names = Utils.asArray(options.using);
|
|
320
|
+
if (names.length > 1) {
|
|
321
|
+
throw new Error('MongoDB only supports a single index hint per query. Provide one index name instead of an array.');
|
|
322
|
+
}
|
|
323
|
+
ret.indexHint = names[0];
|
|
324
|
+
}
|
|
250
325
|
if (options.maxTimeMS != null) {
|
|
251
326
|
ret.maxTimeMS = options.maxTimeMS;
|
|
252
327
|
}
|
|
253
328
|
if (options.allowDiskUse != null) {
|
|
254
329
|
ret.allowDiskUse = options.allowDiskUse;
|
|
255
330
|
}
|
|
331
|
+
if (options.signal) {
|
|
332
|
+
ret.signal = options.signal;
|
|
333
|
+
}
|
|
256
334
|
return ret;
|
|
257
335
|
}
|
|
336
|
+
/** @internal */
|
|
258
337
|
renameFields(entityName, data, dotPaths = false, object, root = true) {
|
|
259
338
|
if (data == null && root) {
|
|
260
339
|
return {};
|
package/MongoEntityManager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityManager, type EntityName, type EntityRepository, type GetRepository, type
|
|
1
|
+
import { EntityManager, type CountByOptions, type Dictionary, type EntityKey, type EntityName, type EntityRepository, type GetRepository, type Loaded, type StreamOptions, type TransactionOptions, type WithUsingOptions } from '@mikro-orm/core';
|
|
2
2
|
import type { Collection, Document, TransactionOptions as MongoTransactionOptions } from 'mongodb';
|
|
3
3
|
import type { MongoDriver } from './MongoDriver.js';
|
|
4
4
|
import type { MongoEntityRepository } from './MongoEntityRepository.js';
|
|
@@ -17,8 +17,12 @@ export declare class MongoEntityManager<Driver extends MongoDriver = MongoDriver
|
|
|
17
17
|
/**
|
|
18
18
|
* @inheritDoc
|
|
19
19
|
*/
|
|
20
|
-
stream<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entityName: EntityName<Entity>, options?: StreamOptions<NoInfer<Entity>, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
20
|
+
stream<Entity extends object, Hint extends string = never, Fields extends string = never, Excludes extends string = never, Using extends string = never>(entityName: EntityName<Entity>, options?: WithUsingOptions<StreamOptions<NoInfer<Entity>, Hint, Fields, Excludes>, NoInfer<Entity>, Using>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
21
21
|
getCollection<T extends Document>(entityOrCollectionName: EntityName<T> | string): Collection<T>;
|
|
22
|
+
/**
|
|
23
|
+
* @inheritDoc
|
|
24
|
+
*/
|
|
25
|
+
countBy<Entity extends object>(entityName: EntityName<Entity>, groupBy: EntityKey<Entity> | readonly EntityKey<Entity>[], options?: CountByOptions<Entity>): Promise<Dictionary<number>>;
|
|
22
26
|
/**
|
|
23
27
|
* @inheritDoc
|
|
24
28
|
*/
|
package/MongoEntityManager.js
CHANGED
|
@@ -7,13 +7,13 @@ export class MongoEntityManager extends EntityManager {
|
|
|
7
7
|
* Shortcut to driver's aggregate method. Available in MongoDriver only.
|
|
8
8
|
*/
|
|
9
9
|
async aggregate(entityName, pipeline) {
|
|
10
|
-
return this.getDriver().aggregate(entityName, pipeline, this.getTransactionContext());
|
|
10
|
+
return this.getDriver().aggregate(entityName, pipeline, this.getTransactionContext(), this.signal);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Shortcut to driver's aggregate method. Returns a stream. Available in MongoDriver only.
|
|
14
14
|
*/
|
|
15
15
|
async *streamAggregate(entityName, pipeline) {
|
|
16
|
-
yield* this.getDriver().streamAggregate(entityName, pipeline, this.getTransactionContext());
|
|
16
|
+
yield* this.getDriver().streamAggregate(entityName, pipeline, this.getTransactionContext(), this.signal);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* @inheritDoc
|
|
@@ -27,6 +27,37 @@ export class MongoEntityManager extends EntityManager {
|
|
|
27
27
|
getCollection(entityOrCollectionName) {
|
|
28
28
|
return this.getConnection().getCollection(entityOrCollectionName);
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* @inheritDoc
|
|
32
|
+
*/
|
|
33
|
+
async countBy(entityName, groupBy, options = {}) {
|
|
34
|
+
const em = this.getContext(false);
|
|
35
|
+
options = { ...options };
|
|
36
|
+
em.prepareOptions(options);
|
|
37
|
+
const meta = em.getMetadata().find(entityName);
|
|
38
|
+
const fields = Utils.asArray(groupBy);
|
|
39
|
+
if (options.having) {
|
|
40
|
+
throw new Error('The `having` option is not supported for MongoDB in `countBy`.');
|
|
41
|
+
}
|
|
42
|
+
await em.tryFlush(entityName, options);
|
|
43
|
+
const rawWhere = options.where;
|
|
44
|
+
const where = await em.processWhere(entityName, rawWhere ?? {}, options, 'read');
|
|
45
|
+
const renamedWhere = em.getDriver().renameFields(meta.class, where, true);
|
|
46
|
+
const fieldNames = fields.map(f => meta.properties[f]?.fieldNames?.[0] ?? f);
|
|
47
|
+
const groupId = fieldNames.length === 1 ? `$${fieldNames[0]}` : Object.fromEntries(fieldNames.map(f => [f, `$${f}`]));
|
|
48
|
+
const pipeline = [];
|
|
49
|
+
if (renamedWhere && Object.keys(renamedWhere).length > 0) {
|
|
50
|
+
pipeline.push({ $match: renamedWhere });
|
|
51
|
+
}
|
|
52
|
+
pipeline.push({ $group: { _id: groupId, count: { $sum: 1 } } });
|
|
53
|
+
const rows = await em.getDriver().aggregate(meta.class, pipeline, em.getTransactionContext(), em.signal);
|
|
54
|
+
const results = {};
|
|
55
|
+
for (const row of rows) {
|
|
56
|
+
const key = fieldNames.length === 1 ? String(row._id) : fieldNames.map(f => String(row._id[f])).join(Utils.PK_SEPARATOR);
|
|
57
|
+
results[key] = +row.count;
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
30
61
|
/**
|
|
31
62
|
* @inheritDoc
|
|
32
63
|
*/
|
package/MongoMikroORM.d.ts
CHANGED
|
@@ -2,17 +2,17 @@ import { type AnyEntity, type EntityClass, type EntitySchema, MikroORM, type Opt
|
|
|
2
2
|
import { MongoDriver } from './MongoDriver.js';
|
|
3
3
|
import type { MongoEntityManager } from './MongoEntityManager.js';
|
|
4
4
|
/** Configuration options for the MongoDB driver. */
|
|
5
|
-
export type MongoOptions<EM extends MongoEntityManager = MongoEntityManager, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<MongoDriver, EM, Entities>>;
|
|
5
|
+
export type MongoOptions<EM extends MongoEntityManager = MongoEntityManager, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> = Partial<Options<MongoDriver, EM, Entities>>;
|
|
6
6
|
/** Creates a type-safe configuration object for the MongoDB driver. */
|
|
7
|
-
export declare function defineMongoConfig<EM extends MongoEntityManager = MongoEntityManager, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: MongoOptions<EM, Entities>): MongoOptions<EM, Entities>;
|
|
7
|
+
export declare function defineMongoConfig<EM extends MongoEntityManager = MongoEntityManager, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: MongoOptions<EM, Entities>): MongoOptions<EM, Entities>;
|
|
8
8
|
/**
|
|
9
9
|
* @inheritDoc
|
|
10
10
|
*/
|
|
11
|
-
export declare class MongoMikroORM<EM extends MongoEntityManager = MongoEntityManager, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends MikroORM<MongoDriver, EM, Entities> {
|
|
11
|
+
export declare class MongoMikroORM<EM extends MongoEntityManager = MongoEntityManager, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]> extends MikroORM<MongoDriver, EM, Entities> {
|
|
12
12
|
/**
|
|
13
13
|
* @inheritDoc
|
|
14
14
|
*/
|
|
15
|
-
static init<D extends IDatabaseDriver = MongoDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
|
|
15
|
+
static init<D extends IDatabaseDriver = MongoDriver, EM extends EntityManager<D> = D[typeof EntityManagerType] & EntityManager<D>, Entities extends readonly (string | EntityClass<AnyEntity> | EntitySchema)[] = (string | EntityClass<AnyEntity> | EntitySchema)[]>(options: Partial<Options<D, EM, Entities>>): Promise<MikroORM<D, EM, Entities>>;
|
|
16
16
|
/**
|
|
17
17
|
* @inheritDoc
|
|
18
18
|
*/
|
package/MongoPlatform.js
CHANGED
|
@@ -73,9 +73,13 @@ export class MongoPlatform extends Platform {
|
|
|
73
73
|
return prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner;
|
|
74
74
|
}
|
|
75
75
|
validateMetadata(meta) {
|
|
76
|
+
super.validateMetadata(meta);
|
|
76
77
|
if (meta.inheritanceType === 'tpt') {
|
|
77
78
|
throw MetadataError.tptNotSupportedByDriver(meta);
|
|
78
79
|
}
|
|
80
|
+
if (meta.triggers?.length > 0) {
|
|
81
|
+
throw MetadataError.triggersNotSupportedByDriver(meta);
|
|
82
|
+
}
|
|
79
83
|
const pk = meta.getPrimaryProps()[0];
|
|
80
84
|
if (pk && pk.fieldNames?.[0] !== '_id') {
|
|
81
85
|
throw MetadataError.invalidPrimaryKey(meta, pk, '_id');
|
|
@@ -21,6 +21,11 @@ export declare class MongoSchemaGenerator extends AbstractSchemaGenerator<MongoD
|
|
|
21
21
|
ensureIndexes(options?: EnsureIndexesOptions): Promise<void>;
|
|
22
22
|
private mapIndexProperties;
|
|
23
23
|
private createIndexes;
|
|
24
|
+
/**
|
|
25
|
+
* An explicit `options.partialFilterExpression` wins over `where` — this preserves the
|
|
26
|
+
* long-standing `options: { partialFilterExpression }` escape hatch.
|
|
27
|
+
*/
|
|
28
|
+
private applyPartialFilter;
|
|
24
29
|
private executeQuery;
|
|
25
30
|
private createUniqueIndexes;
|
|
26
31
|
private createPropertyIndexes;
|
package/MongoSchemaGenerator.js
CHANGED
|
@@ -20,10 +20,13 @@ export class MongoSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
20
20
|
throw err;
|
|
21
21
|
}
|
|
22
22
|
}));
|
|
23
|
+
// Collections must exist before we touch indexes. `createIndex` on a missing collection
|
|
24
|
+
// races with the explicit `createCollection` above, and on retries the recovery path can
|
|
25
|
+
// see partial state and drop indexes that were already created successfully.
|
|
26
|
+
await Promise.all(promises);
|
|
23
27
|
if (options.ensureIndexes) {
|
|
24
28
|
await this.ensureIndexes({ ensureCollections: false });
|
|
25
29
|
}
|
|
26
|
-
await Promise.all(promises);
|
|
27
30
|
}
|
|
28
31
|
async drop(options = {}) {
|
|
29
32
|
await this.connection.ensureConnection();
|
|
@@ -132,10 +135,21 @@ export class MongoSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
132
135
|
const properties = this.mapIndexProperties(index, meta);
|
|
133
136
|
const collection = this.connection.getCollection(meta.class);
|
|
134
137
|
if (Array.isArray(index.options) && index.options.length === 2 && properties.length === 0) {
|
|
135
|
-
|
|
138
|
+
// The array-form escape hatch passes raw [spec, options] to the driver; fold `where`
|
|
139
|
+
// into the options dict (on a clone, to avoid mutating the user's metadata).
|
|
140
|
+
const opts = { ...index.options[1] };
|
|
141
|
+
this.applyPartialFilter(opts, index.where, index.name, meta.className);
|
|
142
|
+
res.push([collection.collectionName, collection.createIndex(index.options[0], opts)]);
|
|
136
143
|
return;
|
|
137
144
|
}
|
|
138
145
|
if (index.options && properties.length === 0) {
|
|
146
|
+
// The plain escape hatch forwards `index.options` as the sole argument; there is
|
|
147
|
+
// no options slot for `partialFilterExpression`, so warn instead of silently dropping.
|
|
148
|
+
if (index.where != null) {
|
|
149
|
+
this.config
|
|
150
|
+
.getLogger()
|
|
151
|
+
.warn('schema', `Index '${index.name ?? '(unnamed)'}' on entity '${meta.className}': \`where\` was ignored because \`options\` is used as the raw index spec and leaves no slot for \`partialFilterExpression\` — pass \`options: [spec, { partialFilterExpression }]\` (array form) to combine both.`);
|
|
152
|
+
}
|
|
139
153
|
res.push([collection.collectionName, collection.createIndex(index.options)]);
|
|
140
154
|
return;
|
|
141
155
|
}
|
|
@@ -162,10 +176,30 @@ export class MongoSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
162
176
|
if (index.invisible) {
|
|
163
177
|
indexOptions.hidden = true;
|
|
164
178
|
}
|
|
179
|
+
this.applyPartialFilter(indexOptions, index.where, index.name, meta.className);
|
|
165
180
|
res.push([collection.collectionName, this.executeQuery(collection, 'createIndex', fieldOrSpec, indexOptions)]);
|
|
166
181
|
});
|
|
167
182
|
return res;
|
|
168
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* An explicit `options.partialFilterExpression` wins over `where` — this preserves the
|
|
186
|
+
* long-standing `options: { partialFilterExpression }` escape hatch.
|
|
187
|
+
*/
|
|
188
|
+
applyPartialFilter(options, where, indexName, entityName) {
|
|
189
|
+
if (where == null) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (options.partialFilterExpression != null) {
|
|
193
|
+
this.config
|
|
194
|
+
.getLogger()
|
|
195
|
+
.warn('schema', `Index '${indexName ?? '(unnamed)'}' on entity '${entityName}': both \`where\` and \`options.partialFilterExpression\` are set; \`options.partialFilterExpression\` wins.`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (typeof where === 'string') {
|
|
199
|
+
throw new Error(`Index '${indexName ?? '(unnamed)'}' on entity '${entityName}': string \`where\` is not supported on MongoDB; pass an object/FilterQuery (it maps to MongoDB's \`partialFilterExpression\`).`);
|
|
200
|
+
}
|
|
201
|
+
options.partialFilterExpression = where;
|
|
202
|
+
}
|
|
169
203
|
async executeQuery(collection, method, ...args) {
|
|
170
204
|
const now = Date.now();
|
|
171
205
|
return collection[method](...args).then((res) => {
|
|
@@ -188,14 +222,13 @@ export class MongoSchemaGenerator extends AbstractSchemaGenerator {
|
|
|
188
222
|
return o;
|
|
189
223
|
}, {});
|
|
190
224
|
const collection = this.connection.getCollection(meta.class);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
]);
|
|
225
|
+
const indexOptions = {
|
|
226
|
+
name: index.name,
|
|
227
|
+
unique: true,
|
|
228
|
+
...index.options,
|
|
229
|
+
};
|
|
230
|
+
this.applyPartialFilter(indexOptions, index.where, index.name, meta.className);
|
|
231
|
+
res.push([collection.collectionName, this.executeQuery(collection, 'createIndex', fieldOrSpec, indexOptions)]);
|
|
199
232
|
});
|
|
200
233
|
return res;
|
|
201
234
|
}
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL (including CockroachDB and PGlite), SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ Install a driver package for your database:
|
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install @mikro-orm/postgresql # PostgreSQL
|
|
22
|
+
npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
|
|
22
23
|
npm install @mikro-orm/mysql # MySQL
|
|
23
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
24
25
|
npm install @mikro-orm/sqlite # SQLite
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/mongodb",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.18-dev.1",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@mikro-orm/core": "^7.0.17"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.
|
|
56
|
+
"@mikro-orm/core": "7.0.18-dev.1"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|