@travetto/model-mongo 5.0.18 → 5.0.19

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/README.md CHANGED
@@ -107,6 +107,11 @@ export class MongoModelConfig {
107
107
  */
108
108
  connectionString?: string;
109
109
 
110
+ /**
111
+ * Should we store the _id as a string in the id field
112
+ */
113
+ storeId?: boolean;
114
+
110
115
  /**
111
116
  * Load all the ssl certs as needed
112
117
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "5.0.18",
3
+ "version": "5.0.19",
4
4
  "description": "Mongo backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "mongo",
@@ -25,10 +25,10 @@
25
25
  "directory": "module/model-mongo"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/cli": "^5.0.17",
29
- "@travetto/config": "^5.0.14",
30
- "@travetto/model": "^5.0.15",
31
- "@travetto/model-query": "^5.0.15",
28
+ "@travetto/cli": "^5.0.18",
29
+ "@travetto/config": "^5.0.15",
30
+ "@travetto/model": "^5.0.16",
31
+ "@travetto/model-query": "^5.0.16",
32
32
  "mongodb": "^6.12.0"
33
33
  },
34
34
  "travetto": {
package/src/config.ts CHANGED
@@ -59,6 +59,11 @@ export class MongoModelConfig {
59
59
  */
60
60
  connectionString?: string;
61
61
 
62
+ /**
63
+ * Should we store the _id as a string in the id field
64
+ */
65
+ storeId?: boolean;
66
+
62
67
  /**
63
68
  * Load all the ssl certs as needed
64
69
  */
@@ -1,11 +1,14 @@
1
- import { Binary, CreateIndexesOptions, FindCursor, IndexDirection, ObjectId } from 'mongodb';
1
+ import {
2
+ Binary, type CreateIndexesOptions, type Filter, type FindCursor, type IndexDirection, ObjectId, type WithId as MongoWithId
3
+ } from 'mongodb';
2
4
 
3
- import { castTo, Class, TypedObject } from '@travetto/runtime';
4
- import { DistanceUnit, PageableModelQuery, WhereClause } from '@travetto/model-query';
5
+ import { AppError, castTo, Class, TypedObject } from '@travetto/runtime';
6
+ import type { DistanceUnit, PageableModelQuery, WhereClause } from '@travetto/model-query';
5
7
  import type { ModelType, IndexField, IndexConfig } from '@travetto/model';
6
8
  import { DataUtil, SchemaRegistry } from '@travetto/schema';
9
+
7
10
  import { ModelQueryUtil } from '@travetto/model-query/src/internal/service/query';
8
- import { AllViewⲐ } from '@travetto/schema/src/internal/types';
11
+ import { AllViewSymbol } from '@travetto/schema/src/internal/types';
9
12
  import { PointImpl } from '@travetto/model-query/src/internal/model/point';
10
13
 
11
14
  type IdxCfg = CreateIndexesOptions;
@@ -22,8 +25,6 @@ const RADIANS_TO: Record<DistanceUnit, number> = {
22
25
  };
23
26
 
24
27
  export type WithId<T, I = unknown> = T & { _id?: I };
25
- const isWithId = <T extends ModelType, I = unknown>(o: T): o is WithId<T, I> => o && '_id' in o;
26
-
27
28
  export type BasicIdx = Record<string, IndexDirection>;
28
29
  export type PlainIdx = Record<string, -1 | 0 | 1>;
29
30
 
@@ -59,24 +60,9 @@ export class MongoUtil {
59
60
  }
60
61
  }
61
62
 
