@travetto/model-mongo 2.1.5 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,10 +10,10 @@ npm install @travetto/model-mongo
10
10
 
11
11
  This module provides an [mongodb](https://mongodb.com)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [mongodb](https://mongodb.com).. Given the dynamic nature of [mongodb](https://mongodb.com), during development when models are modified, nothing needs to be done to adapt to the latest schema.
12
12
 
13
- Supported featrues:
13
+ Supported features:
14
14
 
15
15
  * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
16
- * [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L1)
16
+ * [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L3)
17
17
  * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11)
18
18
  * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L23)
19
19
  * [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/service/indexed.ts#L12)
@@ -95,22 +95,22 @@ export class MongoModelConfig {
95
95
  };
96
96
 
97
97
  /**
98
- * Should we autocreate the db
98
+ * Should we auto create the db
99
99
  */
100
100
  autoCreate?: boolean;
101
101
 
102
102
  /**
103
- * Frequency of culling for expirable content
103
+ * Frequency of culling for cullable content
104
104
  */
105
105
  cullRate?: number | TimeSpan;
106
106
 
107
107
  /**
108
108
  * Load a resource
109
109
  */
110
- async fetch(val: string) {
110
+ async fetch(val: string): Promise<string> {
111
111
  return ResourceManager.read(val)
112
112
  .then(res => typeof res === 'string' ? res : res.toString('utf8'))
113
- .catch(e => fs.readFile(val)
113
+ .catch(() => fs.readFile(val)
114
114
  .then(res => typeof res === 'string' ? res : res.toString('utf8'))
115
115
  )
116
116
  .catch(() => val);
@@ -119,7 +119,7 @@ export class MongoModelConfig {
119
119
  /**
120
120
  * Load all the ssl certs as needed
121
121
  */
122
- async postConstruct() {
122
+ async postConstruct(): Promise<void> {
123
123
  const opts = this.options;
124
124
  if (opts.ssl) {
125
125
  if (opts.sslCert) {
@@ -140,7 +140,7 @@ export class MongoModelConfig {
140
140
  /**
141
141
  * Build connection URLs
142
142
  */
143
- get url() {
143
+ get url(): string {
144
144
  const hosts = this.hosts
145
145
  .map(h => (this.srvRecord || h.includes(':')) ? h : `${h}:${this.port}`)
146
146
  .join(',');
@@ -159,4 +159,4 @@ export class MongoModelConfig {
159
159
  standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Environment-aware config management using yaml files")resolution paths.
160
160
 
161
161
 
162
- The SSL file options in `clientOptions` will automatically be resolved to files when given a path. This path can be a [ResourceManager](https://github.com/travetto/travetto/tree/main/module/base/src/resource.ts#L14) path or just a standard file path.
162
+ The SSL file options in `clientOptions` will automatically be resolved to files when given a path. This path can be a [ResourceManager](https://github.com/travetto/travetto/tree/main/module/base/src/resource.ts#L16) path or just a standard file path.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
3
  "displayName": "MongoDB Model Support",
4
- "version": "2.1.5",
4
+ "version": "2.2.2",
5
5
  "description": "Mongo backing for the travetto model module.",
6
6
  "keywords": [
7
7
  "mongo",
@@ -26,9 +26,9 @@
26
26
  "directory": "module/model-mongo"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/config": "^2.1.5",
30
- "@travetto/model": "^2.1.5",
31
- "@travetto/model-query": "2.1.5",
29
+ "@travetto/config": "^2.2.2",
30
+ "@travetto/model": "^2.2.2",
31
+ "@travetto/model-query": "2.2.2",
32
32
  "mongodb": "^4.8.0"
33
33
  },
34
34
  "publishConfig": {
package/src/config.ts CHANGED
@@ -47,22 +47,22 @@ export class MongoModelConfig {
47
47
  };
48
48
 
49
49
  /**
50
- * Should we autocreate the db
50
+ * Should we auto create the db
51
51
  */
52
52
  autoCreate?: boolean;
53
53
 
54
54
  /**
55
- * Frequency of culling for expirable content
55
+ * Frequency of culling for cullable content
56
56
  */
57
57
  cullRate?: number | TimeSpan;
58
58
 
59
59
  /**
60
60
  * Load a resource
61
61
  */
62
- async fetch(val: string) {
62
+ async fetch(val: string): Promise<string> {
63
63
  return ResourceManager.read(val)
64
64
  .then(res => typeof res === 'string' ? res : res.toString('utf8'))
65
- .catch(e => fs.readFile(val)
65
+ .catch(() => fs.readFile(val)
66
66
  .then(res => typeof res === 'string' ? res : res.toString('utf8'))
67
67
  )
68
68
  .catch(() => val);
@@ -71,7 +71,7 @@ export class MongoModelConfig {
71
71
  /**
72
72
  * Load all the ssl certs as needed
73
73
  */
74
- async postConstruct() {
74
+ async postConstruct(): Promise<void> {
75
75
  const opts = this.options;
76
76
  if (opts.ssl) {
77
77
  if (opts.sslCert) {
@@ -92,7 +92,7 @@ export class MongoModelConfig {
92
92
  /**
93
93
  * Build connection URLs
94
94
  */
95
- get url() {
95
+ get url(): string {
96
96
  const hosts = this.hosts
97
97
  .map(h => (this.srvRecord || h.includes(':')) ? h : `${h}:${this.port}`)
98
98
  .join(',');
@@ -17,29 +17,35 @@ const RADIANS_TO: Record<DistanceUnit, number> = {
17
17
  rad: 1
18
18
  };
19
19
 
20
- export type WithId<T> = T & { _id: mongo.Binary };
20
+ export type WithId<T> = T & { _id?: mongo.Binary };
21
+ const isWithId = <T extends ModelType>(o: T): o is WithId<T> => o && '_id' in o;
22
+
21
23
 
22
24
  /**
23
25
  * Basic mongo utils for conforming to the model module
24
26
  */
25
27
  export class MongoUtil {
26
28
 
27
- static toIndex<T extends ModelType>(f: IndexField<T>) {
29
+ static toIndex<T extends ModelType>(f: IndexField<T>): Record<string, number> {
28
30
  const keys = [];
29
31
  while (typeof f !== 'number' && typeof f !== 'boolean' && Object.keys(f)) {
30
32
  const key = Object.keys(f)[0];
33
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
31
34
  f = f[key as keyof typeof f] as IndexField<T>;
32
35
  keys.push(key);
33
36
  }
37
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
34
38
  const rf = f as unknown as (number | boolean);
35
- return { [keys.join('.')]: rf === true ? 1 : rf } as Record<string, number>;
39
+ return {
40
+ [keys.join('.')]: typeof rf === 'boolean' ? (rf ? 1 : 0) : rf
41
+ };
36
42
  }
37
43
 
38
- static uuid(val: string) {
44
+ static uuid(val: string): mongo.Binary {
39
45
  return new mongo.Binary(Buffer.from(val.replace(/-/g, ''), 'hex'), mongo.Binary.SUBTYPE_UUID);
40
46
  }
41
47
 
42
- static idToString(id: string | mongo.ObjectId | mongo.Binary) {
48
+ static idToString(id: string | mongo.ObjectId | mongo.Binary): string {
43
49
  if (typeof id === 'string') {
44
50
  return id;
45
51
  } else if (id instanceof mongo.ObjectId) {
@@ -49,32 +55,41 @@ export class MongoUtil {
49
55
  }
50
56
  }
51
57
 
52
- static async postLoadId<T extends ModelType>(item: T) {
53
- if (item && '_id' in item) {
54
- item.id = this.idToString((item as WithId<T>)._id);
55
- delete (item as { _id?: unknown })._id;
58
+ static async postLoadId<T extends ModelType>(item: T): Promise<T> {
59
+ if (isWithId(item)) {
60
+ item.id = this.idToString(item._id!);
61
+ delete item._id;
56
62
  }
57
63
  return item;
58
64
  }
59
65
 
60
- static preInsertId<T extends ModelType>(item: T) {
66
+ static preInsertId<T extends ModelType>(item: T): T {
61
67
  if (item && item.id) {
62
- (item as WithId<T>)._id = this.uuid(item.id);
63
- delete (item as { id?: unknown }).id;
68
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
69
+ const itemWithId = item as WithId<T>;
70
+ itemWithId._id = this.uuid(item.id);
71
+ // @ts-expect-error
72
+ delete item.id;
64
73
  }
65
74
  return item;
66
75
  }
67
76
 
68
- static prepareQuery<T extends ModelType, U extends Query<T> | ModelQuery<T>>(cls: Class<T>, query: U, checkExpiry = true) {
77
+ static prepareQuery<T extends ModelType, U extends Query<T> | ModelQuery<T>>(cls: Class<T>, query: U, checkExpiry = true): {
78
+ query: U & { where: WhereClause<T> };
79
+ filter: Record<string, unknown>;
80
+ } {
69
81
  const q = ModelQueryUtil.getQueryAndVerify(cls, query, checkExpiry);
70
82
  return {
71
83
  query: q,
72
84
  filter: q.where ? this.extractWhereClause(q.where) : {}
73
- } as const;
85
+ };
74
86
  }
75
87
 
88
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
76
89
  static has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
90
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
77
91
  static has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
92
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
78
93
  static has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
79
94
 
80
95
  /**
@@ -99,7 +114,7 @@ export class MongoUtil {
99
114
  static replaceId(v: string[]): mongo.Binary[];
100
115
  static replaceId(v: string): mongo.Binary;
101
116
  static replaceId(v: unknown): undefined;
102
- static replaceId(v: string | string[] | Record<string, unknown> | unknown) {
117
+ static replaceId(v: string | string[] | Record<string, unknown> | unknown): unknown {
103
118
  if (typeof v === 'string') {
104
119
  return this.uuid(v);
105
120
  } else if (Array.isArray(v)) {
@@ -123,10 +138,12 @@ export class MongoUtil {
123
138
  */
124
139
  static extractSimple<T>(o: T, path: string = ''): Record<string, unknown> {
125
140
  const out: Record<string, unknown> = {};
141
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
126
142
  const sub = o as Record<string, unknown>;
127
143
  const keys = Object.keys(sub);
128
144
  for (const key of keys) {
129
145
  const subpath = `${path}${key}`;
146
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
130
147
  const v = sub[key] as Record<string, unknown>;
131
148
 
132
149
  if (subpath === 'id') { // Handle ids directly
@@ -142,13 +159,17 @@ export class MongoUtil {
142
159
  v[sk] = ModelQueryUtil.resolveComparator(sv);
143
160
  }
144
161
  } else if (firstKey === '$regex') {
162
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
145
163
  v.$regex = Util.toRegex(v.$regex as string | RegExp);
146
164
  } else if (firstKey && '$near' in v) {
165
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
147
166
  const dist = v.$maxDistance as number;
167
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
148
168
  const distance = dist / RADIANS_TO[(v.$unit as DistanceUnit ?? 'km')];
149
169
  v.$maxDistance = distance;
150
170
  delete v.$unit;
151
171
  } else if (firstKey && '$geoWithin' in v) {
172
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
152
173
  const coords = v.$geoWithin as [number, number][];
153
174
  const first = coords[0];
154
175
  const last = coords[coords.length - 1];
package/src/service.ts CHANGED
@@ -1,10 +1,12 @@
1
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
1
2
  import * as mongo from 'mongodb';
3
+ import { Readable } from 'stream';
2
4
 
3
5
  import {
4
6
  ModelRegistry, ModelType, OptionalId,
5
7
  ModelCrudSupport, ModelStorageSupport, ModelStreamSupport,
6
8
  ModelExpirySupport, ModelBulkSupport, ModelIndexedSupport,
7
- StreamMeta, IndexField, BulkOp, BulkResponse,
9
+ StreamMeta, BulkOp, BulkResponse,
8
10
  NotFoundError, ExistsError, IndexConfig
9
11
  } from '@travetto/model';
10
12
  import {
@@ -33,10 +35,13 @@ import { MongoModelConfig } from './config';
33
35
 
34
36
  const IdxFieldsⲐ = Symbol.for('@trv:model-mongo/idx');
35
37
 
36
- const asFielded = <T extends ModelType>(cfg: IndexConfig<T>) => (cfg as unknown as { [IdxFieldsⲐ]: mongo.Sort });
38
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
39
+ const asFielded = <T extends ModelType>(cfg: IndexConfig<T>): { [IdxFieldsⲐ]: mongo.Sort } => (cfg as unknown as { [IdxFieldsⲐ]: mongo.Sort });
37
40
 
38
41
  type IdxCfg = mongo.CreateIndexesOptions;
39
42
 
43
+ type StreamRaw = mongo.GridFSFile & { metadata: StreamMeta };
44
+
40
45
  /**
41
46
  * Mongo-based model source
42
47
  */
@@ -54,8 +59,9 @@ export class MongoModelService implements
54
59
 
55
60
  constructor(public readonly config: MongoModelConfig) { }
56
61
 
57
- async #describeStreamRaw(location: string) {
58
- const files = (await this.#bucket.find({ filename: location }, { limit: 1 }).toArray()) as (mongo.GridFSFile & { metadata: StreamMeta })[];
62
+ async #describeStreamRaw(location: string): Promise<StreamRaw> {
63
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
64
+ const files: StreamRaw[] = (await this.#bucket.find({ filename: location }, { limit: 1 }).toArray()) as StreamRaw[];
59
65
 
60
66
  if (!files?.length) {
61
67
  throw new NotFoundError(STREAMS, location);
@@ -64,7 +70,7 @@ export class MongoModelService implements
64
70
  return files[0];
65
71
  }
66
72
 
67
- async postConstruct() {
73
+ async postConstruct(): Promise<void> {
68
74
  this.client = await mongo.MongoClient.connect(this.config.url, this.config.options);
69
75
  this.#db = this.client.db(this.config.namespace);
70
76
  this.#bucket = new mongo.GridFSBucket(this.#db, {
@@ -76,21 +82,21 @@ export class MongoModelService implements
76
82
  ModelExpiryUtil.registerCull(this);
77
83
  }
78
84
 
79
- getWhere<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true) {
85
+ getWhere<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Record<string, unknown> {
80
86
  return MongoUtil.prepareQuery(cls, { where }, checkExpiry).filter;
81
87
  }
82
88
 
83
89
  /**
84
90
  * Build a mongo identifier
85
91
  */
86
- uuid() {
92
+ uuid(): string {
87
93
  return Util.uuid();
88
94
  }
89
95
 
90
96
  // Storage
91
- async createStorage() { }
97
+ async createStorage(): Promise<void> { }
92
98
 
93
- async deleteStorage() {
99
+ async deleteStorage(): Promise<void> {
94
100
  await this.#db.dropDatabase();
95
101
  }
96
102
 
@@ -112,21 +118,22 @@ export class MongoModelService implements
112
118
  return out;
113
119
  }
114
120
 
115
- getIndicies<T extends ModelType>(cls: Class<T>) {
121
+ getIndicies<T extends ModelType>(cls: Class<T>): ([mongo.IndexSpecification] | [mongo.IndexSpecification, IdxCfg])[] {
116
122
  const indices = ModelRegistry.get(cls).indices ?? [];
117
123
  return [
118
- ...indices.map(idx => {
119
- const combined = asFielded(idx)[IdxFieldsⲐ] ??= Object.assign({}, ...idx.fields.map(x => MongoUtil.toIndex(x as IndexField<T>)));
124
+ ...indices.map((idx): [mongo.IndexSpecification, IdxCfg] => {
125
+ const combined = asFielded(idx)[IdxFieldsⲐ] ??= Object.assign({}, ...idx.fields.map(x => MongoUtil.toIndex(x)));
120
126
  return [
127
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
121
128
  combined as mongo.IndexSpecification,
122
- (idx.type === 'unique' ? { unique: true } : {}) as IdxCfg
123
- ] as const;
129
+ (idx.type === 'unique' ? { unique: true } : {})
130
+ ];
124
131
  }),
125
- ...this.getGeoIndices(cls).map(x => [x] as const)
132
+ ...this.getGeoIndices(cls).map((x): [mongo.IndexSpecification] => [x])
126
133
  ];
127
134
  }
128
135
 
129
- async establishIndices<T extends ModelType>(cls: Class<T>) {
136
+ async establishIndices<T extends ModelType>(cls: Class<T>): Promise<void> {
130
137
  const col = await this.getStore(cls);
131
138
  const creating = this.getIndicies(cls);
132
139
  if (creating.length) {
@@ -137,15 +144,15 @@ export class MongoModelService implements
137
144
  }
138
145
  }
139
146
 
140
- async createModel(cls: Class) {
147
+ async createModel(cls: Class): Promise<void> {
141
148
  await this.establishIndices(cls);
142
149
  }
143
150
 
144
- async changeModel(cls: Class) {
151
+ async changeModel(cls: Class): Promise<void> {
145
152
  await this.establishIndices(cls);
146
153
  }
147
154
 
148
- async truncateModel<T extends ModelType>(cls: Class<T>) {
155
+ async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
149
156
  if (cls === StreamModel) {
150
157
  try {
151
158
  await this.#bucket.drop();
@@ -164,7 +171,7 @@ export class MongoModelService implements
164
171
  }
165
172
 
166
173
  // Crud
167
- async get<T extends ModelType>(cls: Class<T>, id: string) {
174
+ async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
168
175
  const store = await this.getStore(cls);
169
176
  const result = await store.findOne(this.getWhere<ModelType>(cls, { id }), {});
170
177
  if (result) {
@@ -176,8 +183,9 @@ export class MongoModelService implements
176
183
  throw new NotFoundError(cls, id);
177
184
  }
178
185
 
179
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
186
+ async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
180
187
  const cleaned = await ModelCrudUtil.preStore(cls, item, this);
188
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
181
189
  (cleaned as WithId<T>)._id = MongoUtil.uuid(cleaned.id);
182
190
 
183
191
  const store = await this.getStore(cls);
@@ -189,7 +197,7 @@ export class MongoModelService implements
189
197
  return cleaned;
190
198
  }
191
199
 
192
- async update<T extends ModelType>(cls: Class<T>, item: T) {
200
+ async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
193
201
  item = await ModelCrudUtil.preStore(cls, item, this);
194
202
  const store = await this.getStore(cls);
195
203
  const res = await store.replaceOne(this.getWhere<ModelType>(cls, { id: item.id }), item);
@@ -199,7 +207,7 @@ export class MongoModelService implements
199
207
  return item;
200
208
  }
201
209
 
202
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
210
+ async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
203
211
  const cleaned = await ModelCrudUtil.preStore(cls, item, this);
204
212
  const store = await this.getStore(cls);
205
213
  try {
@@ -209,7 +217,7 @@ export class MongoModelService implements
209
217
  { upsert: true }
210
218
  );
211
219
  } catch (err) {
212
- if (err.message.includes('duplicate key error')) {
220
+ if (err instanceof Error && err.message.includes('duplicate key error')) {
213
221
  throw new ExistsError(cls, cleaned.id);
214
222
  } else {
215
223
  throw err;
@@ -218,7 +226,7 @@ export class MongoModelService implements
218
226
  return cleaned;
219
227
  }
220
228
 
221
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
229
+ async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
222
230
  const store = await this.getStore(cls);
223
231
 
224
232
  if (view) {
@@ -232,16 +240,18 @@ export class MongoModelService implements
232
240
  let final: Record<string, unknown> = item;
233
241
 
234
242
  const items = MongoUtil.extractSimple(final);
235
- final = Object.entries(items).reduce((acc, [k, v]) => {
236
- if (v === null || v === undefined) {
237
- const o = (acc.$unset = acc.$unset ?? {}) as Record<string, unknown>;
238
- o[k] = v;
239
- } else {
240
- const o = (acc.$set = acc.$set ?? {}) as Record<string, unknown>;
241
- o[k] = v;
242
- }
243
- return acc;
244
- }, {} as Record<string, unknown>);
243
+ final = Object
244
+ .entries(items)
245
+ .reduce<Record<string, unknown>>((acc, [k, v]) => {
246
+ if (v === null || v === undefined) {
247
+ const o = (acc.$unset ??= {}) as Record<string, unknown>;
248
+ o[k] = v;
249
+ } else {
250
+ const o = (acc.$set ??= {}) as Record<string, unknown>;
251
+ o[k] = v;
252
+ }
253
+ return acc;
254
+ }, {});
245
255
 
246
256
  const id = item.id;
247
257
  const res = await store.findOneAndUpdate(
@@ -257,7 +267,7 @@ export class MongoModelService implements
257
267
  return this.get(cls, id);
258
268
  }
259
269
 
260
- async delete<T extends ModelType>(cls: Class<T>, id: string) {
270
+ async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
261
271
  const store = await this.getStore(cls);
262
272
  const result = await store.deleteOne(this.getWhere<ModelType>(cls, { id }, false));
263
273
  if (result.deletedCount === 0) {
@@ -265,22 +275,22 @@ export class MongoModelService implements
265
275
  }
266
276
  }
267
277
 
268
- async * list<T extends ModelType>(cls: Class<T>) {
278
+ async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
269
279
  const store = await this.getStore(cls);
270
280
  const cursor = store.find(this.getWhere(cls, {}), { timeout: true }).batchSize(100);
271
281
  for await (const el of cursor) {
272
282
  try {
273
283
  yield MongoUtil.postLoadId(await ModelCrudUtil.load(cls, el));
274
- } catch (e) {
275
- if (!(e instanceof NotFoundError)) {
276
- throw e;
284
+ } catch (err) {
285
+ if (!(err instanceof NotFoundError)) {
286
+ throw err;
277
287
  }
278
288
  }
279
289
  }
280
290
  }
281
291
 
282
292
  // Stream
283
- async upsertStream(location: string, input: NodeJS.ReadableStream, meta: StreamMeta) {
293
+ async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
284
294
  const writeStream = this.#bucket.openUploadStream(location, {
285
295
  contentType: meta.contentType,
286
296
  metadata: meta
@@ -293,7 +303,7 @@ export class MongoModelService implements
293
303
  });
294
304
  }
295
305
 
296
- async getStream(location: string) {
306
+ async getStream(location: string): Promise<Readable> {
297
307
  await this.describeStream(location);
298
308
 
299
309
  const res = await this.#bucket.openDownloadStreamByName(location);
@@ -303,18 +313,18 @@ export class MongoModelService implements
303
313
  return res;
304
314
  }
305
315
 
306
- async describeStream(location: string) {
316
+ async describeStream(location: string): Promise<StreamMeta> {
307
317
  return (await this.#describeStreamRaw(location)).metadata;
308
318
  }
309
319
 
310
- async deleteStream(location: string) {
320
+ async deleteStream(location: string): Promise<void> {
311
321
  const fileId = (await this.#describeStreamRaw(location))._id;
312
322
  await this.#bucket.delete(fileId);
313
323
  }
314
324
 
315
325
  // Bulk
316
- async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[]) {
317
- const out: BulkResponse = {
326
+ async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[]): Promise<BulkResponse<{ index: number }>> {
327
+ const out: BulkResponse<{ index: number }> = {
318
328
  errors: [],
319
329
  counts: {
320
330
  delete: 0,
@@ -368,7 +378,7 @@ export class MongoModelService implements
368
378
 
369
379
  if (res.hasWriteErrors()) {
370
380
  out.errors = res.getWriteErrors();
371
- for (const err of out.errors as { index: number }[]) {
381
+ for (const err of out.errors) {
372
382
  const op = operations[err.index];
373
383
  const k = Object.keys(op)[0] as keyof BulkResponse['counts'];
374
384
  out.counts[k] -= 1;
@@ -380,12 +390,12 @@ export class MongoModelService implements
380
390
  }
381
391
 
382
392
  // Expiry
383
- deleteExpired<T extends ModelType>(cls: Class<T>) {
393
+ deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
384
394
  return ModelQueryExpiryUtil.deleteExpired(this, cls);
385
395
  }
386
396
 
387
397
  // Indexed
388
- async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
398
+ async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
389
399
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
390
400
  const store = await this.getStore(cls);
391
401
  const result = await store.findOne(
@@ -400,7 +410,7 @@ export class MongoModelService implements
400
410
  return await ModelCrudUtil.load(cls, result);
401
411
  }
402
412
 
403
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
413
+ async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
404
414
  const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
405
415
  const store = await this.getStore(cls);
406
416
  const result = await store.deleteOne(
@@ -419,7 +429,7 @@ export class MongoModelService implements
419
429
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
420
430
  }
421
431
 
422
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>) {
432
+ async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
423
433
  const store = await this.getStore(cls);
424
434
  const idxCfg = ModelRegistry.getIndex(cls, idx);
425
435
 
@@ -445,7 +455,8 @@ export class MongoModelService implements
445
455
  const { filter } = MongoUtil.prepareQuery(cls, query);
446
456
  let cursor = col.find<T>(filter, {});
447
457
  if (query.select) {
448
- const select = Object.keys(query.select)[0].startsWith('$') ? query.select : MongoUtil.extractSimple(query.select);
458
+ const selectKey = Object.keys(query.select)[0];
459
+ const select = typeof selectKey === 'string' && selectKey.startsWith('$') ? query.select : MongoUtil.extractSimple(query.select);
449
460
  // Remove id if not explicitly defined, and selecting fields directly
450
461
  if (!select['_id']) {
451
462
  const values = new Set([...Object.values(select)]);
@@ -489,11 +500,11 @@ export class MongoModelService implements
489
500
  return res.deletedCount ?? 0;
490
501
  }
491
502
 
492
- async updateByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>) {
503
+ async updateByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>): Promise<number> {
493
504
  const col = await this.getStore(cls);
494
505
 
495
506
  const items = MongoUtil.extractSimple(data);
496
- const final = Object.entries(items).reduce((acc, [k, v]) => {
507
+ const final = Object.entries(items).reduce<Record<string, unknown>>((acc, [k, v]) => {
497
508
  if (v === null || v === undefined) {
498
509
  const o = (acc.$unset = acc.$unset ?? {}) as Record<string, unknown>;
499
510
  o[k] = v;
@@ -502,7 +513,7 @@ export class MongoModelService implements
502
513
  o[k] = v;
503
514
  }
504
515
  return acc;
505
- }, {} as Record<string, unknown>);
516
+ }, {});
506
517
 
507
518
  const { filter } = MongoUtil.prepareQuery(cls, query);
508
519
  const res = await col.updateMany(filter, final);
@@ -514,7 +525,7 @@ export class MongoModelService implements
514
525
  const col = await this.getStore(cls);
515
526
  const pipeline: object[] = [{
516
527
  $group: {
517
- _id: `$${field}`,
528
+ _id: `$${field as string}`,
518
529
  count: {
519
530
  $sum: 1
520
531
  }
@@ -541,7 +552,7 @@ export class MongoModelService implements
541
552
  async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
542
553
  const q = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
543
554
  const results = await this.query(cls, q);
544
- return ModelQuerySuggestUtil.combineSuggestResults(cls, field as 'type', prefix, results, (a) => a, query && query.limit);
555
+ return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, results, (a) => a, query && query.limit);
545
556
  }
546
557
 
547
558
  async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {