@mikro-orm/mongodb 7.0.2-dev.8 → 7.0.2
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 +147 -62
- package/MongoConnection.js +426 -420
- package/MongoDriver.d.ts +106 -32
- package/MongoDriver.js +432 -421
- package/MongoEntityManager.d.ts +44 -26
- package/MongoEntityManager.js +42 -42
- package/MongoEntityRepository.d.ts +12 -11
- package/MongoEntityRepository.js +21 -20
- package/MongoExceptionConverter.d.ts +5 -4
- package/MongoExceptionConverter.js +14 -13
- package/MongoMikroORM.d.ts +57 -16
- package/MongoMikroORM.js +23 -22
- package/MongoPlatform.d.ts +40 -24
- package/MongoPlatform.js +84 -83
- package/MongoSchemaGenerator.d.ts +26 -26
- package/MongoSchemaGenerator.js +214 -209
- package/README.md +128 -294
- package/index.d.ts +5 -1
- package/index.js +1 -1
- package/package.json +3 -3
package/MongoSchemaGenerator.js
CHANGED
|
@@ -1,223 +1,228 @@
|
|
|
1
|
-
import { Utils, inspect
|
|
1
|
+
import { Utils, inspect } from '@mikro-orm/core';
|
|
2
2
|
import { AbstractSchemaGenerator } from '@mikro-orm/core/schema';
|
|
3
|
+
/** Schema generator for MongoDB that manages collections and indexes. */
|
|
3
4
|
export class MongoSchemaGenerator extends AbstractSchemaGenerator {
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
static register(orm) {
|
|
6
|
+
orm.config.registerExtension('@mikro-orm/schema-generator', () => new MongoSchemaGenerator(orm.em));
|
|
7
|
+
}
|
|
8
|
+
async create(options = {}) {
|
|
9
|
+
await this.connection.ensureConnection();
|
|
10
|
+
options.ensureIndexes ??= true;
|
|
11
|
+
const existing = await this.connection.listCollections();
|
|
12
|
+
const metadata = this.getOrderedMetadata();
|
|
13
|
+
/* v8 ignore next */
|
|
14
|
+
const promises = metadata
|
|
15
|
+
.filter(meta => !existing.includes(meta.collection))
|
|
16
|
+
.map(meta =>
|
|
17
|
+
this.connection.createCollection(meta.class).catch(err => {
|
|
18
|
+
const existsErrorMessage = `Collection ${this.config.get('dbName')}.${meta.collection} already exists.`;
|
|
19
|
+
// ignore errors about the collection already existing
|
|
20
|
+
if (!(err.name === 'MongoServerError' && err.message.includes(existsErrorMessage))) {
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
if (options.ensureIndexes) {
|
|
26
|
+
await this.ensureIndexes({ ensureCollections: false });
|
|
6
27
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.map(meta => this.connection.createCollection(meta.class).catch(err => {
|
|
16
|
-
const existsErrorMessage = `Collection ${this.config.get('dbName')}.${meta.collection} already exists.`;
|
|
17
|
-
// ignore errors about the collection already existing
|
|
18
|
-
if (!(err.name === 'MongoServerError' && err.message.includes(existsErrorMessage))) {
|
|
19
|
-
throw err;
|
|
20
|
-
}
|
|
21
|
-
}));
|
|
22
|
-
if (options.ensureIndexes) {
|
|
23
|
-
await this.ensureIndexes({ ensureCollections: false });
|
|
24
|
-
}
|
|
25
|
-
await Promise.all(promises);
|
|
28
|
+
await Promise.all(promises);
|
|
29
|
+
}
|
|
30
|
+
async drop(options = {}) {
|
|
31
|
+
await this.connection.ensureConnection();
|
|
32
|
+
const existing = await this.connection.listCollections();
|
|
33
|
+
const metadata = this.getOrderedMetadata();
|
|
34
|
+
if (options.dropMigrationsTable) {
|
|
35
|
+
metadata.push({ collection: this.config.get('migrations').tableName });
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
const promises = metadata
|
|
38
|
+
.filter(meta => existing.includes(meta.collection))
|
|
39
|
+
.map(meta => this.connection.dropCollection(meta.class));
|
|
40
|
+
await Promise.all(promises);
|
|
41
|
+
}
|
|
42
|
+
async update(options = {}) {
|
|
43
|
+
await this.create(options);
|
|
44
|
+
}
|
|
45
|
+
async ensureDatabase() {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
async refresh(options = {}) {
|
|
49
|
+
await this.ensureDatabase();
|
|
50
|
+
await this.drop();
|
|
51
|
+
await this.create(options);
|
|
52
|
+
}
|
|
53
|
+
async dropIndexes(options) {
|
|
54
|
+
await this.connection.ensureConnection();
|
|
55
|
+
const db = this.connection.getDb();
|
|
56
|
+
const collections = await db.listCollections().toArray();
|
|
57
|
+
const promises = [];
|
|
58
|
+
for (const collection of collections) {
|
|
59
|
+
if (options?.collectionsWithFailedIndexes && !options.collectionsWithFailedIndexes.includes(collection.name)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const indexes = await db.collection(collection.name).listIndexes().toArray();
|
|
63
|
+
for (const index of indexes) {
|
|
64
|
+
const isIdIndex = index.key._id === 1 && Utils.getObjectKeysSize(index.key) === 1;
|
|
65
|
+
/* v8 ignore next */
|
|
66
|
+
if (
|
|
67
|
+
!isIdIndex &&
|
|
68
|
+
!options?.skipIndexes?.find(idx => idx.collection === collection.name && idx.indexName === index.name)
|
|
69
|
+
) {
|
|
70
|
+
promises.push(this.executeQuery(db.collection(collection.name), 'dropIndex', index.name));
|
|
33
71
|
}
|
|
34
|
-
|
|
35
|
-
.filter(meta => existing.includes(meta.collection))
|
|
36
|
-
.map(meta => this.connection.dropCollection(meta.class));
|
|
37
|
-
await Promise.all(promises);
|
|
72
|
+
}
|
|
38
73
|
}
|
|
39
|
-
|
|
40
|
-
|
|
74
|
+
await Promise.all(promises);
|
|
75
|
+
}
|
|
76
|
+
async ensureIndexes(options = {}) {
|
|
77
|
+
await this.connection.ensureConnection();
|
|
78
|
+
options.ensureCollections ??= true;
|
|
79
|
+
options.retryLimit ??= 3;
|
|
80
|
+
if (options.ensureCollections) {
|
|
81
|
+
await this.create({ ensureIndexes: false });
|
|
41
82
|
}
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
const promises = [];
|
|
84
|
+
for (const meta of this.getOrderedMetadata()) {
|
|
85
|
+
if (Array.isArray(options?.retry) && !options.retry.includes(meta.collection)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
promises.push(...this.createIndexes(meta));
|
|
89
|
+
promises.push(...this.createUniqueIndexes(meta));
|
|
90
|
+
for (const prop of meta.props) {
|
|
91
|
+
promises.push(...this.createPropertyIndexes(meta, prop, 'index'));
|
|
92
|
+
promises.push(...this.createPropertyIndexes(meta, prop, 'unique'));
|
|
93
|
+
}
|
|
44
94
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const indexes = await db.collection(collection.name).listIndexes().toArray();
|
|
60
|
-
for (const index of indexes) {
|
|
61
|
-
const isIdIndex = index.key._id === 1 && Utils.getObjectKeysSize(index.key) === 1;
|
|
62
|
-
/* v8 ignore next */
|
|
63
|
-
if (!isIdIndex &&
|
|
64
|
-
!options?.skipIndexes?.find(idx => idx.collection === collection.name && idx.indexName === index.name)) {
|
|
65
|
-
promises.push(this.executeQuery(db.collection(collection.name), 'dropIndex', index.name));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
95
|
+
const res = await Promise.allSettled(promises.map(p => p[1]));
|
|
96
|
+
if (res.some(r => r.status === 'rejected') && options.retry !== false) {
|
|
97
|
+
const skipIndexes = [];
|
|
98
|
+
const collectionsWithFailedIndexes = [];
|
|
99
|
+
const errors = [];
|
|
100
|
+
for (let i = 0; i < res.length; i++) {
|
|
101
|
+
const r = res[i];
|
|
102
|
+
if (r.status === 'rejected') {
|
|
103
|
+
collectionsWithFailedIndexes.push(promises[i][0]);
|
|
104
|
+
errors.push(r.reason);
|
|
105
|
+
} else {
|
|
106
|
+
skipIndexes.push({ collection: promises[i][0], indexName: r.value });
|
|
68
107
|
}
|
|
69
|
-
|
|
108
|
+
}
|
|
109
|
+
await this.dropIndexes({ skipIndexes, collectionsWithFailedIndexes });
|
|
110
|
+
if (options.retryLimit === 0) {
|
|
111
|
+
const details = errors.map(e => e.message).join('\n');
|
|
112
|
+
const message = `Failed to create indexes on the following collections: ${collectionsWithFailedIndexes.join(', ')}\n${details}`;
|
|
113
|
+
throw new Error(message, { cause: errors });
|
|
114
|
+
}
|
|
115
|
+
await this.ensureIndexes({
|
|
116
|
+
retry: collectionsWithFailedIndexes,
|
|
117
|
+
retryLimit: options.retryLimit - 1,
|
|
118
|
+
});
|
|
70
119
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const meta of this.getOrderedMetadata()) {
|
|
80
|
-
if (Array.isArray(options?.retry) && !options.retry.includes(meta.collection)) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
promises.push(...this.createIndexes(meta));
|
|
84
|
-
promises.push(...this.createUniqueIndexes(meta));
|
|
85
|
-
for (const prop of meta.props) {
|
|
86
|
-
promises.push(...this.createPropertyIndexes(meta, prop, 'index'));
|
|
87
|
-
promises.push(...this.createPropertyIndexes(meta, prop, 'unique'));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const res = await Promise.allSettled(promises.map(p => p[1]));
|
|
91
|
-
if (res.some(r => r.status === 'rejected') && options.retry !== false) {
|
|
92
|
-
const skipIndexes = [];
|
|
93
|
-
const collectionsWithFailedIndexes = [];
|
|
94
|
-
const errors = [];
|
|
95
|
-
for (let i = 0; i < res.length; i++) {
|
|
96
|
-
const r = res[i];
|
|
97
|
-
if (r.status === 'rejected') {
|
|
98
|
-
collectionsWithFailedIndexes.push(promises[i][0]);
|
|
99
|
-
errors.push(r.reason);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
skipIndexes.push({ collection: promises[i][0], indexName: r.value });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
await this.dropIndexes({ skipIndexes, collectionsWithFailedIndexes });
|
|
106
|
-
if (options.retryLimit === 0) {
|
|
107
|
-
const details = errors.map(e => e.message).join('\n');
|
|
108
|
-
const message = `Failed to create indexes on the following collections: ${collectionsWithFailedIndexes.join(', ')}\n${details}`;
|
|
109
|
-
throw new Error(message, { cause: errors });
|
|
110
|
-
}
|
|
111
|
-
await this.ensureIndexes({
|
|
112
|
-
retry: collectionsWithFailedIndexes,
|
|
113
|
-
retryLimit: options.retryLimit - 1,
|
|
114
|
-
});
|
|
120
|
+
}
|
|
121
|
+
mapIndexProperties(index, meta) {
|
|
122
|
+
return Utils.flatten(
|
|
123
|
+
Utils.asArray(index.properties).map(propName => {
|
|
124
|
+
const rootPropName = propName.split('.')[0];
|
|
125
|
+
const prop = meta.properties[rootPropName];
|
|
126
|
+
if (propName.includes('.')) {
|
|
127
|
+
return [prop.fieldNames[0] + propName.substring(propName.indexOf('.'))];
|
|
115
128
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (index.options && properties.length === 0) {
|
|
138
|
-
res.push([collection.collectionName, collection.createIndex(index.options)]);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (index.type) {
|
|
142
|
-
if (index.type === 'fulltext') {
|
|
143
|
-
index.type = 'text';
|
|
144
|
-
}
|
|
145
|
-
const spec = {};
|
|
146
|
-
properties.forEach(prop => (spec[prop] = index.type));
|
|
147
|
-
fieldOrSpec = spec;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
fieldOrSpec = properties.reduce((o, i) => {
|
|
151
|
-
o[i] = 1;
|
|
152
|
-
return o;
|
|
153
|
-
}, {});
|
|
154
|
-
}
|
|
155
|
-
// MongoDB uses 'hidden' for invisible indexes
|
|
156
|
-
const indexOptions = {
|
|
157
|
-
name: index.name,
|
|
158
|
-
unique: false,
|
|
159
|
-
...index.options,
|
|
160
|
-
};
|
|
161
|
-
if (index.invisible) {
|
|
162
|
-
indexOptions.hidden = true;
|
|
163
|
-
}
|
|
164
|
-
res.push([collection.collectionName, this.executeQuery(collection, 'createIndex', fieldOrSpec, indexOptions)]);
|
|
165
|
-
});
|
|
166
|
-
return res;
|
|
167
|
-
}
|
|
168
|
-
async executeQuery(collection, method, ...args) {
|
|
169
|
-
const now = Date.now();
|
|
170
|
-
return collection[method](...args).then((res) => {
|
|
171
|
-
Utils.dropUndefinedProperties(args);
|
|
172
|
-
const query = `db.getCollection('${collection.collectionName}').${method}(${args.map(arg => inspect(arg)).join(', ')});`;
|
|
173
|
-
this.config.getLogger().logQuery({
|
|
174
|
-
level: 'info',
|
|
175
|
-
query,
|
|
176
|
-
took: Date.now() - now,
|
|
177
|
-
});
|
|
178
|
-
return res;
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
createUniqueIndexes(meta) {
|
|
182
|
-
const res = [];
|
|
183
|
-
meta.uniques.forEach(index => {
|
|
184
|
-
const properties = this.mapIndexProperties(index, meta);
|
|
185
|
-
const fieldOrSpec = properties.reduce((o, i) => {
|
|
186
|
-
o[i] = 1;
|
|
187
|
-
return o;
|
|
188
|
-
}, {});
|
|
189
|
-
const collection = this.connection.getCollection(meta.class);
|
|
190
|
-
res.push([
|
|
191
|
-
collection.collectionName,
|
|
192
|
-
this.executeQuery(collection, 'createIndex', fieldOrSpec, {
|
|
193
|
-
name: index.name,
|
|
194
|
-
unique: true,
|
|
195
|
-
...index.options,
|
|
196
|
-
}),
|
|
197
|
-
]);
|
|
198
|
-
});
|
|
199
|
-
return res;
|
|
200
|
-
}
|
|
201
|
-
createPropertyIndexes(meta, prop, type) {
|
|
202
|
-
if (!prop[type] || !meta.collection) {
|
|
203
|
-
return [];
|
|
129
|
+
return prop?.fieldNames ?? propName;
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
createIndexes(meta) {
|
|
134
|
+
const res = [];
|
|
135
|
+
meta.indexes.forEach(index => {
|
|
136
|
+
let fieldOrSpec;
|
|
137
|
+
const properties = this.mapIndexProperties(index, meta);
|
|
138
|
+
const collection = this.connection.getCollection(meta.class);
|
|
139
|
+
if (Array.isArray(index.options) && index.options.length === 2 && properties.length === 0) {
|
|
140
|
+
res.push([collection.collectionName, collection.createIndex(index.options[0], index.options[1])]);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (index.options && properties.length === 0) {
|
|
144
|
+
res.push([collection.collectionName, collection.createIndex(index.options)]);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (index.type) {
|
|
148
|
+
if (index.type === 'fulltext') {
|
|
149
|
+
index.type = 'text';
|
|
204
150
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
151
|
+
const spec = {};
|
|
152
|
+
properties.forEach(prop => (spec[prop] = index.type));
|
|
153
|
+
fieldOrSpec = spec;
|
|
154
|
+
} else {
|
|
155
|
+
fieldOrSpec = properties.reduce((o, i) => {
|
|
156
|
+
o[i] = 1;
|
|
157
|
+
return o;
|
|
158
|
+
}, {});
|
|
159
|
+
}
|
|
160
|
+
// MongoDB uses 'hidden' for invisible indexes
|
|
161
|
+
const indexOptions = {
|
|
162
|
+
name: index.name,
|
|
163
|
+
unique: false,
|
|
164
|
+
...index.options,
|
|
165
|
+
};
|
|
166
|
+
if (index.invisible) {
|
|
167
|
+
indexOptions.hidden = true;
|
|
168
|
+
}
|
|
169
|
+
res.push([collection.collectionName, this.executeQuery(collection, 'createIndex', fieldOrSpec, indexOptions)]);
|
|
170
|
+
});
|
|
171
|
+
return res;
|
|
172
|
+
}
|
|
173
|
+
async executeQuery(collection, method, ...args) {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
return collection[method](...args).then(res => {
|
|
176
|
+
Utils.dropUndefinedProperties(args);
|
|
177
|
+
const query = `db.getCollection('${collection.collectionName}').${method}(${args.map(arg => inspect(arg)).join(', ')});`;
|
|
178
|
+
this.config.getLogger().logQuery({
|
|
179
|
+
level: 'info',
|
|
180
|
+
query,
|
|
181
|
+
took: Date.now() - now,
|
|
182
|
+
});
|
|
183
|
+
return res;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
createUniqueIndexes(meta) {
|
|
187
|
+
const res = [];
|
|
188
|
+
meta.uniques.forEach(index => {
|
|
189
|
+
const properties = this.mapIndexProperties(index, meta);
|
|
190
|
+
const fieldOrSpec = properties.reduce((o, i) => {
|
|
191
|
+
o[i] = 1;
|
|
192
|
+
return o;
|
|
193
|
+
}, {});
|
|
194
|
+
const collection = this.connection.getCollection(meta.class);
|
|
195
|
+
res.push([
|
|
196
|
+
collection.collectionName,
|
|
197
|
+
this.executeQuery(collection, 'createIndex', fieldOrSpec, {
|
|
198
|
+
name: index.name,
|
|
199
|
+
unique: true,
|
|
200
|
+
...index.options,
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
return res;
|
|
205
|
+
}
|
|
206
|
+
createPropertyIndexes(meta, prop, type) {
|
|
207
|
+
if (!prop[type] || !meta.collection) {
|
|
208
|
+
return [];
|
|
222
209
|
}
|
|
210
|
+
const collection = this.connection.getCollection(meta.class);
|
|
211
|
+
const fieldOrSpec = prop.embeddedPath
|
|
212
|
+
? prop.embeddedPath.join('.')
|
|
213
|
+
: prop.fieldNames.reduce((o, i) => {
|
|
214
|
+
o[i] = 1;
|
|
215
|
+
return o;
|
|
216
|
+
}, {});
|
|
217
|
+
return [
|
|
218
|
+
[
|
|
219
|
+
collection.collectionName,
|
|
220
|
+
this.executeQuery(collection, 'createIndex', fieldOrSpec, {
|
|
221
|
+
name: typeof prop[type] === 'string' ? prop[type] : undefined,
|
|
222
|
+
unique: type === 'unique',
|
|
223
|
+
sparse: prop.nullable === true,
|
|
224
|
+
}),
|
|
225
|
+
],
|
|
226
|
+
];
|
|
227
|
+
}
|
|
223
228
|
}
|