62
- static async postLoadId<T extends ModelType>(item: T): Promise<T> {
63
- if (isWithId(item)) {
64
- delete item._id;
65
- }
66
- return item;
67
- }
68
-
69
- static preInsertId<T extends ModelType>(item: T): T {
70
- if (item && item.id) {
71
- const itemWithId: WithId<T> = castTo(item);
72
- itemWithId._id = this.uuid(item.id);
73
- }
74
- return item;
75
- }
76
-
77
- static extractWhereFilter<T extends ModelType, U extends WhereClause<T>>(cls: Class<T>, where?: U, checkExpiry = true): Record<string, unknown> {
63
+ static extractWhereFilter<T extends ModelType, U extends WhereClause<T>>(cls: Class<T>, where?: U, checkExpiry = true): Filter<T> {
78
64
  where = castTo(ModelQueryUtil.getWhereClause(cls, where, checkExpiry));
79
- return where ? this.extractWhereClause(cls, where) : {};
65
+ return castTo(where ? this.extractWhereClause(cls, where) : {});
80
66
  }
81
67
 
82
68
  /**
@@ -103,58 +89,63 @@ export class MongoUtil {
103
89
  for (const key of keys) {
104
90
  const subpath = `${path}${key}`;
105
91
  const v: Record<string, unknown> = castTo(sub[key]);
106
- const subField = schema?.views[AllViewⲐ].schema[key];
92
+ const subField = schema?.views[AllViewSymbol].schema[key];
107
93
 
108
- if (subpath === 'id') { // Handle ids directly
109
- out._id = typeof v === 'string' ? this.uuid(v) : v;
110
- } else {
111
- const isPlain = v && DataUtil.isPlainObject(v);
112
- const firstKey = isPlain ? Object.keys(v)[0] : '';
94
+ const isPlain = v && DataUtil.isPlainObject(v);
95
+ const firstKey = isPlain ? Object.keys(v)[0] : '';
113
96
 
114
- if ((isPlain && !firstKey.startsWith('$')) || v?.constructor?.Ⲑid) {
115
- if (recursive) {
116
- Object.assign(out, this.extractSimple(subField?.type, v, `${subpath}.`, recursive));
97
+ if (subpath === 'id') {
98
+ if (!firstKey) {
99
+ out._id = Array.isArray(v) ? v.map(x => this.uuid(x)) : this.uuid(`${v}`);
100
+ } else if (firstKey === '$in' || firstKey === '$nin' || firstKey === '$eq' || firstKey === '$ne') {
101
+ const temp = v[firstKey];
102
+ out._id = { [firstKey]: Array.isArray(temp) ? temp.map(x => this.uuid(x)) : this.uuid(`${temp}`) };
103
+ } else {
104
+ throw new AppError('Invalid id query');
105
+ }
106
+ } else if ((isPlain && !firstKey.startsWith('$')) || v?.constructor?.Ⲑid) {
107
+ if (recursive) {
108
+ Object.assign(out, this.extractSimple(subField?.type, v, `${subpath}.`, recursive));
109
+ } else {
110
+ out[subpath] = v;
111
+ }
112
+ } else {
113
+ if (firstKey === '$gt' || firstKey === '$lt' || firstKey === '$gte' || firstKey === '$lte') {
114
+ for (const [sk, sv] of Object.entries(v)) {
115
+ v[sk] = ModelQueryUtil.resolveComparator(sv);
116
+ }
117
+ } else if (firstKey === '$exists' && subField?.array) {
118
+ const exists = v.$exists;
119
+ if (!exists) {
120
+ delete v.$exists;
121
+ v.$in = [null, []];
117
122
  } else {
118
- out[subpath] = v;
123
+ v.$exists = true;
124
+ v.$nin = [null, []];
119
125
  }
120
- } else {
121
- if (firstKey === '$gt' || firstKey === '$lt' || firstKey === '$gte' || firstKey === '$lte') {
122
- for (const [sk, sv] of Object.entries(v)) {
123
- v[sk] = ModelQueryUtil.resolveComparator(sv);
124
- }
125
- } else if (firstKey === '$exists' && subField?.array) {
126
- const exists = v.$exists;
127
- if (!exists) {
128
- delete v.$exists;
129
- v.$in = [null, []];
130
- } else {
131
- v.$exists = true;
132
- v.$nin = [null, []];
133
- }
134
- } else if (firstKey === '$regex') {
135
- v.$regex = DataUtil.toRegex(castTo(v.$regex));
136
- } else if (firstKey && '$near' in v) {
137
- const dist: number = castTo(v.$maxDistance);
138
- const distance = dist / RADIANS_TO[(castTo<DistanceUnit>(v.$unit) ?? 'km')];
139
- v.$maxDistance = distance;
140
- delete v.$unit;
141
- } else if (firstKey && '$geoWithin' in v) {
142
- const coords: [number, number][] = castTo(v.$geoWithin);
143
- const first = coords[0];
144
- const last = coords[coords.length - 1];
145
- // Connect if not
146
- if (first[0] !== last[0] || first[1] !== last[1]) {
147
- coords.push(first);
148
- }
149
- v.$geoWithin = {
150
- $geometry: {
151
- type: 'Polygon',
152
- coordinates: [coords]
153
- }
154
- };
126
+ } else if (firstKey === '$regex') {
127
+ v.$regex = DataUtil.toRegex(castTo(v.$regex));
128
+ } else if (firstKey && '$near' in v) {
129
+ const dist: number = castTo(v.$maxDistance);
130
+ const distance = dist / RADIANS_TO[(castTo<DistanceUnit>(v.$unit) ?? 'km')];
131
+ v.$maxDistance = distance;
132
+ delete v.$unit;
133
+ } else if (firstKey && '$geoWithin' in v) {
134
+ const coords: [number, number][] = castTo(v.$geoWithin);
135
+ const first = coords[0];
136
+ const last = coords[coords.length - 1];
137
+ // Connect if not
138
+ if (first[0] !== last[0] || first[1] !== last[1]) {
139
+ coords.push(first);
155
140
  }
156
- out[subpath] = v;
141
+ v.$geoWithin = {
142
+ $geometry: {
143
+ type: 'Polygon',
144
+ coordinates: [coords]
145
+ }
146
+ };
157
147
  }
148
+ out[subpath === 'id' ? '_id' : subpath] = v;
158
149
  }
159
150
  }
160
151
  return out;
@@ -197,7 +188,7 @@ export class MongoUtil {
197
188
  ].map(x => [...x]);
198
189
  }
199
190
 
200
- static prepareCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor<T>, query: PageableModelQuery<T>): FindCursor<T> {
191
+ static prepareCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor<T | MongoWithId<T>>, query: PageableModelQuery<T>): FindCursor<T> {
201
192
  if (query.select) {
202
193
  const selectKey = Object.keys(query.select)[0];
203
194
  const select = typeof selectKey === 'string' && selectKey.startsWith('$') ? query.select : this.extractSimple(cls, query.select);
@@ -221,6 +212,6 @@ export class MongoUtil {
221
212
  cursor = cursor.skip(Math.trunc(query.offset ?? 0));
222
213
  }
223
214
 
224
- return cursor;
215
+ return castTo(cursor);
225
216
  }
226
217
  }
package/src/service.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { pipeline } from 'node:stream/promises';
2
2
 
3
- import { type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection, type ObjectId, type Binary, type RootFilterOperators } from 'mongodb';
3
+ import {
4
+ type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
5
+ type ObjectId, type Binary, type RootFilterOperators, type Filter,
6
+ type WithId as MongoWithId
7
+ } from 'mongodb';
4
8
 
5
9
  import {
6
10
  ModelRegistry, ModelType, OptionalId, ModelCrudSupport, ModelStorageSupport,
@@ -32,7 +36,7 @@ import { MODEL_BLOB, ModelBlobNamespace, ModelBlobUtil } from '@travetto/model/s
32
36
  import { MongoUtil, PlainIdx, WithId } from './internal/util';
33
37
  import { MongoModelConfig } from './config';
34
38
 
35
- const ListIndexⲐ = Symbol.for('@travetto/mongo-model:list-index');
39
+ const ListIndexSymbol = Symbol.for('@travetto/mongo-model:list-index');
36
40
 
37
41
  type BlobRaw = GridFSFile & { metadata?: BlobMeta };
38
42
 
@@ -56,6 +60,40 @@ export class MongoModelService implements
56
60
 
57
61
  constructor(public readonly config: MongoModelConfig) { }
58
62
 
63
+ restoreId(item: { id?: string, _id?: unknown }): void {
64
+ if (item._id) {
65
+ item.id ??= MongoUtil.idToString(castTo(item._id));
66
+ delete item._id;
67
+ }
68
+ }
69
+
70
+ async postLoad<T extends ModelType>(cls: Class<T>, item: T | MongoWithId<T>): Promise<T> {
71
+ this.restoreId(item);
72
+ return await ModelCrudUtil.load(cls, item);
73
+ }
74
+
75
+ postUpdate<T extends ModelType>(item: T, id?: string): T {
76
+ if (id) {
77
+ item.id ??= id;
78
+ }
79
+ this.restoreId(item);
80
+ return item;
81
+ }
82
+
83
+ preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id: string }): string;
84
+ preUpdate<T extends OptionalId<ModelType>>(item: Omit<T, 'id'> & { _id?: Binary }): undefined;
85
+ preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id: undefined }): undefined;
86
+ preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id?: string }): string | undefined {
87
+ if (item && item.id) {
88
+ const id = item.id;
89
+ item._id = MongoUtil.uuid(id);
90
+ if (!this.config.storeId) {
91
+ delete item.id;
92
+ }
93
+ return id;
94
+ }
95
+ }
96
+
59
97
  async #describeBlobRaw(location: string): Promise<BlobRaw> {
60
98
  const files: BlobRaw[] = await this.#bucket.find({ filename: location }, { limit: 1 }).toArray();
61
99
 
@@ -78,8 +116,12 @@ export class MongoModelService implements
78
116
  ModelExpiryUtil.registerCull(this);
79
117
  }
80
118
 
81
- getWhereFilter<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Record<string, unknown> {
82
- return MongoUtil.extractWhereFilter(cls, where, checkExpiry);
119
+ getWhereFilter<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Filter<T> {
120
+ return castTo(MongoUtil.extractWhereFilter(cls, where, checkExpiry));
121
+ }
122
+
123
+ getIdFilter<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true): Filter<T> {
124
+ return this.getWhereFilter(cls, castTo({ _id: MongoUtil.uuid(id) }), checkExpiry);
83
125
  }
84
126
 
85
127
  // Storage
@@ -120,73 +162,75 @@ export class MongoModelService implements
120
162
  /**
121
163
  * Get mongo collection
122
164
  */
123
- async getStore<T extends ModelType>(cls: Class<T>): Promise<Collection> {
165
+ async getStore<T extends ModelType>(cls: Class<T>): Promise<Collection<T>> {
124
166
  return this.#db.collection(ModelRegistry.getStore(cls).toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_'));
125
167
  }
126
168
 
127
169
  // Crud
128
170
  async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
129
171
  const store = await this.getStore(cls);
130
- const result = await store.findOne(this.getWhereFilter<ModelType>(cls, { id }), {});
172
+ const result = await store.findOne(this.getIdFilter(cls, id), {});
131
173
  if (result) {
132
- const res = await ModelCrudUtil.load(cls, result);
174
+ const res = await this.postLoad(cls, result);
133
175
  if (res) {
134
- return MongoUtil.postLoadId(res);
176
+ return res;
135
177
  }
136
178
  }
137
179
  throw new NotFoundError(cls, id);
138
180
  }
139
181
 
140
182
  async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
141
- const cleaned: WithId<T, Binary> = castTo(await ModelCrudUtil.preStore(cls, item, this));
142
- cleaned._id = MongoUtil.uuid(cleaned.id);
183
+ const cleaned = await ModelCrudUtil.preStore<WithId<T, Binary>>(cls, item, this);
184
+ const id = this.preUpdate(cleaned);
143
185
 
144
186
  const store = await this.getStore(cls);
145
187
  const result = await store.insertOne(castTo(cleaned));
146
188
  if (!result.insertedId) {
147
- throw new ExistsError(cls, cleaned.id);
189
+ throw new ExistsError(cls, id);
148
190
  }
149
- delete cleaned._id;
150
- return cleaned;
191
+ return this.postUpdate(cleaned, id);
151
192
  }
