@travetto/model-mongo 8.0.0-alpha.2 → 8.0.0-alpha.20

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
@@ -19,8 +19,8 @@ Supported features:
19
19
  * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
20
20
  * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
21
21
  * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/types/bulk.ts#L64)
22
- * [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/types/indexed.ts#L11)
23
22
  * [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
23
+ * [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L16)
24
24
  * [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/crud.ts#L11)
25
25
  * [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/facet.ts#L14)
26
26
  * [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/query.ts#L10)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mongo",
3
- "version": "8.0.0-alpha.2",
3
+ "version": "8.0.0-alpha.20",
4
4
  "type": "module",
5
5
  "description": "Mongo backing for the travetto model module.",
6
6
  "keywords": [
@@ -26,13 +26,14 @@
26
26
  "directory": "module/model-mongo"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/config": "^8.0.0-alpha.2",
30
- "@travetto/model": "^8.0.0-alpha.2",
31
- "@travetto/model-query": "^8.0.0-alpha.2",
32
- "mongodb": "^7.1.0"
29
+ "@travetto/config": "^8.0.0-alpha.18",
30
+ "@travetto/model": "^8.0.0-alpha.18",
31
+ "@travetto/model-indexed": "^8.0.0-alpha.20",
32
+ "@travetto/model-query": "^8.0.0-alpha.19",
33
+ "mongodb": "^7.2.0"
33
34
  },
34
35
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.3"
36
+ "@travetto/cli": "^8.0.0-alpha.23"
36
37
  },
37
38
  "peerDependenciesMeta": {
38
39
  "@travetto/cli": {
@@ -3,15 +3,14 @@ import {
3
3
  type IndexDescriptionInfo
4
4
  } from 'mongodb';
5
5
 
6
- import { RuntimeError, CodecUtil, castTo, type Class, toConcrete, TypedObject, BinaryUtil } from '@travetto/runtime';
7
- import { type DistanceUnit, type PageableModelQuery, type WhereClause, ModelQueryUtil } from '@travetto/model-query';
8
- import type { ModelType, IndexField, IndexConfig } from '@travetto/model';
6
+ import { RuntimeError, CodecUtil, castTo, type Class, toConcrete, BinaryUtil } from '@travetto/runtime';
7
+ import { type DistanceUnit, type PageableModelQuery, type WhereClause, isModelQueryIndex, ModelQueryUtil } from '@travetto/model-query';
8
+ import { type ModelType, type IndexConfig, IndexNotSupported } from '@travetto/model';
9
9
  import { DataUtil, SchemaRegistryIndex, type Point } from '@travetto/schema';
10
+ import { isModelIndexedIndex } from '@travetto/model-indexed';
10
11
 
11
12
  const PointConcrete = toConcrete<Point>();
12
13
 
13
- type IdxConfig = CreateIndexesOptions;
14
-
15
14
  /**
16
15
  * Converting units to various radians
17
16
  */
@@ -25,7 +24,19 @@ const RADIANS_TO: Record<DistanceUnit, number> = {
25
24
 
26
25
  export type WithId<T, I = unknown> = T & { _id?: I };
27
26
  export type BasicIdx = Record<string, IndexDirection>;
28
- export type PlainIdx = Record<string, -1 | 0 | 1>;
27
+
28
+ function flattenKeys(obj: Record<string, unknown>, prefix = ''): Record<string, 1 | -1> {
29
+ const out: Record<string, 1 | -1> = {};
30
+ for (const [key, value] of Object.entries(obj)) {
31
+ const path = prefix ? `${prefix}.${key}` : key;
32
+ if (typeof value === 'object' && value !== null) {
33
+ Object.assign(out, flattenKeys(castTo(value), path));
34
+ } else {
35
+ out[path] = typeof value === 'boolean' ? (value ? 1 : -1) : castTo<-1 | 1>(value);
36
+ }
37
+ }
38
+ return out;
39
+ }
29
40
 
30
41
  /**
31
42
  * Basic mongo utils for conforming to the model module
@@ -36,19 +47,6 @@ export class MongoUtil {
36
47
  return `${cls.Ⲑid}__${name}`.replace(/[^a-zA-Z0-9_]+/g, '_');
37
48
  }
38
49
 
39
- static toIndex<T extends ModelType>(field: IndexField<T>): PlainIdx {
40
- const keys = [];
41
- while (typeof field !== 'number' && typeof field !== 'boolean' && Object.keys(field)) {
42
- const key = TypedObject.keys(field)[0];
43
- field = castTo(field[key]);
44
- keys.push(key);
45
- }
46
- const rf: number | boolean = castTo(field);
47
- return {
48
- [keys.join('.')]: typeof rf === 'boolean' ? (rf ? 1 : 0) : castTo<-1 | 1 | 0>(rf)
49
- };
50
- }
51
-
52
50
  static uuid(value: string): Binary {
53
51
  try {
54
52
  return new Binary(
@@ -166,8 +164,8 @@ export class MongoUtil {
166
164
  return out;
167
165
  }
168
166
 
169
- static getExtraIndices<T extends ModelType>(cls: Class<T>): [BasicIdx, IdxConfig][] {
170
- const out: [BasicIdx, IdxConfig][] = [];
167
+ static getExtraIndices<T extends ModelType>(cls: Class<T>): [BasicIdx, CreateIndexesOptions][] {
168
+ const out: [BasicIdx, CreateIndexesOptions][] = [];
171
169
  const textFields: string[] = [];
172
170
  SchemaRegistryIndex.visitFields(cls, (field, path) => {
173
171
  if (field.type === PointConcrete) {
@@ -185,19 +183,26 @@ export class MongoUtil {
185
183
  return out;
186
184
  }
187
185
 
188
- static getPlainIndex(idx: IndexConfig<ModelType>): PlainIdx {
189
- let out: PlainIdx = {};
190
- for (const config of idx.fields.map(value => this.toIndex(value))) {
191
- out = Object.assign(out, config);
192
- }
193
- return out;
194
- }
186
+ static getIndex(cls: Class, idx: IndexConfig): [BasicIdx, CreateIndexesOptions] {
187
+ const name = this.namespaceIndex(cls, idx.name);
188
+ if (isModelQueryIndex(idx)) {
189
+ const out = idx.fields.reduce(
190
+ (acc, field) => ({ ...acc, ...flattenKeys(castTo(field)) }),
191
+ castTo<Record<string, -1 | 0 | 1>>({}));
195
192
 
196
- static getIndices<T extends ModelType>(cls: Class<T>, indices: IndexConfig<ModelType>[] = []): [BasicIdx, IdxConfig][] {
197
- return [
198
- ...indices.map(idx => [this.getPlainIndex(idx), { ...(idx.type === 'unique' ? { unique: true } : {}), name: this.namespaceIndex(cls, idx.name) }] as const),
199
- ...this.getExtraIndices(cls)
200
- ].map(idx => [...idx]);
193
+ return [out, { name, unique: !!idx.unique }];
194
+ } else if (isModelIndexedIndex(idx)) {
195
+ const filter = Object.fromEntries([
196
+ ...idx.keyTemplate.map(({ path }) => [path.join('.'), 1]),
197
+ ...idx.sortTemplate.map(({ path, value }) => [path.join('.'), value === -1 ? -1 : 1])
198
+ ]);
199
+ switch (idx.type) {
200
+ case 'indexed:keyed': return [filter, { name, unique: idx.unique }];
201
+ case 'indexed:sorted': return [filter, { name }];
202
+ }
203
+ } else {
204
+ throw new IndexNotSupported(cls, idx);
205
+ }
201
206
  }
202
207
 
203
208
  static prepareCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor<T | MongoWithId<T>>, query: PageableModelQuery<T>): FindCursor<T> {
package/src/service.ts CHANGED
@@ -1,38 +1,41 @@
1
1
  import {
2
- type Binary,
3
- type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
4
- type ObjectId, type RootFilterOperators, type Filter,
5
- type WithId as MongoWithId,
2
+ type Binary, type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
3
+ type ObjectId, type RootFilterOperators, type Filter, type WithId as MongoWithId, type FindCursor,
4
+ MongoServerError,
6
5
  } from 'mongodb';
7
6
 
8
7
  import {
9
- ModelRegistryIndex, type ModelType, type OptionalId, type ModelCrudSupport, type ModelStorageSupport,
10
- type ModelExpirySupport, type ModelBulkSupport, type ModelIndexedSupport, type BulkOperation, type BulkResponse,
11
- NotFoundError, ExistsError, type ModelBlobSupport,
12
- ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil
8
+ ModelRegistryIndex, type ModelType, type OptionalId, type ModelCrudSupport, type ModelStorageSupport, type ModelExpirySupport,
9
+ type ModelBulkSupport, type BulkOperation, type BulkResponse, NotFoundError, ExistsError, type ModelBlobSupport,
10
+ ModelCrudUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil, type ModelListOptions,
13
11
  } from '@travetto/model';
14
12
  import {
15
13
  type ModelQuery, type ModelQueryCrudSupport, type ModelQueryFacetSupport, type ModelQuerySupport,
16
14
  type PageableModelQuery, type ValidStringFields, type WhereClause, type ModelQuerySuggestSupport,
17
- QueryVerifier, ModelQueryUtil, ModelQuerySuggestUtil, ModelQueryCrudUtil,
18
- type ModelQueryFacet,
15
+ QueryVerifier, ModelQueryUtil, ModelQuerySuggestUtil, ModelQueryCrudUtil, type ModelQueryFacet,
19
16
  } from '@travetto/model-query';
17
+ import {
18
+ type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ModelPageOptions, ModelIndexedUtil,
19
+ type SingleItemIndex, type SortedIndexSelection, type ModelPageResult, type SortedIndex, type FullKeyedIndexBody,
20
+ type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex, type ModelIndexedSearchOptions, type SortedIndexSelectionType,
21
+ } from '@travetto/model-indexed';
20
22
 
21
23
  import {
22
- ShutdownManager, type Class, type DeepPartial, TypedObject,
23
- castTo, asFull, type BinaryMetadata, type ByteRange, type BinaryType, BinaryUtil, BinaryMetadataUtil,
24
+ ShutdownManager, type Class, TypedObject, castTo, asFull, type BinaryMetadata, type ByteRange, type BinaryType,
25
+ BinaryUtil, BinaryMetadataUtil, JSONUtil,
24
26
  } from '@travetto/runtime';
25
27
  import { Injectable, PostConstruct } from '@travetto/di';
26
28
 
27
- import { MongoUtil, type PlainIdx, type WithId } from './internal/util.ts';
29
+ import { MongoUtil, type WithId } from './internal/util.ts';
28
30
  import type { MongoModelConfig } from './config.ts';
29
31
 
30
- const ListIndexSymbol = Symbol();
31
-
32
32
  type BlobRaw = GridFSFile & { metadata?: BinaryMetadata };
33
33
 
34
34
  type MongoTextSearch = RootFilterOperators<unknown>['$text'];
35
35
 
36
+ const isDuplicateKeyError = (error: unknown): boolean =>
37
+ error instanceof MongoServerError && error.message.includes('duplicate key error');
38
+
36
39
  export const ModelBlobNamespace = '__blobs';
37
40
 
38
41
  /**
@@ -54,6 +57,54 @@ export class MongoModelService implements
54
57
 
55
58
  constructor(config: MongoModelConfig) { this.config = config; }
56
59
 
60
+ async * #iterateCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor, options?: ModelListOptions & ModelPageOptions<number>): AsyncGenerator<T[]> {
61
+ const batchSize = options?.batchSizeHint ?? 100;
62
+ let batch: T[] = [];
63
+ const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
64
+ for await (const item of cursor
65
+ .batchSize(batchSize)
66
+ .limit(maxCount)
67
+ .skip(options?.offset ?? 0)
68
+ ) {
69
+ if (options?.abort?.aborted) {
70
+ cursor.close();
71
+ break;
72
+ }
73
+ batch.push(item);
74
+ if (batch.length >= batchSize) {
75
+ yield await ModelCrudUtil.filterOutNotFound(batch.map(i => this.postLoad(cls, i)));
76
+ batch = [];
77
+ }
78
+ }
79
+ if (batch.length) {
80
+ yield await ModelCrudUtil.filterOutNotFound(batch.map(i => this.postLoad(cls, i)));
81
+ }
82
+ }
83
+
84
+ async #buildIndexQuery<T extends ModelType>(
85
+ cls: Class<T>,
86
+ idx: SortedIndex<T>,
87
+ body: KeyedIndexBody<T>,
88
+ transformWhere?: (where: WhereClause<T>) => WhereClause<T>
89
+ ): Promise<FindCursor> {
90
+ const store = await this.getStore(cls);
91
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate();
92
+ let whereClause: WhereClause<T> = castTo(computed.project());
93
+ if (transformWhere) {
94
+ whereClause = transformWhere(whereClause);
95
+ }
96
+
97
+ const where = this.getWhereFilter(cls, whereClause);
98
+ let q = store.find(where, { timeout: true })
99
+ .batchSize(100);
100
+
101
+ // TODO: We could cache this
102
+ if ('sort' in idx) {
103
+ q = q.sort(idx.sortTemplate.map(({ path, value }) => [path.join('.'), value] as const));
104
+ }
105
+ return q;
106
+ }
107
+
57
108
  restoreId(item: { id?: string, _id?: unknown }): void {
58
109
  if (item._id) {
59
110
  item.id ??= MongoUtil.idToString(castTo(item._id));
@@ -131,7 +182,10 @@ export class MongoModelService implements
131
182
 
132
183
  async upsertModel(cls: Class): Promise<void> {
133
184
  const col = await this.getStore(cls);
134
- const indices = MongoUtil.getIndices(cls, ModelRegistryIndex.getConfig(cls).indices);
185
+ const indices = [
186
+ ...ModelRegistryIndex.getIndices(cls).map(idx => MongoUtil.getIndex(cls, idx)),
187
+ ...MongoUtil.getExtraIndices(cls)
188
+ ];
135
189
  const existingIndices = (await col.indexes().catch(() => [])).filter(idx => idx.name !== '_id_');
136
190
 
137
191
  const pendingMap = Object.fromEntries(indices.map(pair => [pair[1].name!, pair]));
@@ -193,11 +247,15 @@ export class MongoModelService implements
193
247
  const id = this.preUpdate(cleaned);
194
248
 
195
249
  const store = await this.getStore(cls);
196
- const result = await store.insertOne(castTo(cleaned));
197
- if (!result.insertedId) {
198
- throw new ExistsError(cls, id);
250
+ try {
251
+ const result = await store.insertOne(castTo(cleaned));
252
+ if (!result.insertedId) {
253
+ throw new ExistsError(cls, id);
254
+ }
255
+ return this.postUpdate(cleaned, id);
256
+ } catch (error) {
257
+ throw isDuplicateKeyError(error) ? new ExistsError(cls, id) : error;
199
258
  }
200
- return this.postUpdate(cleaned, id);
201
259
  }
202
260
 
203
261
  async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
@@ -223,11 +281,7 @@ export class MongoModelService implements
223
281
  { upsert: true }
224
282
  );
225
283
  } catch (error) {
226
- if (error instanceof Error && error.message.includes('duplicate key error')) {
227
- throw new ExistsError(cls, id);
228
- } else {
229
- throw error;
230
- }
284
+ throw isDuplicateKeyError(error) ? new ExistsError(cls, id) : error;
231
285
  }
232
286
  return this.postUpdate(cleaned, id);
233
287
  }
@@ -272,18 +326,10 @@ export class MongoModelService implements
272
326
  }
273
327
  }
274
328
 
275
- async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
329
+ async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
276
330
  const store = await this.getStore(cls);
277
- const cursor = store.find(this.getWhereFilter(cls, {}), { timeout: true }).batchSize(100);
278
- for await (const item of cursor) {
279
- try {
280
- yield await this.postLoad(cls, item);
281
- } catch (error) {
282
- if (!(error instanceof NotFoundError)) {
283
- throw error;
284
- }
285
- }
286
- }
331
+ const cursor = store.find(this.getWhereFilter(cls, {}), { timeout: true });
332
+ yield* this.#iterateCursor(cls, cursor, options);
287
333
  }
288
334
 
289
335
  // Blob
@@ -405,57 +451,120 @@ export class MongoModelService implements
405
451
  }
406
452
 
407
453
  // Indexed
408
- async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
409
- const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
454
+ async getByIndex<
455
+ T extends ModelType,
456
+ K extends KeyedIndexSelection<T>,
457
+ S extends SortedIndexSelection<T>
458
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
410
459
  const store = await this.getStore(cls);
460
+
461
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
462
+
411
463
  const result = await store.findOne(
412
- this.getWhereFilter(
413
- cls,
414
- castTo(ModelIndexedUtil.projectIndex(cls, idx, body))
415
- )
464
+ this.getWhereFilter(cls, castTo(computed.project({ sort: true, includeId: true })))
416
465
  );
417
466
  if (!result) {
418
- throw new NotFoundError(`${cls.name}: ${idx}`, key);
467
+ throw new NotFoundError(`${cls.name}: ${idx}`, computed.getKey({ sort: true }));
419
468
  }
420
469
  return await this.postLoad(cls, result);
421
470
  }
422
471
 
423
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
424
- const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
472
+ async deleteByIndex<
473
+ T extends ModelType,
474
+ K extends KeyedIndexSelection<T>,
475
+ S extends SortedIndexSelection<T>
476
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
425
477
  const store = await this.getStore(cls);
478
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
479
+
426
480
  const result = await store.deleteOne(
427
- this.getWhereFilter(
428
- cls,
429
- castTo(ModelIndexedUtil.projectIndex(cls, idx, body))
430
- )
481
+ this.getWhereFilter(cls, castTo(computed.project({ sort: true, includeId: true })))
431
482
  );
432
- if (result.deletedCount) {
433
- return;
483
+ if (!result.deletedCount) {
484
+ throw new NotFoundError(`${cls.name}: ${idx}`, computed.getKey({ sort: true }));
434
485
  }
435
- throw new NotFoundError(`${cls.name}: ${idx}`, key);
436
486
  }
437
487
 
438
- async upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> {
488
+ upsertByIndex<
489
+ T extends ModelType,
490
+ K extends KeyedIndexSelection<T>,
491
+ S extends SortedIndexSelection<T>
492
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
439
493
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
440
494
  }
441
495
 
442
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
443
- const store = await this.getStore(cls);
444
- const idxConfig = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
445
-
446
- const where = this.getWhereFilter(
447
- cls,
448
- castTo(ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }))
449
- );
450
-
451
- const sort = castTo<{ [ListIndexSymbol]: PlainIdx }>(idxConfig)[ListIndexSymbol] ??= MongoUtil.getPlainIndex(idxConfig);
452
- const cursor = store.find(where, { timeout: true }).batchSize(100).sort(castTo(sort));
453
-
454
- for await (const item of cursor) {
455
- yield await this.postLoad(cls, item);
496
+ updateByIndex<
497
+ T extends ModelType,
498
+ K extends KeyedIndexSelection<T>,
499
+ S extends SortedIndexSelection<T>
500
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
501
+ return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
502
+ }
503
+
504
+ async updatePartialByIndex<
505
+ T extends ModelType,
506
+ K extends KeyedIndexSelection<T>,
507
+ S extends SortedIndexSelection<T>
508
+ >(cls: Class<T>, idx: SingleItemIndex<T, K>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
509
+ const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
510
+ return this.update(cls, item);
511
+ }
512
+
513
+ async pageByIndex<
514
+ T extends ModelType,
515
+ K extends KeyedIndexSelection<T>,
516
+ S extends SortedIndexSelection<T>
517
+ >(
518
+ cls: Class<T>,
519
+ idx: SortedIndex<T, K, S>,
520
+ body: KeyedIndexBody<T, K>,
521
+ options?: ModelPageOptions,
522
+ ): Promise<ModelPageResult<T>> {
523
+ {
524
+ const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
525
+ const cursor = (await this.#buildIndexQuery(cls, idx, body));
526
+ const batches = await Array.fromAsync(this.#iterateCursor(cls, cursor, { limit: 100, ...options, offset }));
527
+ const items = batches.flat();
528
+ return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + items.length) : undefined };
456
529
  }
457
530
  }
458
531
 
532
+ async * listByIndex<
533
+ T extends ModelType,
534
+ K extends KeyedIndexSelection<T>,
535
+ S extends SortedIndexSelection<T>
536
+ >(
537
+ cls: Class<T>,
538
+ idx: SortedIndex<T, K, S>,
539
+ body: KeyedIndexBody<T, K>,
540
+ options?: ModelListOptions
541
+ ): AsyncIterable<T[]> {
542
+ const cursor = await this.#buildIndexQuery(cls, idx, body);
543
+ yield* this.#iterateCursor(cls, cursor, options);
544
+ }
545
+
546
+ async suggestByIndex<
547
+ T extends ModelType,
548
+ S extends SortedIndexSelection<T>,
549
+ K extends KeyedIndexSelection<T>,
550
+ B extends SortedIndexSelectionType<T, S> & string
551
+ >(
552
+ cls: Class<T>,
553
+ idx: SortedIndex<T, K, S>,
554
+ body: KeyedIndexBody<T, K>,
555
+ prefix: B,
556
+ options?: ModelIndexedSearchOptions
557
+ ): Promise<T[]> {
558
+ const cursor = (await this.#buildIndexQuery(cls, idx, body, (where) => castTo({
559
+ $and: [where, {
560
+ [idx.sortTemplate[0].path.join('.')]: ModelIndexedUtil.getSuggestRegex(prefix)
561
+ }]
562
+ })));
563
+ const batches = await Array.fromAsync(this.#iterateCursor(cls, cursor, { limit: 10, ...options }));
564
+
565
+ return batches.flat();
566
+ }
567
+
459
568
  // Query
460
569
  async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
461
570
  await QueryVerifier.verify(cls, query);
@@ -529,7 +638,7 @@ export class MongoModelService implements
529
638
  }
530
639
 
531
640
  // Facet
532
- async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
641
+ async facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
533
642
  await QueryVerifier.verify(cls, query);
534
643
 
535
644
  const col = await this.getStore(cls);
@@ -566,14 +675,14 @@ export class MongoModelService implements
566
675
  }
567
676
 
568
677
  // Suggest
569
- async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
678
+ async suggestValuesByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
570
679
  await QueryVerifier.verify(cls, query);
571
680
  const resolvedQuery = ModelQuerySuggestUtil.getSuggestFieldQuery<T>(cls, field, prefix, query);
572
681
  const results = await this.query<T>(cls, resolvedQuery);
573
682
  return ModelQuerySuggestUtil.combineSuggestResults<T, string>(cls, field, prefix, results, (a) => a, query && query.limit);
574
683
  }
575
684
 
576
- async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
685
+ async suggestByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
577
686
  await QueryVerifier.verify(cls, query);
578
687
  const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
579
688
  const results = await this.query<T>(cls, resolvedQuery);
@@ -1,10 +1,14 @@
1
1
  import type { ServiceDescriptor } from '@travetto/cli';
2
2
 
3
- const version = process.env.MONGO_VERSION || '8.2';
3
+ const version = process.env.MONGO_VERSION || '8.3';
4
4
 
5
5
  export const service: ServiceDescriptor = {
6
6
  name: 'mongodb',
7
7
  version,
8
8
  port: 27017,
9
- image: `mongo:${version}`
9
+ image: `mongo:${version}`,
10
+ env: {
11
+ // Temp until mongo image fixes orbstack issue
12
+ GLIBC_TUNABLES: 'glibc.pthread.rseq=1'
13
+ }
10
14
  };