@travetto/model-mongo 5.0.0-rc.1 → 5.0.0-rc.10

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
@@ -48,7 +48,7 @@ where the [MongoModelConfig](https://github.com/travetto/travetto/tree/main/modu
48
48
  ```typescript
49
49
  import type mongo from 'mongodb';
50
50
 
51
- import { Env, TimeSpan, TimeUtil, RuntimeResources } from '@travetto/base';
51
+ import { TimeSpan, TimeUtil, RuntimeResources, Runtime } from '@travetto/runtime';
52
52
  import { Config } from '@travetto/config';
53
53
  import { Field } from '@travetto/schema';
54
54
 
@@ -151,7 +151,7 @@ export class MongoModelConfig {
151
151
  }
152
152
  }
153
153
 
154
- if (!Env.production) {
154
+ if (!Runtime.production) {
155
155
  opts.waitQueueTimeoutMS ??= TimeUtil.asMillis(1, 'd'); // Wait a day in dev mode
156
156
  }
157
157
  }
@@ -174,4 +174,4 @@ export class MongoModelConfig {
174
174
  }
175
175
  ```
176
176
 
177
- Additionally, you can see that the class is registered with the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) annotation, and so these values can be overridden using the standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Configuration support") resolution paths.The SSL file options in `clientOptions` will automatically be resolved to files when given a path. This path can be a resource path (will attempt to lookup using [RuntimeResources](https://github.com/travetto/travetto/tree/main/module/base/src/runtime.ts#L8)) or just a standard file path.
177
+ Additionally, you can see that the class is registered with the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) annotation, and so these values can be overridden using the standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Configuration support") resolution paths.The SSL file options in `clientOptions` will automatically be resolved to files when given a path. This path can be a resource path (will attempt to lookup using [RuntimeResources](https://github.com/travetto/travetto/tree/main/module/runtime/src/resources.ts#L8)) or just a standard file path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "5.0.0-rc.1",
3
+ "version": "5.0.0-rc.10",
4
4
  "description": "Mongo backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "mongo",
@@ -25,13 +25,13 @@
25
25
  "directory": "module/model-mongo"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/config": "^5.0.0-rc.1",
29
- "@travetto/model": "^5.0.0-rc.1",
30
- "@travetto/model-query": "^5.0.0-rc.0",
28
+ "@travetto/config": "^5.0.0-rc.10",
29
+ "@travetto/model": "^5.0.0-rc.10",
30
+ "@travetto/model-query": "^5.0.0-rc.10",
31
31
  "mongodb": "^6.8.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/command": "^5.0.0-rc.0"
34
+ "@travetto/command": "^5.0.0-rc.9"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/command": {
package/src/config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type mongo from 'mongodb';
2
2
 
3
- import { Env, TimeSpan, TimeUtil, RuntimeResources } from '@travetto/base';
3
+ import { TimeSpan, TimeUtil, RuntimeResources, Runtime } from '@travetto/runtime';
4
4
  import { Config } from '@travetto/config';
5
5
  import { Field } from '@travetto/schema';
6
6
 
@@ -103,7 +103,7 @@ export class MongoModelConfig {
103
103
  }
104
104
  }
105
105
 
106
- if (!Env.production) {
106
+ if (!Runtime.production) {
107
107
  opts.waitQueueTimeoutMS ??= TimeUtil.asMillis(1, 'd'); // Wait a day in dev mode
108
108
  }
109
109
  }
@@ -1,7 +1,7 @@
1
1
  import { Binary, ObjectId } from 'mongodb';
2
2
 
3
- import { Class } from '@travetto/base';
4
- import { DistanceUnit, ModelQuery, Query, WhereClause } from '@travetto/model-query';
3
+ import { castTo, Class, TypedObject } from '@travetto/runtime';
4
+ import { DistanceUnit, WhereClause } from '@travetto/model-query';
5
5
  import type { ModelType, IndexField } from '@travetto/model';
6
6
  import { DataUtil, SchemaRegistry } from '@travetto/schema';
7
7
  import { ModelQueryUtil } from '@travetto/model-query/src/internal/service/query';
@@ -18,8 +18,8 @@ const RADIANS_TO: Record<DistanceUnit, number> = {
18
18
  rad: 1
19
19
  };
20
20
 