152
193
 
153
194
  async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
154
195
  item = await ModelCrudUtil.preStore(cls, item, this);
196
+ const id = this.preUpdate(item);
155
197
  const store = await this.getStore(cls);
156
- const res = await store.replaceOne(this.getWhereFilter<ModelType>(cls, { id: item.id }), item);
198
+ const res = await store.replaceOne(this.getIdFilter(cls, id), item);
157
199
  if (res.matchedCount === 0) {
158
- throw new NotFoundError(cls, item.id);
200
+ throw new NotFoundError(cls, id);
159
201
  }
160
- return item;
202
+ return this.postUpdate(item, id);
161
203
  }
162
204
 
163
205
  async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
164
- const cleaned = await ModelCrudUtil.preStore(cls, item, this);
206
+ const cleaned = await ModelCrudUtil.preStore<WithId<T, Binary>>(cls, item, this);
207
+ const id = this.preUpdate(cleaned);
165
208
  const store = await this.getStore(cls);
209
+
166
210
  try {
167
211
  await store.updateOne(
168
- this.getWhereFilter<ModelType>(cls, { id: cleaned.id }, false),
212
+ this.getIdFilter(cls, id, false),
169
213
  { $set: cleaned },
170
214
  { upsert: true }
171
215
  );
172
216
  } catch (err) {
173
217
  if (err instanceof Error && err.message.includes('duplicate key error')) {
174
- throw new ExistsError(cls, cleaned.id);
218
+ throw new ExistsError(cls, id);
175
219
  } else {
176
220
  throw err;
177
221
  }
178
222
  }
