@opra/mongodb 1.0.0-alpha.9 → 1.0.0-beta.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.
@@ -12,7 +12,7 @@ export var MongoAdapter;
12
12
  MongoAdapter.prepareSort = _prepareSort;
13
13
  async function parseRequest(context) {
14
14
  const { operation } = context;
15
- if (operation.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
15
+ if (operation?.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
16
16
  const controller = operation.owner;
17
17
  switch (operation.composition) {
18
18
  case 'Entity.Create': {
@@ -39,11 +39,11 @@ export var MongoAdapter;
39
39
  case 'Entity.FindMany': {
40
40
  const options = {
41
41
  filter: context.queryParams.filter,
42
- projection: context.queryParams.projection,
42
+ projection: context.queryParams.projection || operation.compositionOptions.defaultProjection,
43
43
  count: context.queryParams.count,
44
- limit: context.queryParams.limit,
44
+ limit: context.queryParams.limit || operation.compositionOptions.defaultLimit,
45
45
  skip: context.queryParams.skip,
46
- sort: context.queryParams.sort,
46
+ sort: context.queryParams.sort || operation.compositionOptions.defaultSort,
47
47
  };
48
48
  return { method: 'findMany', options };
49
49
  }
@@ -1,5 +1,5 @@
1
1
  import { ResourceNotAvailableError } from '@opra/common';
2
- import { ObjectId } from 'mongodb';
2
+ import omit from 'lodash.omit';
3
3
  import { MongoAdapter } from './mongo-adapter.js';
4
4
  import { MongoEntityService } from './mongo-entity-service.js';
5
5
  /**
@@ -16,14 +16,14 @@ export class MongoCollectionService extends MongoEntityService {
16
16
  */
17
17
  constructor(dataType, options) {
18
18
  super(dataType, options);
19
- this.defaultLimit = this.defaultLimit || options?.defaultLimit || 10;
19
+ this.defaultLimit = options?.defaultLimit || 10;
20
20
  }
21
21
  /**
22
22
  * Asserts the existence of a resource with the given ID.
23
23
  * Throws a ResourceNotFoundError if the resource does not exist.
24
24
  *
25
25
  * @param {MongoAdapter.AnyId} id - The ID of the resource to assert.
26
- * @param {MongoCollectionService.ExistsOptions<T>} [options] - Optional options for checking the existence.
26
+ * @param {MongoEntityService.ExistsOptions<T>} [options] - Optional options for checking the existence.
27
27
  * @returns {Promise<void>} - A Promise that resolves when the resource exists.
28
28
  * @throws {ResourceNotAvailableError} - If the resource does not exist.
29
29
  */
@@ -31,285 +31,266 @@ export class MongoCollectionService extends MongoEntityService {
31
31
  if (!(await this.exists(id, options)))
32
32
  throw new ResourceNotAvailableError(this.getResourceName(), id);
33
33
  }
34
- /**
35
- * Creates a new document in the MongoDB collection.
36
- * Interceptors will be called before performing db operation
37
- *
38
- * @param {PartialDTO<T>} input - The input data for creating the document.
39
- * @param {MongoCollectionService.CreateOptions} [options] - The options for creating the document.
40
- * @returns {Promise<PartialDTO<T>>} A promise that resolves to the created document.
41
- * @throws {Error} if an unknown error occurs while creating the document.
42
- */
43
34
  async create(input, options) {
44
- const id = input._id || this._generateId();
45
- if (id != null)
46
- input._id = id;
47
- const info = {
35
+ const command = {
48
36
  crud: 'create',
49
37
  method: 'create',
50
38
  byId: false,
51
- documentId: id,
52
39
  input,
53
40
  options,
54
41
  };
55
- return this._intercept(() => this._create(input, options), info);
42
+ input._id = input._id ?? this._generateId(command);
43
+ return this._executeCommand(command, async () => {
44
+ const r = await this._create(command);
45
+ if (!command.options?.projection)
46
+ return r;
47
+ const findCommand = {
48
+ ...command,
49
+ crud: 'read',
50
+ byId: true,
51
+ documentId: r._id,
52
+ options: omit(options, 'filter'),
53
+ };
54
+ const out = await this._findById(findCommand);
55
+ if (out)
56
+ return out;
57
+ });
56
58
  }
57
59
  /**
58
60
  * Returns the count of documents in the collection based on the provided options.
59
61
  *
60
- * @param {MongoCollectionService.CountOptions<T>} options - The options for the count operation.
62
+ * @param {MongoEntityService.CountOptions<T>} options - The options for the count operation.
61
63
  * @return {Promise<number>} - A promise that resolves to the count of documents in the collection.
62
64
  */
63
65
  async count(options) {
64
- const info = {
66
+ const command = {
65
67
  crud: 'read',
66
68
  method: 'count',
67
69
  byId: false,
68
70
  options,
69
71
  };
70
- return this._intercept(async () => {
71
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
72
- return this._count({ ...options, filter });
73
- }, info);
72
+ return this._executeCommand(command, async () => {
73
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
74
+ command.options = { ...command.options, filter };
75
+ return this._count(command);
76
+ });
74
77
  }
75
78
  /**
76
79
  * Deletes a document from the collection.
77
80
  *
78
81
  * @param {MongoAdapter.AnyId} id - The ID of the document to delete.
79
- * @param {MongoCollectionService.DeleteOptions<T>} [options] - Optional delete options.
82
+ * @param {MongoEntityService.DeleteOptions<T>} [options] - Optional delete options.
80
83
  * @return {Promise<number>} - A Promise that resolves to the number of documents deleted.
81
84
  */
82
85
  async delete(id, options) {
83
- const info = {
86
+ const command = {
84
87
  crud: 'delete',
85
88
  method: 'delete',
86
89
  byId: true,
87
90
  documentId: id,
88
91
  options,
89
92
  };
90
- return this._intercept(async () => {
91
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
92
- return this._delete(id, { ...options, filter });
93
- }, info);
93
+ return this._executeCommand(command, async () => {
94
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
95
+ command.options = { ...command.options, filter };
96
+ return this._delete(command);
97
+ });
94
98
  }
95
99
  /**
96
100
  * Deletes multiple documents from the collection that meet the specified filter criteria.
97
101
  *
98
- * @param {MongoCollectionService.DeleteManyOptions<T>} options - The options for the delete operation.
102
+ * @param {MongoEntityService.DeleteManyOptions<T>} options - The options for the delete operation.
99
103
  * @return {Promise<number>} - A promise that resolves to the number of documents deleted.
100
104
  */
101
105
  async deleteMany(options) {
102
- const info = {
106
+ const command = {
103
107
  crud: 'delete',
104
108
  method: 'deleteMany',
105
109
  byId: false,
106
110
  options,
107
111
  };
108
- return this._intercept(async () => {
109
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
110
- return this._deleteMany({ ...options, filter });
111
- }, info);
112
+ return this._executeCommand(command, async () => {
113
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
114
+ command.options = { ...command.options, filter };
115
+ return this._deleteMany(command);
116
+ });
112
117
  }
113
118
  /**
114
119
  * The distinct command returns a list of distinct values for the given key across a collection.
115
120
  * @param {string} field
116
- * @param {MongoCollectionService.DistinctOptions<T>} [options]
121
+ * @param {MongoEntityService.DistinctOptions<T>} [options]
117
122
  * @protected
118
123
  */
119
124
  async distinct(field, options) {
120
- const info = {
125
+ const command = {
121
126
  crud: 'read',
122
127
  method: 'distinct',
123
- byId: true,
128
+ byId: false,
129
+ field,
124
130
  options,
125
131
  };
126
- return this._intercept(async () => {
127
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
128
- return this._distinct(field, { ...options, filter });
129
- }, info);
132
+ return this._executeCommand(command, async () => {
133
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
134
+ command.options = { ...command.options, filter };
135
+ return this._distinct(command);
136
+ });
130
137
  }
131
138
  /**
132
139
  * Checks if an object with the given id exists.
133
140
  *
134
141
  * @param {MongoAdapter.AnyId} id - The id of the object to check.
135
- * @param {MongoCollectionService.ExistsOptions<T>} [options] - The options for the query (optional).
142
+ * @param {MongoEntityService.ExistsOptions<T>} [options] - The options for the query (optional).
136
143
  * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the object exists or not.
137
144
  */
138
145
  async exists(id, options) {
139
- return !!(await this.findById(id, { ...options, projection: ['_id'] }));
146
+ const command = {
147
+ crud: 'read',
148
+ method: 'exists',
149
+ byId: true,
150
+ documentId: id,
151
+ options,
152
+ };
153
+ return this._executeCommand(command, async () => {
154
+ const documentFilter = await this._getDocumentFilter(command);
155
+ const filter = MongoAdapter.prepareFilter([documentFilter, command.options?.filter]);
156
+ const findCommand = command;
157
+ findCommand.options = { ...command.options, filter, projection: ['_id'] };
158
+ return !!(await this._findById(findCommand));
159
+ });
140
160
  }
141
161
  /**
142
162
  * Checks if an object with the given arguments exists.
143
163
  *
144
- * @param {MongoCollectionService.ExistsOneOptions} [options] - The options for the query (optional).
164
+ * @param {MongoEntityService.ExistsOptions} [options] - The options for the query (optional).
145
165
  * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the object exists or not.
146
166
  */
147
167
  async existsOne(options) {
148
168
  return !!(await this.findOne({ ...options, projection: ['_id'] }));
149
169
  }
150
- /**
151
- * Finds a document by its ID.
152
- *
153
- * @param {MongoAdapter.AnyId} id - The ID of the document.
154
- * @param {MongoCollectionService.FindOneOptions<T>} [options] - The options for the find query.
155
- * @return {Promise<PartialDTO<T | undefined>>} - A promise resolving to the found document, or undefined if not found.
156
- */
157
170
  async findById(id, options) {
158
- const info = {
171
+ const command = {
159
172
  crud: 'read',
160
173
  method: 'findById',
161
174
  byId: true,
162
175
  documentId: id,
163
176
  options,
164
177
  };
165
- return this._intercept(async () => {
166
- const documentFilter = await this._getDocumentFilter(info);
167
- const filter = MongoAdapter.prepareFilter([documentFilter, options?.filter]);
168
- return this._findById(id, { ...options, filter });
169
- }, info);
178
+ return this._executeCommand(command, async () => {
179
+ const documentFilter = await this._getDocumentFilter(command);
180
+ const filter = MongoAdapter.prepareFilter([documentFilter, command.options?.filter]);
181
+ command.options = { ...command.options, filter };
182
+ return this._findById(command);
183
+ });
170
184
  }
171
- /**
172
- * Finds a document in the collection that matches the specified options.
173
- *
174
- * @param {MongoCollectionService.FindOneOptions<T>} [options] - The options for the query.
175
- * @return {Promise<PartialDTO<T> | undefined>} A promise that resolves with the found document or undefined if no document is found.
176
- */
177
185
  async findOne(options) {
178
- const info = {
186
+ const command = {
179
187
  crud: 'read',
180
188
  method: 'findOne',
181
189
  byId: false,
182
190
  options,
183
191
  };
184
- return this._intercept(async () => {
185
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
186
- return this._findOne({ ...options, filter });
187
- }, info);
192
+ return this._executeCommand(command, async () => {
193
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
194
+ command.options = { ...command.options, filter };
195
+ return this._findOne(command);
196
+ });
188
197
  }
189
- /**
190
- * Finds multiple documents in the MongoDB collection.
191
- *
192
- * @param {MongoCollectionService.FindManyOptions<T>} options - The options for the find operation.
193
- * @return A Promise that resolves to an array of partial outputs of type T.
194
- */
195
198
  async findMany(options) {
196
- const info = {
199
+ const command = {
197
200
  crud: 'read',
198
201
  method: 'findMany',
199
202
  byId: false,
200
203
  options,
201
204
  };
202
- return this._intercept(async () => {
203
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
204
- return this._findMany({ ...options, filter, limit: options?.limit || this.defaultLimit });
205
- }, info);
205
+ return this._executeCommand(command, async () => {
206
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
207
+ const limit = command.options?.limit || this.defaultLimit;
208
+ command.options = { ...command.options, filter, limit };
209
+ return this._findMany(command);
210
+ });
206
211
  }
207
- /**
208
- * Finds multiple documents in the collection and returns both records (max limit)
209
- * and total count that matched the given criteria
210
- *
211
- * @param {MongoCollectionService.FindManyOptions<T>} [options] - The options for the find operation.
212
- * @return A Promise that resolves to an array of partial outputs of type T.
213
- */
214
212
  async findManyWithCount(options) {
215
- const info = {
213
+ const command = {
216
214
  crud: 'read',
217
215
  method: 'findManyWithCount',
218
216
  byId: false,
219
217
  options,
220
218
  };
221
- return this._intercept(async () => {
222
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
223
- return this._findManyWithCount({ ...options, filter, limit: options?.limit || this.defaultLimit });
224
- }, info);
219
+ return this._executeCommand(command, async () => {
220
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
221
+ command.options = { ...command.options, filter, limit: command.options?.limit || this.defaultLimit };
222
+ return this._findManyWithCount(command);
223
+ });
225
224
  }
226
- /**
227
- * Retrieves a document from the collection by its ID. Throws error if not found.
228
- *
229
- * @param {MongoAdapter.AnyId} id - The ID of the document to retrieve.
230
- * @param {MongoCollectionService.FindOneOptions<T>} [options] - Optional options for the findOne operation.
231
- * @returns {Promise<PartialDTO<T>>} - A promise that resolves to the retrieved document,
232
- * or rejects with a ResourceNotFoundError if the document does not exist.
233
- * @throws {ResourceNotAvailableError} - If the document with the specified ID does not exist.
234
- */
235
225
  async get(id, options) {
236
226
  const out = await this.findById(id, options);
237
227
  if (!out)
238
228
  throw new ResourceNotAvailableError(this.getResourceName(), id);
239
229
  return out;
240
230
  }
241
- /**
242
- * Updates a document with the given id in the collection.
243
- *
244
- * @param {MongoAdapter.AnyId} id - The id of the document to update.
245
- * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input object containing the fields to update.
246
- * @param {MongoCollectionService.UpdateOptions<T>} [options] - The options for the update operation.
247
- * @returns {Promise<PartialDTO<T> | undefined>} A promise that resolves to the updated document or
248
- * undefined if the document was not found.
249
- */
250
231
  async update(id, input, options) {
251
- const info = {
232
+ const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
233
+ const command = {
252
234
  crud: 'update',
253
235
  method: 'update',
254
236
  documentId: id,
255
237
  byId: true,
256
- input,
238
+ input: isUpdateFilter ? undefined : input,
239
+ inputRaw: isUpdateFilter ? input : undefined,
257
240
  options,
258
241
  };
259
- return this._intercept(async () => {
260
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
261
- return this._update(id, input, { ...options, filter });
262
- }, info);
242
+ return this._executeCommand(command, async () => {
243
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
244
+ command.options = { ...command.options, filter };
245
+ return this._update(command);
246
+ });
263
247
  }
264
248
  /**
265
249
  * Updates a document in the collection with the specified ID.
266
250
  *
267
251
  * @param {MongoAdapter.AnyId} id - The ID of the document to update.
268
252
  * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input data to update the document with.
269
- * @param {MongoCollectionService.UpdateOptions<T>} [options] - The options for updating the document.
253
+ * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The options for updating the document.
270
254
  * @returns {Promise<number>} - A promise that resolves to the number of documents modified.
271
255
  */
272
256
  async updateOnly(id, input, options) {
273
- const info = {
257
+ const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
258
+ const command = {
274
259
  crud: 'update',
275
- method: 'update',
260
+ method: 'updateOnly',
276
261
  documentId: id,
277
262
  byId: true,
278
- input,
263
+ input: isUpdateFilter ? undefined : input,
264
+ inputRaw: isUpdateFilter ? input : undefined,
279
265
  options,
280
266
  };
281
- return this._intercept(async () => {
282
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
283
- return this._updateOnly(id, input, { ...options, filter });
284
- }, info);
267
+ return this._executeCommand(command, async () => {
268
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
269
+ command.options = { ...command.options, filter };
270
+ return this._updateOnly(command);
271
+ });
285
272
  }
286
273
  /**
287
274
  * Updates multiple documents in the collection based on the specified input and options.
288
275
  *
289
276
  * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input to update the documents with.
290
- * @param {MongoCollectionService.UpdateManyOptions<T>} options - The options for updating the documents.
277
+ * @param {MongoEntityService.UpdateManyOptions<T>} options - The options for updating the documents.
291
278
  * @return {Promise<number>} - A promise that resolves to the number of documents matched and modified.
292
279
  */
293
280
  async updateMany(input, options) {
294
- const info = {
281
+ const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
282
+ const command = {
295
283
  crud: 'update',
296
284
  method: 'updateMany',
297
285
  byId: false,
298
- input,
286
+ input: isUpdateFilter ? undefined : input,
287
+ inputRaw: isUpdateFilter ? input : undefined,
299
288
  options,
300
289
  };
301
- return this._intercept(async () => {
302
- const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
303
- return this._updateMany(input, { ...options, filter });
304
- }, info);
305
- }
306
- /**
307
- * Generates an ID.
308
- *
309
- * @protected
310
- * @returns {MongoAdapter.AnyId} The generated ID.
311
- */
312
- _generateId() {
313
- return typeof this.$idGenerator === 'function' ? this.$idGenerator(this) : new ObjectId();
290
+ return this._executeCommand(command, async () => {
291
+ const filter = MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
292
+ command.options = { ...command.options, filter };
293
+ return this._updateMany(command);
294
+ });
314
295
  }
315
296
  }