@opra/mongodb 1.0.0-alpha.2 → 1.0.0-alpha.21

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,6 +1,6 @@
1
- import omit from 'lodash.omit';
2
- import * as assert from 'node:assert';
3
1
  import { InternalServerError } from '@opra/common';
2
+ import omit from 'lodash.omit';
3
+ import { isNotNullish } from 'valgen';
4
4
  import { MongoAdapter } from './mongo-adapter.js';
5
5
  import { MongoService } from './mongo-service.js';
6
6
  /**
@@ -21,19 +21,27 @@ export class MongoEntityService extends MongoService {
21
21
  /**
22
22
  * Creates a new document in the MongoDB collection.
23
23
  *
24
- * @param {PartialDTO<T>} input
25
- * @param {MongoEntityService.CreateOptions} options
24
+ * @param {MongoEntityService.CreateCommand} command
26
25
  * @protected
27
26
  */
28
- async _create(input, options) {
29
- const encode = this.getEncoder('create');
30
- const doc = encode(input);
31
- assert.ok(doc._id, 'You must provide the "_id" field');
27
+ async _create(command) {
28
+ isNotNullish(command.input, { label: 'input' });
29
+ isNotNullish(command.input._id, { label: 'input._id' });
30
+ const inputCodec = this.getInputCodec('create');
31
+ const doc = inputCodec(command.input);
32
+ const { options } = command;
32
33
  const r = await this._dbInsertOne(doc, options);
33
34
  if (r.insertedId) {
34
- if (!options)
35
+ if (!command.options)
35
36
  return doc;
36
- const out = await this._findById(doc._id, omit(options, 'filter'));
37
+ const findCommand = {
38
+ ...command,
39
+ crud: 'read',
40
+ byId: true,
41
+ documentId: doc._id,
42
+ options: omit(options, 'filter'),
43
+ };
44
+ const out = await this._findById(findCommand);
37
45
  if (out)
38
46
  return out;
39
47
  }
@@ -43,92 +51,103 @@ export class MongoEntityService extends MongoService {
43
51
  /**
44
52
  * Returns the count of documents in the collection based on the provided options.
45
53
  *
46
- * @param {MongoEntityService.CountOptions<T>} options - The options for the count operation.
47
- * @return {Promise<number>} - A promise that resolves to the count of documents in the collection.
54
+ * @param {MongoEntityService.CountCommand<T>} command
55
+ * @protected
48
56
  */
49
- async _count(options) {
57
+ async _count(command) {
58
+ const { options } = command;
50
59
  const filter = MongoAdapter.prepareFilter(options?.filter);
51
60
  return this._dbCountDocuments(filter, omit(options, 'filter'));
52
61
  }
53
62
  /**
54
63
  * Deletes a document from the collection.
55
64
  *
56
- * @param {MongoAdapter.AnyId} id - The ID of the document to delete.
57
- * @param {MongoEntityService.DeleteOptions<T>} [options] - Optional delete options.
58
- * @return {Promise<number>} - A Promise that resolves to the number of documents deleted.
65
+ * @param {MongoEntityService.DeleteCommand<T>} command
66
+ * @protected
59
67
  */
60
- async _delete(id, options) {
61
- assert.ok(id, 'You must provide an id');
62
- const filter = MongoAdapter.prepareFilter([MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
68
+ async _delete(command) {
69
+ isNotNullish(command.documentId, { label: 'documentId' });
70
+ const { options } = command;
71
+ const filter = MongoAdapter.prepareFilter([
72
+ MongoAdapter.prepareKeyValues(command.documentId, ['_id']),
73
+ options?.filter,
74
+ ]);
63
75
  const r = await this._dbDeleteOne(filter, options);
64
76
  return r.deletedCount;
65
77
  }
66
78
  /**
67
79
  * Deletes multiple documents from the collection that meet the specified filter criteria.
68
80
  *
69
- * @param {MongoEntityService.DeleteManyOptions<T>} options - The options for the delete operation.
70
- * @return {Promise<number>} - A promise that resolves to the number of documents deleted.
81
+ * @param {MongoEntityService.DeleteCommand<T>} command
82
+ * @protected
71
83
  */
72
- async _deleteMany(options) {
84
+ async _deleteMany(command) {
85
+ const { options } = command;
73
86
  const filter = MongoAdapter.prepareFilter(options?.filter);
74
87
  const r = await this._dbDeleteMany(filter, omit(options, 'filter'));
75
88
  return r.deletedCount;
76
89
  }
77
90
  /**
78
91
  * The distinct command returns a list of distinct values for the given key across a collection.
79
- * @param {string} field
80
- * @param {MongoEntityService.DistinctOptions<T>} options
92
+ *
93
+ * @param {MongoEntityService.DistinctCommand<T>} command
81
94
  * @protected
82
95
  */
83
- async _distinct(field, options) {
96
+ async _distinct(command) {
97
+ const { options, field } = command;
84
98
  const filter = MongoAdapter.prepareFilter(options?.filter);
85
99
  return await this._dbDistinct(field, filter, omit(options, 'filter'));
86
100
  }
87
101
  /**
88
102
  * Finds a document by its ID.
89
103
  *
90
- * @param {MongoAdapter.AnyId} id - The ID of the document.
91
- * @param {MongoEntityService.FindOneOptions<T>} [options] - The options for the find query.
92
- * @return {Promise<PartialDTO<T | undefined>>} - A promise resolving to the found document, or undefined if not found.
104
+ * @param { MongoEntityService.FindOneCommand<T>} command
93
105
  */
94
- async _findById(id, options) {
95
- const filter = MongoAdapter.prepareFilter([MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
106
+ async _findById(command) {
107
+ isNotNullish(command.documentId, { label: 'documentId' });
108
+ const filter = MongoAdapter.prepareFilter([
109
+ MongoAdapter.prepareKeyValues(command.documentId, ['_id']),
110
+ command.options?.filter,
111
+ ]);
112
+ const { options } = command;
96
113
  const mongoOptions = {
97
114
  ...options,
98
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
115
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
99
116
  limit: undefined,
100
117
  skip: undefined,
101
118
  sort: undefined,
102
119
  };
103
- const decode = this.getDecoder();
104
120
  const out = await this._dbFindOne(filter, mongoOptions);
105
- return out ? decode(out) : undefined;
121
+ const outputCodec = this.getOutputCodec('find');
122
+ if (out)
123
+ return outputCodec(out);
106
124
  }
107
125
  /**
108
126
  * Finds a document in the collection that matches the specified options.
109
127
  *
110
- * @param {MongoEntityService.FindOneOptions} [options] - The options for the query.
111
- * @return {Promise<PartialDTO<T> | undefined>} A promise that resolves with the found document or undefined if no document is found.
128
+ * @param {MongoEntityService.FindOneCommand<T>} command
112
129
  */
113
- async _findOne(options) {
130
+ async _findOne(command) {
131
+ const { options } = command;
114
132
  const filter = MongoAdapter.prepareFilter(options?.filter);
115
133
  const mongoOptions = {
116
134
  ...omit(options, 'filter'),
117
135
  sort: options?.sort ? MongoAdapter.prepareSort(options.sort) : undefined,
118
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
136
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
119
137
  limit: undefined,
120
138
  };
121
- const decode = this.getDecoder();
122
139
  const out = await this._dbFindOne(filter, mongoOptions);
123
- return out ? decode(out, { coerce: true }) : undefined;
140
+ const outputCodec = this.getOutputCodec('find');
141
+ if (out)
142
+ return outputCodec(out);
124
143
  }
125
144
  /**
126
145
  * Finds multiple documents in the MongoDB collection.
127
146
  *
128
- * @param {MongoEntityService.FindManyOptions<T>} [options] - The options for the find operation.
129
- * @return A Promise that resolves to an array of partial outputs of type T.
147
+ * @param {MongoEntityService.FindManyCommand<T>} command
130
148
  */
131
- async _findMany(options) {
149
+ async _findMany(command) {
150
+ const { options } = command;
132
151
  const mongoOptions = {
133
152
  ...omit(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
134
153
  };
@@ -147,16 +166,16 @@ export class MongoEntityService extends MongoService {
147
166
  stages.push({ $sort: sort });
148
167
  }
149
168
  stages.push({ $limit: limit });
150
- const dataType = this.getDataType();
169
+ const dataType = this.dataType;
151
170
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
152
171
  if (projection)
153
172
  stages.push({ $project: projection });
154
- const decode = this.getDecoder();
155
173
  const cursor = await this._dbAggregate(stages, mongoOptions);
156
174
  /** Execute db command */
157
175
  try {
158
176
  /** Fetch the cursor and decode the result objects */
159
- return (await cursor.toArray()).map((r) => decode(r));
177
+ const outputCodec = this.getOutputCodec('find');
178
+ return (await cursor.toArray()).map((r) => outputCodec(r));
160
179
  }
161
180
  finally {
162
181
  if (!cursor.closed)
@@ -167,10 +186,10 @@ export class MongoEntityService extends MongoService {
167
186
  * Finds multiple documents in the collection and returns both records (max limit)
168
187
  * and total count that matched the given criteria
169
188
  *
170
- * @param {MongoEntityService.FindManyOptions<T>} [options] - The options for the find operation.
171
- * @return A Promise that resolves to an array of partial outputs of type T.
189
+ * @param {MongoEntityService.FindManyCommand<T>} command
172
190
  */
173
- async _findManyWithCount(options) {
191
+ async _findManyWithCount(command) {
192
+ const { options } = command;
174
193
  const mongoOptions = {
175
194
  ...omit(options, ['projection', 'sort', 'skip', 'limit', 'filter']),
176
195
  };
@@ -201,11 +220,11 @@ export class MongoEntityService extends MongoService {
201
220
  dataStages.push({ $sort: sort });
202
221
  }
203
222
  dataStages.push({ $limit: limit });
204
- const dataType = this.getDataType();
223
+ const dataType = this.dataType;
205
224
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
206
225
  if (projection)
207
226
  dataStages.push({ $project: projection });
208
- const decode = this.getDecoder();
227
+ const outputCodec = this.getOutputCodec('find');
209
228
  /** Execute db command */
210
229
  const cursor = await this._dbAggregate(stages, mongoOptions);
211
230
  try {
@@ -213,7 +232,7 @@ export class MongoEntityService extends MongoService {
213
232
  const facetResult = await cursor.toArray();
214
233
  return {
215
234
  count: facetResult[0].count[0]?.totalMatches || 0,
216
- items: facetResult[0].data?.map((r) => decode(r)),
235
+ items: facetResult[0].data?.map((r) => outputCodec(r)),
217
236
  };
218
237
  }
219
238
  finally {
@@ -224,68 +243,72 @@ export class MongoEntityService extends MongoService {
224
243
  /**
225
244
  * Updates a document with the given id in the collection.
226
245
  *
227
- * @param {AnyId} id - The id of the document to update.
228
- * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input object containing the fields to update.
229
- * @param {MongoEntityService.UpdateOptions<T>} [options] - The options for the update operation.
230
- * @returns {Promise<PartialDTO<T> | undefined>} A promise that resolves to the updated document or
231
- * undefined if the document was not found.
246
+ * @param {MongoEntityService.UpdateOneCommand<T>} command
232
247
  */
233
- async _update(id, input, options) {
234
- const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
235
- const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
236
- if (isUpdateFilter && isDocument)
248
+ async _update(command) {
249
+ isNotNullish(command.documentId, { label: 'documentId' });
250
+ const { input, inputRaw, options } = command;
251
+ isNotNullish(input || inputRaw, { label: 'input' });
252
+ if (input && inputRaw) {
237
253
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
254
+ }
238
255
  let update;
239
- if (isDocument) {
240
- const encode = this.getEncoder('update');
241
- const doc = encode(input, { coerce: true });
256
+ if (input) {
257
+ const inputCodec = this.getInputCodec('update');
258
+ const doc = inputCodec(input);
242
259
  delete doc._id;
243
260
  update = MongoAdapter.preparePatch(doc);
244
261
  update.$set = update.$set || {};
245
262
  }
246
263
  else
247
- update = input;
248
- const filter = MongoAdapter.prepareFilter([MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
264
+ update = inputRaw;
265
+ const filter = MongoAdapter.prepareFilter([
266
+ MongoAdapter.prepareKeyValues(command.documentId, ['_id']),
267
+ options?.filter,
268
+ ]);
249
269
  const mongoOptions = {
250
270
  ...options,
251
271
  includeResultMetadata: false,
252
272
  upsert: undefined,
253
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
273
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
254
274
  };
255
- const decode = this.getDecoder();
256
275
  const out = await this._dbFindOneAndUpdate(filter, update, mongoOptions);
257
- return out ? decode(out, { coerce: true }) : undefined;
276
+ const outputCodec = this.getOutputCodec('update');
277
+ if (out)
278
+ return outputCodec(out);
258
279
  }
259
280
  /**
260
281
  * Updates a document in the collection with the specified ID.
261
282
  *
262
- * @param {MongoAdapter.AnyId} id - The ID of the document to update.
263
- * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input data to update the document with.
264
- * @param {MongoEntityService.UpdateOptions<T>} [options] - The options for updating the document.
265
- * @returns {Promise<number>} - A promise that resolves to the number of documents modified.
283
+ * @param {MongoEntityService.UpdateOneCommand<T>} command
266
284
  */
267
- async _updateOnly(id, input, options) {
268
- const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
269
- const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
270
- if (isUpdateFilter && isDocument)
285
+ async _updateOnly(command) {
286
+ isNotNullish(command.documentId, { label: 'documentId' });
287
+ const { input, inputRaw, options } = command;
288
+ isNotNullish(input || inputRaw, { label: 'input' });
289
+ if (input && inputRaw) {
271
290
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
291
+ }
272
292
  let update;
273
- if (isDocument) {
274
- const encode = this.getEncoder('update');
275
- const doc = encode(input, { coerce: true });
293
+ if (input) {
294
+ const inputCodec = this.getInputCodec('update');
295
+ const doc = inputCodec(input);
276
296
  delete doc._id;
277
297
  update = MongoAdapter.preparePatch(doc);
278
298
  if (!Object.keys(doc).length)
279
299
  return 0;
280
300
  }
281
301
  else
282
- update = input;
283
- const filter = MongoAdapter.prepareFilter([MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
302
+ update = inputRaw;
303
+ const filter = MongoAdapter.prepareFilter([
304
+ MongoAdapter.prepareKeyValues(command.documentId, ['_id']),
305
+ options?.filter,
306
+ ]);
284
307
  const mongoOptions = {
285
308
  ...options,
286
309
  includeResultMetadata: false,
287
310
  upsert: undefined,
288
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
311
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
289
312
  };
290
313
  const out = await this._dbUpdateOne(filter, update, mongoOptions);
291
314
  return out.matchedCount;
@@ -293,26 +316,26 @@ export class MongoEntityService extends MongoService {
293
316
  /**
294
317
  * Updates multiple documents in the collection based on the specified input and options.
295
318
  *
296
- * @param {PatchDTO<T>|UpdateFilter<T>} input - The partial input to update the documents with.
297
- * @param {MongoEntityService.UpdateManyOptions<T>} [options] - The options for updating the documents.
298
- * @return {Promise<number>} - A promise that resolves to the number of documents matched and modified.
319
+ * @param {MongoEntityService.UpdateManyCommand<T>} command
299
320
  */
300
- async _updateMany(input, options) {
301
- const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
302
- const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
303
- if (isUpdateFilter && isDocument)
321
+ async _updateMany(command) {
322
+ isNotNullish(command.input, { label: 'input' });
323
+ const { input, inputRaw, options } = command;
324
+ isNotNullish(input || inputRaw, { label: 'input' });
325
+ if (input && inputRaw) {
304
326
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
327
+ }
305
328
  let update;
306
- if (isDocument) {
307
- const encode = this.getEncoder('update');
308
- const doc = encode(input, { coerce: true });
329
+ if (input) {
330
+ const inputCodec = this.getInputCodec('update');
331
+ const doc = inputCodec(input);
309
332
  delete doc._id;
310
333
  update = MongoAdapter.preparePatch(doc);
311
334
  if (!Object.keys(doc).length)
312
335
  return 0;
313
336
  }
314
337
  else
315
- update = input;
338
+ update = inputRaw;
316
339
  const mongoOptions = {
317
340
  ...omit(options, 'filter'),
318
341
  upsert: undefined,