@travetto/model-mongo 8.0.0-alpha.2 → 8.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +7 -6
- package/src/internal/util.ts +38 -33
- package/src/service.ts +179 -70
- package/support/service.mongo.ts +6 -2
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.
|
|
3
|
+
"version": "8.0.0-alpha.21",
|
|
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.
|
|
30
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/model-
|
|
32
|
-
"
|
|
29
|
+
"@travetto/config": "^8.0.0-alpha.18",
|
|
30
|
+
"@travetto/model": "^8.0.0-alpha.19",
|
|
31
|
+
"@travetto/model-indexed": "^8.0.0-alpha.21",
|
|
32
|
+
"@travetto/model-query": "^8.0.0-alpha.20",
|
|
33
|
+
"mongodb": "^7.2.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
36
|
+
"@travetto/cli": "^8.0.0-alpha.24"
|
|
36
37
|
},
|
|
37
38
|
"peerDependenciesMeta": {
|
|
38
39
|
"@travetto/cli": {
|
package/src/internal/util.ts
CHANGED
|
@@ -3,15 +3,14 @@ import {
|
|
|
3
3
|
type IndexDescriptionInfo
|
|
4
4
|
} from 'mongodb';
|
|
5
5
|
|
|
6
|
-
import { RuntimeError, CodecUtil, castTo, type Class, toConcrete,
|
|
7
|
-
import { type DistanceUnit, type PageableModelQuery, type WhereClause, ModelQueryUtil } from '@travetto/model-query';
|
|
8
|
-
import type
|
|
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
|
-
|
|
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,
|
|
170
|
-
const out: [BasicIdx,
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
out =
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
4
|
-
|
|
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
|
|
11
|
-
|
|
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
|
|
23
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
|
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 })
|
|
278
|
-
|
|
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<
|
|
409
|
-
|
|
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}`,
|
|
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<
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
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
|
|
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
|
|
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);
|
package/support/service.mongo.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ServiceDescriptor } from '@travetto/cli';
|
|
2
2
|
|
|
3
|
-
const version = process.env.MONGO_VERSION || '8.
|
|
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
|
};
|