21
- export type WithId<T> = T & { _id?: Binary };
22
- const isWithId = <T extends ModelType>(o: T): o is WithId<T> => o && '_id' in o;
21
+ export type WithId<T, I = unknown> = T & { _id?: I };
22
+ const isWithId = <T extends ModelType, I = unknown>(o: T): o is WithId<T, I> => o && '_id' in o;
23
23
 
24
24
  /**
25
25
  * Basic mongo utils for conforming to the model module
@@ -29,20 +29,18 @@ export class MongoUtil {
29
29
  static toIndex<T extends ModelType>(f: IndexField<T>): Record<string, number> {
30
30
  const keys = [];
31
31
  while (typeof f !== 'number' && typeof f !== 'boolean' && Object.keys(f)) {
32
- const key = Object.keys(f)[0];
33
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
34
- f = f[key as keyof typeof f] as IndexField<T>;
32
+ const key = TypedObject.keys(f)[0];
33
+ f = castTo(f[key]);
35
34
  keys.push(key);
36
35
  }
37
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
38
- const rf = f as unknown as (number | boolean);
36
+ const rf: number | boolean = castTo(f);
39
37
  return {
40
38
  [keys.join('.')]: typeof rf === 'boolean' ? (rf ? 1 : 0) : rf
41
39
  };
42
40
  }
43
41
 
44
42
  static uuid(val: string): Binary {
45
- return new Binary(Buffer.from(val.replace(/-/g, ''), 'hex'), Binary.SUBTYPE_UUID);
43
+ return new Binary(Buffer.from(val.replaceAll('-', ''), 'hex'), Binary.SUBTYPE_UUID);
46
44
  }
47
45
 
48
46
  static idToString(id: string | ObjectId | Binary): string {
@@ -64,87 +62,45 @@ export class MongoUtil {
64
62
 
65
63
  static preInsertId<T extends ModelType>(item: T): T {
66
64
  if (item && item.id) {
67
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
68
- const itemWithId = item as WithId<T>;
65
+ const itemWithId: WithId<T> = castTo(item);
69
66
  itemWithId._id = this.uuid(item.id);
70
67
  }
71
68
  return item;
72
69
  }
73
70
 
74
- static prepareQuery<T extends ModelType, U extends Query<T> | ModelQuery<T>>(cls: Class<T>, query: U, checkExpiry = true): {
75
- query: U & { where: WhereClause<T> };
76
- filter: Record<string, unknown>;
77
- } {
78
- const q = ModelQueryUtil.getQueryAndVerify(cls, query, checkExpiry);
79
- return {
80
- query: q,
81
- filter: q.where ? this.extractWhereClause(cls, q.where) : {}
82
- };
71
+ static extractWhereFilter<T extends ModelType, U extends WhereClause<T>>(cls: Class<T>, where?: U, checkExpiry = true): Record<string, unknown> {
72
+ where = castTo(ModelQueryUtil.getWhereClause(cls, where, checkExpiry));
73
+ return where ? this.extractWhereClause(cls, where) : {};
83
74
  }
84
75
 
85
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
86
- static has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
87
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
88
- static has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
89
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
90
- static has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
91
-
92
76
  /**
93
77
  * Build mongo where clause
94
78
  */