179
- return cleaned;
223
+ return this.postUpdate(cleaned, id);
180
224
  }
181
225
 
182
226
  async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
183
227
  const store = await this.getStore(cls);
184
228
 
185
- let final: Record<string, unknown> = await ModelCrudUtil.prePartialUpdate(cls, item, view);
229
+ const final = await ModelCrudUtil.prePartialUpdate(cls, item, view);
230
+ const simple = MongoUtil.extractSimple(cls, final, undefined, false);
186
231
 
187
- const items = MongoUtil.extractSimple(cls, final, undefined, false);
188
- final = Object
189
- .entries(items)
232
+ const operation: Partial<T> = castTo(Object
233
+ .entries(simple)
190
234
  .reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>((acc, [k, v]) => {
191
235
  if (v === null || v === undefined) {
192
236
  (acc.$unset ??= {})[k] = v;
@@ -194,13 +238,13 @@ export class MongoModelService implements
194
238
  (acc.$set ??= {})[k] = v;
195
239
  }
196
240
  return acc;
197
- }, {});
241
+ }, {}));
198
242
 
199
243
  const id = item.id;
200
244
 
201
245
  const res = await store.findOneAndUpdate(
202
- this.getWhereFilter<ModelType>(cls, { id }),
203
- final,
246
+ this.getIdFilter(cls, id),
247
+ operation,
204
248
  { returnDocument: 'after', includeResultMetadata: true }
205
249
  );
