@mikro-orm/mongodb 7.0.2-dev.9 → 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.
@@ -1,223 +1,228 @@
1
- import { Utils, inspect, } from '@mikro-orm/core';
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
- static register(orm) {
5
- orm.config.registerExtension('@mikro-orm/schema-generator', () => new MongoSchemaGenerator(orm.em));
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
- async create(options = {}) {
8
- await this.connection.ensureConnection();
9
- options.ensureIndexes ??= true;
10
- const existing = await this.connection.listCollections();
11
- const metadata = this.getOrderedMetadata();
12
- /* v8 ignore next */
13
- const promises = metadata
14
- .filter(meta => !existing.includes(meta.collection))
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
- async drop(options = {}) {
28
- await this.connection.ensureConnection();
29
- const existing = await this.connection.listCollections();
30
- const metadata = this.getOrderedMetadata();
31
- if (options.dropMigrationsTable) {
32
- metadata.push({ collection: this.config.get('migrations').tableName });
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
- const promises = metadata
35
- .filter(meta => existing.includes(meta.collection))
36
- .map(meta => this.connection.dropCollection(meta.class));
37
- await Promise.all(promises);
72
+ }
38
73
  }
39
- async update(options = {}) {
40
- await this.create(options);
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
- async ensureDatabase() {
43
- return false;
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
- async refresh(options = {}) {
46
- await this.ensureDatabase();
47
- await this.drop();
48
- await this.create(options);
49
- }
50
- async dropIndexes(options) {
51
- await this.connection.ensureConnection();
52
- const db = this.connection.getDb();
53
- const collections = await db.listCollections().toArray();
54
- const promises = [];
55
- for (const collection of collections) {
56
- if (options?.collectionsWithFailedIndexes && !options.collectionsWithFailedIndexes.includes(collection.name)) {
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
- await Promise.all(promises);
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
- async ensureIndexes(options = {}) {
72
- await this.connection.ensureConnection();
73
- options.ensureCollections ??= true;
74
- options.retryLimit ??= 3;
75
- if (options.ensureCollections) {
76
- await this.create({ ensureIndexes: false });
77
- }
78
- const promises = [];
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
- mapIndexProperties(index, meta) {
118
- return Utils.flatten(Utils.asArray(index.properties).map(propName => {
119
- const rootPropName = propName.split('.')[0];
120
- const prop = meta.properties[rootPropName];
121
- if (propName.includes('.')) {
122
- return [prop.fieldNames[0] + propName.substring(propName.indexOf('.'))];
123
- }
124
- return prop?.fieldNames ?? propName;
125
- }));
126
- }
127
- createIndexes(meta) {
128
- const res = [];
129
- meta.indexes.forEach(index => {
130
- let fieldOrSpec;
131
- const properties = this.mapIndexProperties(index, meta);
132
- const collection = this.connection.getCollection(meta.class);
133
- if (Array.isArray(index.options) && index.options.length === 2 && properties.length === 0) {
134
- res.push([collection.collectionName, collection.createIndex(index.options[0], index.options[1])]);
135
- return;
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 collection = this.connection.getCollection(meta.class);
206
- const fieldOrSpec = prop.embeddedPath
207
- ? prop.embeddedPath.join('.')
208
- : prop.fieldNames.reduce((o, i) => {
209
- o[i] = 1;
210
- return o;
211
- }, {});
212
- return [
213
- [
214
- collection.collectionName,
215
- this.executeQuery(collection, 'createIndex', fieldOrSpec, {
216
- name: typeof prop[type] === 'string' ? prop[type] : undefined,
217
- unique: type === 'unique',
218
- sparse: prop.nullable === true,
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
  }