95
79
  static extractWhereClause<T>(cls: Class<T>, o: WhereClause<T>): Record<string, unknown> {
96
- if (this.has$And(o)) {
80
+ if (ModelQueryUtil.has$And(o)) {
97
81
  return { $and: o.$and.map(x => this.extractWhereClause<T>(cls, x)) };
98
- } else if (this.has$Or(o)) {
82
+ } else if (ModelQueryUtil.has$Or(o)) {
99
83
  return { $or: o.$or.map(x => this.extractWhereClause<T>(cls, x)) };
100
- } else if (this.has$Not(o)) {
84
+ } else if (ModelQueryUtil.has$Not(o)) {
101
85
  return { $nor: [this.extractWhereClause<T>(cls, o.$not)] };
102
86
  } else {
103
87
  return this.extractSimple(cls, o);
104
88
  }
105
89
  }
106
90
 
107
- /**
108
- * Convert ids from '_id' to 'id'
109
- */
110
- static replaceId(v: Record<string, unknown>): Record<string, Binary>;
111
- static replaceId(v: string[]): Binary[];
112
- static replaceId(v: string): Binary;
113
- static replaceId(v: unknown): undefined;
114
- static replaceId(v: string | string[] | Record<string, unknown> | unknown): unknown {
115
- if (typeof v === 'string') {
116
- return this.uuid(v);
117
- } else if (Array.isArray(v)) {
118
- return v.map(x => this.replaceId(x));
119
- } else if (DataUtil.isPlainObject(v)) {
120
- const out: Record<string, Binary> = {};
121
- for (const [k, el] of Object.entries(v)) {
122
- const found = this.replaceId(el);
123
- if (found) {
124
- out[k] = found;
125
- }
126
- }
127
- return out;
128
- } else {
129
- return v;
130
- }
131
- }
132
-
133
91
  /**/
134
92
  static extractSimple<T>(base: Class<T> | undefined, o: Record<string, unknown>, path: string = '', recursive: boolean = true): Record<string, unknown> {
135
93
  const schema = base ? SchemaRegistry.get(base) : undefined;
136
94
  const out: Record<string, unknown> = {};
137
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
138
- const sub = o as Record<string, unknown>;
95
+ const sub = o;
139
96
  const keys = Object.keys(sub);
140
97
  for (const key of keys) {
141
98
  const subpath = `${path}${key}`;
142
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
143
- const v = sub[key] as Record<string, unknown>;
99
+ const v: Record<string, unknown> = castTo(sub[key]);
144
100
  const subField = schema?.views[AllViewⲐ].schema[key];
145
101
 
146
102
  if (subpath === 'id') { // Handle ids directly
147
- out._id = this.replaceId(v);
103
+ out._id = typeof v === 'string' ? this.uuid(v) : v;
148
104
  } else {
149
105
  const isPlain = v && DataUtil.isPlainObject(v);
150
106
  const firstKey = isPlain ? Object.keys(v)[0] : '';
@@ -170,18 +126,14 @@ export class MongoUtil {
170
126
  v.$nin = [null, []];
171
127
  }
172
128
  } else if (firstKey === '$regex') {
173
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
174
- v.$regex = DataUtil.toRegex(v.$regex as string | RegExp);
129
+ v.$regex = DataUtil.toRegex(castTo(v.$regex));
175
130
  } else if (firstKey && '$near' in v) {
176
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
177
- const dist = v.$maxDistance as number;
178
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
179
- const distance = dist / RADIANS_TO[(v.$unit as DistanceUnit ?? 'km')];
131
+ const dist: number = castTo(v.$maxDistance);
132
+ const distance = dist / RADIANS_TO[(castTo<DistanceUnit>(v.$unit) ?? 'km')];
180
133
  v.$maxDistance = distance;
181
134
  delete v.$unit;
182
135
  } else if (firstKey && '$geoWithin' in v) {
183
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
184
- const coords = v.$geoWithin as [number, number][];
136
+ const coords: [number, number][] = castTo(v.$geoWithin);
185
137
  const first = coords[0];
186
138
  const last = coords[coords.length - 1];
187
139
  // Connect if not
package/src/service.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  // Wildcard import needed here due to packaging issues
2
2
  import {
3
3
  type Db, GridFSBucket, MongoClient, type Sort, type CreateIndexesOptions,
4
- type GridFSFile, type IndexSpecification, type Collection, type ObjectId, type Filter, type Document
4
+ type GridFSFile, type IndexSpecification, type Collection, ObjectId,
5
+ Binary
5
6
  } from 'mongodb';
6
7
  import { Readable } from 'node:stream';
7
8
  import { pipeline } from 'node:stream/promises';
@@ -16,12 +17,13 @@ import {
16
17
  } from '@travetto/model';
17
18
  import {
18
19
  ModelQuery, ModelQueryCrudSupport, ModelQueryFacetSupport, ModelQuerySupport,
19
- PageableModelQuery, ValidStringFields, WhereClause, ModelQuerySuggestSupport
20
+ PageableModelQuery, ValidStringFields, WhereClause, ModelQuerySuggestSupport,
21
+ QueryVerifier
20
22
  } from '@travetto/model-query';
21
23
 
22
- import { ShutdownManager, type Class, AppError, TypedObject } from '@travetto/base';
24
+ import { ShutdownManager, type Class, type DeepPartial, AppError, TypedObject, castTo, asFull } from '@travetto/runtime';
23
25
  import { Injectable } from '@travetto/di';
24
- import { DeepPartial, FieldConfig, SchemaRegistry, SchemaValidator } from '@travetto/schema';
26
+ import { FieldConfig, SchemaRegistry, SchemaValidator } from '@travetto/schema';
25
27
 
26
28
  import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
27
29
  import { ModelIndexedUtil } from '@travetto/model/src/internal/service/indexed';
@@ -40,8 +42,7 @@ import { MongoModelConfig } from './config';
40
42
 
41
43
  const IdxFieldsⲐ = Symbol.for('@travetto/model-mongo:idx');
42
44
 
43
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
44
- const asFielded = <T extends ModelType>(cfg: IndexConfig<T>): { [IdxFieldsⲐ]: Sort } => (cfg as unknown as { [IdxFieldsⲐ]: Sort });
45
+ const asFielded = (cfg: IndexConfig<ModelType>): { [IdxFieldsⲐ]: Sort } => castTo(cfg);
45
46
 
46
47
  type IdxCfg = CreateIndexesOptions;
47
48
 
@@ -66,8 +67,7 @@ export class MongoModelService implements
66
67
  constructor(public readonly config: MongoModelConfig) { }
67
68
 
68
69
  async #describeStreamRaw(location: string): Promise<StreamRaw> {
69
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
70
- const files: StreamRaw[] = (await this.#bucket.find({ filename: location }, { limit: 1 }).toArray()) as StreamRaw[];
70
+ const files: StreamRaw[] = castTo(await this.#bucket.find({ filename: location }, { limit: 1 }).toArray());
71
71
 
72
72
  if (!files?.length) {
73
73
  throw new NotFoundError(STREAMS, location);
@@ -88,8 +88,8 @@ export class MongoModelService implements
88
88
  ModelExpiryUtil.registerCull(this);
89
89
  }
90
90
 
91
- getWhere<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Record<string, unknown> {
92
- return MongoUtil.prepareQuery(cls, { where }, checkExpiry).filter;
91
+ getWhereFilter<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Record<string, unknown> {
92
+ return MongoUtil.extractWhereFilter(cls, where, checkExpiry);
93
93
  }
94
94
 
95
95
  // Storage
@@ -117,14 +117,13 @@ export class MongoModelService implements
117
117
  return out;
118
118
  }
119
119
 
120
- getIndicies<T extends ModelType>(cls: Class<T>): ([IndexSpecification] | [IndexSpecification, IdxCfg])[] {
120
+ getIndices<T extends ModelType>(cls: Class<T>): ([IndexSpecification] | [IndexSpecification, IdxCfg])[] {
121
121
  const indices = ModelRegistry.get(cls).indices ?? [];
122
122
  return [
123
123
  ...indices.map((idx): [IndexSpecification, IdxCfg] => {
124
124
  const combined = asFielded(idx)[IdxFieldsⲐ] ??= Object.assign({}, ...idx.fields.map(x => MongoUtil.toIndex(x)));
125
125
  return [
126
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
127
- combined as IndexSpecification,
126
+ castTo(combined),
128
127
  (idx.type === 'unique' ? { unique: true } : {})
129
128
  ];
130
129
  }),
@@ -134,7 +133,7 @@ export class MongoModelService implements
134
133
 
135
134
  async establishIndices<T extends ModelType>(cls: Class<T>): Promise<void> {
136
135
  const col = await this.getStore(cls);
137
- const creating = this.getIndicies(cls);
136
+ const creating = this.getIndices(cls);
138
137
  if (creating.length) {
139
138
  console.debug('Creating indexes', { indices: creating });
140
139
  for (const el of creating) {
@@ -172,7 +171,7 @@ export class MongoModelService implements
172
171
  // Crud
173
172
  async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
174
173
  const store = await this.getStore(cls);
175
- const result = await store.findOne(this.getWhere<ModelType>(cls, { id }), {});
174
+ const result = await store.findOne(this.getWhereFilter<ModelType>(cls, { id }), {});
176
175
  if (result) {
177
176
  const res = await ModelCrudUtil.load(cls, result);
178
177
  if (res) {
@@ -183,24 +182,22 @@ export class MongoModelService implements
183
182
  }
184
183
 
185
184
  async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
186
- const cleaned = await ModelCrudUtil.preStore(cls, item, this);
187
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
188
- (cleaned as WithId<T>)._id = MongoUtil.uuid(cleaned.id);
185
+ const cleaned: WithId<T, Binary> = castTo(await ModelCrudUtil.preStore(cls, item, this));
186
+ cleaned._id = MongoUtil.uuid(cleaned.id);
189
187
 
190
188
  const store = await this.getStore(cls);
191
- const result = await store.insertOne(cleaned);
189
+ const result = await store.insertOne(castTo(cleaned));
192
190
  if (!result.insertedId) {
193
191
  throw new ExistsError(cls, cleaned.id);
194
192
  }
195
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
196
- delete (cleaned as { _id?: unknown })._id;
193
+ delete cleaned._id;
197
194
  return cleaned;
198
195
  }
199
196
 
200
197
  async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
201
198
  item = await ModelCrudUtil.preStore(cls, item, this);
202
199
  const store = await this.getStore(cls);
203
- const res = await store.replaceOne(this.getWhere<ModelType>(cls, { id: item.id }), item);
200
+ const res = await store.replaceOne(this.getWhereFilter<ModelType>(cls, { id: item.id }), item);
204
201
  if (res.matchedCount === 0) {
205
202
  throw new NotFoundError(cls, item.id);
206
203
  }
@@ -212,7 +209,7 @@ export class MongoModelService implements
212
209
  const store = await this.getStore(cls);
213
210
  try {
214
211
  await store.updateOne(
215
- this.getWhere<ModelType>(cls, { id: cleaned.id }, false),
212
+ this.getWhereFilter<ModelType>(cls, { id: cleaned.id }, false),
216
213
  { $set: cleaned },
217
214
  { upsert: true }
218
215
  );
@@ -240,15 +237,11 @@ export class MongoModelService implements
240
237
  const items = MongoUtil.extractSimple(cls, final, undefined, false);
241
238
  final = Object
242
239
  .entries(items)
243
- .reduce<Record<string, unknown>>((acc, [k, v]) => {
240
+ .reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>((acc, [k, v]) => {
244
241
  if (v === null || v === undefined) {
245
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
246
- const o = (acc.$unset ??= {}) as Record<string, unknown>;
247
- o[k] = v;
242
+ (acc.$unset ??= {})[k] = v;
248
243
  } else {
249
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
250
- const o = (acc.$set ??= {}) as Record<string, unknown>;
251
- o[k] = v;
244
+ (acc.$set ??= {})[k] = v;
252
245
  }
253
246
  return acc;
254
247
  }, {});
@@ -256,7 +249,7 @@ export class MongoModelService implements
256
249
  const id = item.id;
257
250
 
258
251
  const res = await store.findOneAndUpdate(
259
- this.getWhere<ModelType>(cls, { id }),
252
+ this.getWhereFilter<ModelType>(cls, { id }),
260
253
  final,
261
254
  { returnDocument: 'after', includeResultMetadata: true }
262
255
  );
@@ -270,7 +263,7 @@ export class MongoModelService implements
270
263
 
271
264
  async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
272
265
  const store = await this.getStore(cls);
273
- const result = await store.deleteOne(this.getWhere<ModelType>(cls, { id }, false));
266
+ const result = await store.deleteOne(this.getWhereFilter<ModelType>(cls, { id }, false));
274
267
  if (result.deletedCount === 0) {
275
268
  throw new NotFoundError(cls, id);
276
269
  }
@@ -278,7 +271,7 @@ export class MongoModelService implements
278
271
 
279
272
  async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
280
273
  const store = await this.getStore(cls);
281
- const cursor = store.find(this.getWhere(cls, {}), { timeout: true }).batchSize(100);
274
+ const cursor = store.find(this.getWhereFilter(cls, {}), { timeout: true }).batchSize(100);
282
275
  for await (const el of cursor) {
283
276
  try {
284
277
  yield MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el));
@@ -350,8 +343,7 @@ export class MongoModelService implements
350
343
 
351
344
  for (const op of operations) {
352
345
  if (op.insert) {
353
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
354
- bulk.insert(MongoUtil.preInsertId(op.insert as T));
346
+ bulk.insert(MongoUtil.preInsertId(asFull(op.insert)));
355
347
  } else if (op.upsert) {
356
348
  bulk.find({ _id: MongoUtil.uuid(op.upsert.id!) }).upsert().updateOne({ $set: op.upsert });
357
349
  } else if (op.update) {
@@ -365,13 +357,11 @@ export class MongoModelService implements
365
357
 
366
358
  for (const op of operations) {
367
359
  if (op.insert) {
368
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
369
- MongoUtil.postLoadId(op.insert as T);
360
+ MongoUtil.postLoadId(asFull(op.insert));
370
361
  }
371
362
  }
372
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
373
- for (const [index, _id] of TypedObject.entries(res.upsertedIds) as [number, ObjectId][]) {
374
- out.insertedIds.set(+index, MongoUtil.idToString(_id));
363
+ for (const [index, _id] of TypedObject.entries(res.upsertedIds)) {
364
+ out.insertedIds.set(+index, MongoUtil.idToString(castTo(_id)));
375
365
  }
376
366
 
377
367
  if (out.counts) {
@@ -385,8 +375,7 @@ export class MongoModelService implements
385
375
  out.errors = res.getWriteErrors();
386
376
  for (const err of out.errors) {
387
377
  const op = operations[err.index];
388
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
389
- const k = Object.keys(op)[0] as keyof BulkResponse['counts'];
378
+ const k = TypedObject.keys(op)[0];
390
379
  out.counts[k] -= 1;
391
380
  }
392
381
  out.counts.error = out.errors.length;
@@ -405,10 +394,9 @@ export class MongoModelService implements
405
394
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
406
395
  const store = await this.getStore(cls);
407
396
  const result = await store.findOne(
408
- this.getWhere(
397
+ this.getWhereFilter(
409
398
  cls,
410
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
411
- ModelIndexedUtil.projectIndex(cls, idx, body) as WhereClause<T>
399
+ castTo(ModelIndexedUtil.projectIndex(cls, idx, body))
412
400
  )
413
401
  );
414
402
  if (!result) {
@@ -421,10 +409,9 @@ export class MongoModelService implements
421
409
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
422
410
  const store = await this.getStore(cls);
423
411
  const result = await store.deleteOne(
424
- this.getWhere(
412
+ this.getWhereFilter(
425
413
  cls,
426
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
427
- ModelIndexedUtil.projectIndex(cls, idx, body) as WhereClause<T>
414
+ castTo(ModelIndexedUtil.projectIndex(cls, idx, body))
428
415
  )
429
416
  );
430
417
  if (result.deletedCount) {
@@ -445,25 +432,24 @@ export class MongoModelService implements
445
432
  throw new AppError('Cannot list on unique indices', 'data');
446
433
  }
447
434
 
448
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
449
- const where = this.getWhere(
435
+ const where = this.getWhereFilter(
450
436
  cls,
451
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
452
- ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }) as WhereClause<T>
453
- ) as Filter<Document>;
437
+ castTo(ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }))
438
+ );
454
439
 
455
440
  const cursor = store.find(where, { timeout: true }).batchSize(100).sort(asFielded(idxCfg)[IdxFieldsⲐ]);
456
441
 
457
442
  for await (const el of cursor) {
458
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
459
- yield (await MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el))) as T;
443
+ yield (await MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el)));
460
444
  }
461
445
  }
462
446
 
463
447
  // Query
464
448
  async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
449
+ await QueryVerifier.verify(cls, query);
450
+
465
451
  const col = await this.getStore(cls);
466
- const { filter } = MongoUtil.prepareQuery(cls, query);
452
+ const filter = MongoUtil.extractWhereFilter(cls, query.where);
467
453
  let cursor = col.find<T>(filter, {});
468
454
  if (query.select) {
469
455
  const selectKey = Object.keys(query.select)[0];
@@ -493,8 +479,10 @@ export class MongoModelService implements
493
479
  }
494
480
 
495
481
  async queryCount<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
482
+ await QueryVerifier.verify(cls, query);
483
+
496
484
  const col = await this.getStore(cls);
497
- const { filter } = MongoUtil.prepareQuery(cls, query);
485
+ const filter = MongoUtil.extractWhereFilter(cls, query.where);
498
486
  return col.countDocuments(filter);
499
487
  }
500
488
 
@@ -505,11 +493,14 @@ export class MongoModelService implements
505
493
 
506
494
  // Query Crud
507
495
  async updateOneWithQuery<T extends ModelType>(cls: Class<T>, data: T, query: ModelQuery<T>): Promise<T> {
496
+ await QueryVerifier.verify(cls, query);
497
+
508
498
  const col = await this.getStore(cls);
509
499
  const item = await ModelCrudUtil.preStore(cls, data, this);
510
- query = ModelQueryUtil.getQueryWithId(cls, data, query);
500
+ const where = ModelQueryUtil.getWhereClause(cls, query.where);
501
+ where.id = item.id;
511
502
 
512
- const { filter } = MongoUtil.prepareQuery(cls, query);
503
+ const filter = MongoUtil.extractWhereFilter(cls, where);
513
504
  const res = await col.replaceOne(filter, item);
514
505
  if (res.matchedCount === 0) {
515
506
  throw new NotFoundError(cls, item.id);
@@ -518,57 +509,62 @@ export class MongoModelService implements
518
509
  }
519
510
 
520
511
  async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
512
+ await QueryVerifier.verify(cls, query);
513
+
521
514
  const col = await this.getStore(cls);
522
- const { filter } = MongoUtil.prepareQuery(cls, query, false);
515
+ const filter = MongoUtil.extractWhereFilter(cls, query.where, false);
523
516
  const res = await col.deleteMany(filter);
524
517
  return res.deletedCount ?? 0;
525
518
  }
526
519
 
527
520
  async updateByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>): Promise<number> {
528
- const col = await this.getStore(cls);
521
+ await QueryVerifier.verify(cls, query);
529
522
 
523
+ const col = await this.getStore(cls);
530
524
  const items = MongoUtil.extractSimple(cls, data);
531
- const final = Object.entries(items).reduce<Record<string, unknown>>((acc, [k, v]) => {
532
- if (v === null || v === undefined) {
533
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
534
- const o = (acc.$unset = acc.$unset ?? {}) as Record<string, unknown>;
535
- o[k] = v;
536
- } else {
537
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
538
- const o = (acc.$set = acc.$set ?? {}) as Record<string, unknown>;
539
- o[k] = v;
540
- }
541
- return acc;
542
- }, {});
525
+ const final = Object.entries(items).reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>(
526
+ (acc, [k, v]) => {
527
+ if (v === null || v === undefined) {
528
+ (acc.$unset ??= {})[k] = v;
529
+ } else {
530
+ (acc.$set ??= {})[k] = v;
531
+ }
532
+ return acc;
533
+ }, {});
543
534
 
544
- const { filter } = MongoUtil.prepareQuery(cls, query);
535
+ const filter = MongoUtil.extractWhereFilter(cls, query.where);
545
536
  const res = await col.updateMany(filter, final);
546
537
  return res.matchedCount;
547
538
  }
548
539
 
549
540
  // Facet
550
541
  async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<{ key: string, count: number }[]> {
542
+ await QueryVerifier.verify(cls, query);
543
+
551
544
  const col = await this.getStore(cls);
552
- const aggs: object[] = [{
553
- $group: {
554
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
555
- _id: `$${field as string}`,
556
- count: {
557
- $sum: 1
558
- }
559
- }
560
- }];
545
+ if (query) {
546
+ await QueryVerifier.verify(cls, query);
547
+ }
561
548
 
562
549
  let q: Record<string, unknown> = { [field]: { $exists: true } };
563
550
 
564
551
  if (query?.where) {
565
- q = { $and: [q, MongoUtil.prepareQuery(cls, query).filter] };
552
+ q = { $and: [q, MongoUtil.extractWhereFilter(cls, query.where)] };
566
553
  }
567
554
 
568
- aggs.unshift({ $match: q });
555
+ const aggregations: object[] = [
556
+ { $match: q },
557
+ {
558
+ $group: {
559
+ _id: `$${field}`,
560
+ count: {
561
+ $sum: 1
562
+ }
563
+ }
564
+ }
565
+ ];
569
566
 
570
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
571
- const result = (await col.aggregate(aggs).toArray()) as { _id: ObjectId, count: number }[];
567
+ const result = await col.aggregate<{ _id: ObjectId, count: number }>(aggregations).toArray();
572
568
 
573
569
  return result.map(val => ({
574
570
  key: MongoUtil.idToString(val._id),
@@ -578,12 +574,14 @@ export class MongoModelService implements
578
574
 
579
575
  // Suggest
580
576
  async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
577
+ await QueryVerifier.verify(cls, query);
581
578
  const q = ModelQuerySuggestUtil.getSuggestFieldQuery<T>(cls, field, prefix, query);
582
579
  const results = await this.query<T>(cls, q);
583
580
  return ModelQuerySuggestUtil.combineSuggestResults<T, string>(cls, field, prefix, results, (a) => a, query && query.limit);
584
581
  }
585
582
 
586
583
  async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
584
+ await QueryVerifier.verify(cls, query);
587
585
  const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
588
586
  const results = await this.query<T>(cls, q);
589
587
  return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, results, (_, b) => b, query && query.limit);