206
250
 
@@ -213,7 +257,7 @@ export class MongoModelService implements
213
257
 
214
258
  async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
215
259
  const store = await this.getStore(cls);
216
- const result = await store.deleteOne(this.getWhereFilter<ModelType>(cls, { id }, false));
260
+ const result = await store.deleteOne(this.getIdFilter(cls, id, false));
217
261
  if (result.deletedCount === 0) {
218
262
  throw new NotFoundError(cls, id);
219
263
  }
@@ -224,7 +268,7 @@ export class MongoModelService implements
224
268
  const cursor = store.find(this.getWhereFilter(cls, {}), { timeout: true }).batchSize(100);
225
269
  for await (const el of cursor) {
226
270
  try {
227
- yield MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el));
271
+ yield await this.postLoad(cls, el);
228
272
  } catch (err) {
229
273
  if (!(err instanceof NotFoundError)) {
230
274
  throw err;
@@ -294,7 +338,7 @@ export class MongoModelService implements
294
338
  return out;
295
339
  }
296
340
 
297
- const store = await this.getStore(cls);
341
+ const store = await this.getStore<Partial<T> & ModelType>(cls);
298
342
  const bulk = store.initializeUnorderedBulkOp({ writeConcern: { w: 1 } });
299
343
  const { upsertedIds, insertedIds } = await ModelBulkUtil.preStore(cls, operations, this);
300
344
 
@@ -302,11 +346,14 @@ export class MongoModelService implements
302
346
 
303
347
  for (const op of operations) {
304
348
  if (op.insert) {
305
- bulk.insert(MongoUtil.preInsertId(asFull(op.insert)));
349
+ this.preUpdate(op.insert);
350
+ bulk.insert(op.insert);
306
351
  } else if (op.upsert) {
307
- bulk.find({ _id: MongoUtil.uuid(op.upsert.id!) }).upsert().updateOne({ $set: op.upsert });
352
+ const id = this.preUpdate(op.upsert);
353
+ bulk.find({ _id: MongoUtil.uuid(id!) }).upsert().updateOne({ $set: op.upsert });
308
354
  } else if (op.update) {
309
- bulk.find({ _id: MongoUtil.uuid(op.update.id) }).update({ $set: op.update });
355
+ const id = this.preUpdate(op.update);
356
+ bulk.find({ _id: MongoUtil.uuid(id) }).update({ $set: op.update });
310
357
  } else if (op.delete) {
311
358
  bulk.find({ _id: MongoUtil.uuid(op.delete.id) }).deleteOne();
312
359
  }
@@ -314,13 +361,16 @@ export class MongoModelService implements
314
361
 
315
362
  const res = await bulk.execute({});
316
363
 
364
+ // Restore all ids
317
365
  for (const op of operations) {
318
- if (op.insert) {
319
- MongoUtil.postLoadId(asFull(op.insert));
366
+ const core = op.insert ?? op.upsert ?? op.update;
367
+ if (core) {
368
+ this.postUpdate(asFull(core));
320
369
  }
321
370
  }
322
- for (const [index, _id] of TypedObject.entries(res.upsertedIds)) {
323
- out.insertedIds.set(+index, MongoUtil.idToString(castTo(_id)));
371
+
372
+ for (const [index, _id] of TypedObject.entries<Record<string, string>>(res.upsertedIds)) {
373
+ out.insertedIds.set(+index, MongoUtil.idToString(_id));
324
374
  }
325
375
 
326
376
  if (out.counts) {
@@ -361,7 +411,7 @@ export class MongoModelService implements
361
411
  if (!result) {
362
412
  throw new NotFoundError(`${cls.name}: ${idx}`, key);
363
413
  }
364
- return await ModelCrudUtil.load(cls, result);
414
+ return await this.postLoad(cls, result);
365
415
  }
366
416
 
367
417
  async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
@@ -392,11 +442,11 @@ export class MongoModelService implements
392
442
  castTo(ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }))
393
443
  );
394
444
 
395
- const sort = castTo<{ [ListIndexⲐ]: PlainIdx }>(idxCfg)[ListIndexⲐ] ??= MongoUtil.getPlainIndex(idxCfg);
445
+ const sort = castTo<{ [ListIndexSymbol]: PlainIdx }>(idxCfg)[ListIndexSymbol] ??= MongoUtil.getPlainIndex(idxCfg);
396
446
  const cursor = store.find(where, { timeout: true }).batchSize(100).sort(castTo(sort));
397
447
 
398
448
  for await (const el of cursor) {
399
- yield (await MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el)));
449
+ yield await this.postLoad(cls, el);
400
450
  }
