@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 +3 -3
- package/package.json +5 -5
- package/src/config.ts +2 -2
- package/src/internal/util.ts +22 -70
- package/src/service.ts +85 -87
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 {
|
|
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 (!
|
|
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/
|
|
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.
|
|
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.
|
|
29
|
-
"@travetto/model": "^5.0.0-rc.
|
|
30
|
-
"@travetto/model-query": "^5.0.0-rc.
|
|
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.
|
|
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 {
|
|
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 (!
|
|
106
|
+
if (!Runtime.production) {
|
|
107
107
|
opts.waitQueueTimeoutMS ??= TimeUtil.asMillis(1, 'd'); // Wait a day in dev mode
|
|
108
108
|
}
|
|
109
109
|
}
|
package/src/internal/util.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Binary, ObjectId } from 'mongodb';
|
|
2
2
|
|
|
3
|
-
import { Class } from '@travetto/
|
|
4
|
-
import { DistanceUnit,
|
|
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?:
|
|
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 =
|
|
33
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
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 (
|
|
80
|
+
if (ModelQueryUtil.has$And(o)) {
|
|
97
81
|
return { $and: o.$and.map(x => this.extractWhereClause<T>(cls, x)) };
|
|
98
|
-
} else if (
|
|
82
|
+
} else if (ModelQueryUtil.has$Or(o)) {
|
|
99
83
|
return { $or: o.$or.map(x => this.extractWhereClause<T>(cls, x)) };
|
|
100
|
-
} else if (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
const
|
|
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
|
-
|
|
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,
|
|
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/
|
|
24
|
+
import { ShutdownManager, type Class, type DeepPartial, AppError, TypedObject, castTo, asFull } from '@travetto/runtime';
|
|
23
25
|
import { Injectable } from '@travetto/di';
|
|
24
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
return MongoUtil.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
240
|
+
.reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>((acc, [k, v]) => {
|
|
244
241
|
if (v === null || v === undefined) {
|
|
245
|
-
|
|
246
|
-
const o = (acc.$unset ??= {}) as Record<string, unknown>;
|
|
247
|
-
o[k] = v;
|
|
242
|
+
(acc.$unset ??= {})[k] = v;
|
|
248
243
|
} else {
|
|
249
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
MongoUtil.postLoadId(op.insert as T);
|
|
360
|
+
MongoUtil.postLoadId(asFull(op.insert));
|
|
370
361
|
}
|
|
371
362
|
}
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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.
|
|
397
|
+
this.getWhereFilter(
|
|
409
398
|
cls,
|
|
410
|
-
|
|
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.
|
|
412
|
+
this.getWhereFilter(
|
|
425
413
|
cls,
|
|
426
|
-
|
|
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
|
-
|
|
449
|
-
const where = this.getWhere(
|
|
435
|
+
const where = this.getWhereFilter(
|
|
450
436
|
cls,
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
500
|
+
const where = ModelQueryUtil.getWhereClause(cls, query.where);
|
|
501
|
+
where.id = item.id;
|
|
511
502
|
|
|
512
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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.
|
|
552
|
+
q = { $and: [q, MongoUtil.extractWhereFilter(cls, query.where)] };
|
|
566
553
|
}
|
|
567
554
|
|
|
568
|
-
|
|
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
|
-
|
|
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);
|