401
451
  }
402
452
 
@@ -406,9 +456,9 @@ export class MongoModelService implements
406
456
 
407
457
  const col = await this.getStore(cls);
408
458
  const filter = MongoUtil.extractWhereFilter(cls, query.where);
409
- const cursor = col.find<T>(filter, {});
459
+ const cursor = col.find(filter, {});
410
460
  const items = await MongoUtil.prepareCursor(cls, cursor, query).toArray();
411
- return await Promise.all(items.map(r => ModelCrudUtil.load(cls, r).then(MongoUtil.postLoadId)));
461
+ return await Promise.all(items.map(r => this.postLoad(cls, r)));
412
462
  }
413
463
 
414
464
  async queryCount<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
@@ -430,15 +480,16 @@ export class MongoModelService implements
430
480
 
431
481
  const col = await this.getStore(cls);
432
482
  const item = await ModelCrudUtil.preStore(cls, data, this);
483
+ const id = this.preUpdate(item);
433
484
  const where = ModelQueryUtil.getWhereClause(cls, query.where);
434
- where.id = item.id;
485
+ where.id = id;
435
486
 
436
487
  const filter = MongoUtil.extractWhereFilter(cls, where);
437
488
  const res = await col.replaceOne(filter, item);
438
489
  if (res.matchedCount === 0) {
439
- throw new NotFoundError(cls, item.id);
490
+ throw new NotFoundError(cls, id);
440
491
  }
441
- return item;
492
+ return this.postUpdate(item, id);
442
493
  }
443
494
 
444
495
  async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
@@ -454,7 +505,6 @@ export class MongoModelService implements
454
505
  await QueryVerifier.verify(cls, query);
455
506
 
456
507
  const item = await ModelCrudUtil.prePartialUpdate(cls, data);
457
-
458
508
  const col = await this.getStore(cls);
459
509
  const items = MongoUtil.extractSimple(cls, item);
460
510
  const final = Object.entries(items).reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>(
@@ -468,7 +518,7 @@ export class MongoModelService implements
468
518
  }, {});
469
519
 
470
520
  const filter = MongoUtil.extractWhereFilter(cls, query.where);
471
- const res = await col.updateMany(filter, final);
521
+ const res = await col.updateMany(filter, castTo(final));
472
522
  return res.matchedCount;
473
523
  }
474
524
 
@@ -532,13 +582,12 @@ export class MongoModelService implements
532
582
  search = { $search: search, $language: 'en' };
533
583
  }
534
584
 
535
- (query.sort ??= []).unshift({
536
- // @ts-expect-error
585
+ (query.sort ??= []).unshift(castTo<(typeof query.sort[0])>({
537
586
  score: { $meta: 'textScore' }
538
- });
587
+ }));
539
588
 
540
- const cursor = col.find<T>({ $and: [{ $text: search }, filter] }, {});
589
+ const cursor = col.find(castTo({ $and: [{ $text: search }, filter] }), {});
541
590
  const items = await MongoUtil.prepareCursor(cls, cursor, query).toArray();
542
- return await Promise.all(items.map(r => ModelCrudUtil.load(cls, r).then(MongoUtil.postLoadId)));
591
+ return await Promise.all(items.map(r => this.postLoad(cls, r)));
543
592
  }
544
593
  }