@travetto/model-mongo 5.0.17 → 5.0.19
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/LICENSE +1 -1
- package/README.md +5 -0
- package/package.json +6 -6
- package/src/config.ts +5 -0
- package/src/internal/util.ts +62 -71
- package/src/service.ts +101 -52
- package/support/service.mongo.ts +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-mongo",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.19",
|
|
4
4
|
"description": "Mongo backing for the travetto model module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mongo",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"directory": "module/model-mongo"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/cli": "^5.0.
|
|
29
|
-
"@travetto/config": "^5.0.
|
|
30
|
-
"@travetto/model": "^5.0.
|
|
31
|
-
"@travetto/model-query": "^5.0.
|
|
32
|
-
"mongodb": "^6.
|
|
28
|
+
"@travetto/cli": "^5.0.18",
|
|
29
|
+
"@travetto/config": "^5.0.15",
|
|
30
|
+
"@travetto/model": "^5.0.16",
|
|
31
|
+
"@travetto/model-query": "^5.0.16",
|
|
32
|
+
"mongodb": "^6.12.0"
|
|
33
33
|
},
|
|
34
34
|
"travetto": {
|
|
35
35
|
"displayName": "MongoDB Model Support"
|
package/src/config.ts
CHANGED
package/src/internal/util.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Binary, type CreateIndexesOptions, type Filter, type FindCursor, type IndexDirection, ObjectId, type WithId as MongoWithId
|
|
3
|
+
} from 'mongodb';
|
|
2
4
|
|
|
3
|
-
import { castTo, Class, TypedObject } from '@travetto/runtime';
|
|
4
|
-
import { DistanceUnit, PageableModelQuery, WhereClause } from '@travetto/model-query';
|
|
5
|
+
import { AppError, castTo, Class, TypedObject } from '@travetto/runtime';
|
|
6
|
+
import type { DistanceUnit, PageableModelQuery, WhereClause } from '@travetto/model-query';
|
|
5
7
|
import type { ModelType, IndexField, IndexConfig } from '@travetto/model';
|
|
6
8
|
import { DataUtil, SchemaRegistry } from '@travetto/schema';
|
|
9
|
+
|
|
7
10
|
import { ModelQueryUtil } from '@travetto/model-query/src/internal/service/query';
|
|
8
|
-
import {
|
|
11
|
+
import { AllViewSymbol } from '@travetto/schema/src/internal/types';
|
|
9
12
|
import { PointImpl } from '@travetto/model-query/src/internal/model/point';
|
|
10
13
|
|
|
11
14
|
type IdxCfg = CreateIndexesOptions;
|
|
@@ -22,8 +25,6 @@ const RADIANS_TO: Record<DistanceUnit, number> = {
|
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export type WithId<T, I = unknown> = T & { _id?: I };
|
|
25
|
-
const isWithId = <T extends ModelType, I = unknown>(o: T): o is WithId<T, I> => o && '_id' in o;
|
|
26
|
-
|
|
27
28
|
export type BasicIdx = Record<string, IndexDirection>;
|
|
28
29
|
export type PlainIdx = Record<string, -1 | 0 | 1>;
|
|
29
30
|
|
|
@@ -59,24 +60,9 @@ export class MongoUtil {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
static
|
|
63
|
-
if (isWithId(item)) {
|
|
64
|
-
delete item._id;
|
|
65
|
-
}
|
|
66
|
-
return item;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static preInsertId<T extends ModelType>(item: T): T {
|
|
70
|
-
if (item && item.id) {
|
|
71
|
-
const itemWithId: WithId<T> = castTo(item);
|
|
72
|
-
itemWithId._id = this.uuid(item.id);
|
|
73
|
-
}
|
|
74
|
-
return item;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
static extractWhereFilter<T extends ModelType, U extends WhereClause<T>>(cls: Class<T>, where?: U, checkExpiry = true): Record<string, unknown> {
|
|
63
|
+
static extractWhereFilter<T extends ModelType, U extends WhereClause<T>>(cls: Class<T>, where?: U, checkExpiry = true): Filter<T> {
|
|
78
64
|
where = castTo(ModelQueryUtil.getWhereClause(cls, where, checkExpiry));
|
|
79
|
-
return where ? this.extractWhereClause(cls, where) : {};
|
|
65
|
+
return castTo(where ? this.extractWhereClause(cls, where) : {});
|
|
80
66
|
}
|
|
81
67
|
|
|
82
68
|
/**
|
|
@@ -103,58 +89,63 @@ export class MongoUtil {
|
|
|
103
89
|
for (const key of keys) {
|
|
104
90
|
const subpath = `${path}${key}`;
|
|
105
91
|
const v: Record<string, unknown> = castTo(sub[key]);
|
|
106
|
-
const subField = schema?.views[
|
|
92
|
+
const subField = schema?.views[AllViewSymbol].schema[key];
|
|
107
93
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} else {
|
|
111
|
-
const isPlain = v && DataUtil.isPlainObject(v);
|
|
112
|
-
const firstKey = isPlain ? Object.keys(v)[0] : '';
|
|
94
|
+
const isPlain = v && DataUtil.isPlainObject(v);
|
|
95
|
+
const firstKey = isPlain ? Object.keys(v)[0] : '';
|
|
113
96
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
97
|
+
if (subpath === 'id') {
|
|
98
|
+
if (!firstKey) {
|
|
99
|
+
out._id = Array.isArray(v) ? v.map(x => this.uuid(x)) : this.uuid(`${v}`);
|
|
100
|
+
} else if (firstKey === '$in' || firstKey === '$nin' || firstKey === '$eq' || firstKey === '$ne') {
|
|
101
|
+
const temp = v[firstKey];
|
|
102
|
+
out._id = { [firstKey]: Array.isArray(temp) ? temp.map(x => this.uuid(x)) : this.uuid(`${temp}`) };
|
|
103
|
+
} else {
|
|
104
|
+
throw new AppError('Invalid id query');
|
|
105
|
+
}
|
|
106
|
+
} else if ((isPlain && !firstKey.startsWith('$')) || v?.constructor?.Ⲑid) {
|
|
107
|
+
if (recursive) {
|
|
108
|
+
Object.assign(out, this.extractSimple(subField?.type, v, `${subpath}.`, recursive));
|
|
109
|
+
} else {
|
|
110
|
+
out[subpath] = v;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
if (firstKey === '$gt' || firstKey === '$lt' || firstKey === '$gte' || firstKey === '$lte') {
|
|
114
|
+
for (const [sk, sv] of Object.entries(v)) {
|
|
115
|
+
v[sk] = ModelQueryUtil.resolveComparator(sv);
|
|
116
|
+
}
|
|
117
|
+
} else if (firstKey === '$exists' && subField?.array) {
|
|
118
|
+
const exists = v.$exists;
|
|
119
|
+
if (!exists) {
|
|
120
|
+
delete v.$exists;
|
|
121
|
+
v.$in = [null, []];
|
|
117
122
|
} else {
|
|
118
|
-
|
|
123
|
+
v.$exists = true;
|
|
124
|
+
v.$nin = [null, []];
|
|
119
125
|
}
|
|
120
|
-
} else {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} else if (firstKey === '$regex') {
|
|
135
|
-
v.$regex = DataUtil.toRegex(castTo(v.$regex));
|
|
136
|
-
} else if (firstKey && '$near' in v) {
|
|
137
|
-
const dist: number = castTo(v.$maxDistance);
|
|
138
|
-
const distance = dist / RADIANS_TO[(castTo<DistanceUnit>(v.$unit) ?? 'km')];
|
|
139
|
-
v.$maxDistance = distance;
|
|
140
|
-
delete v.$unit;
|
|
141
|
-
} else if (firstKey && '$geoWithin' in v) {
|
|
142
|
-
const coords: [number, number][] = castTo(v.$geoWithin);
|
|
143
|
-
const first = coords[0];
|
|
144
|
-
const last = coords[coords.length - 1];
|
|
145
|
-
// Connect if not
|
|
146
|
-
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
147
|
-
coords.push(first);
|
|
148
|
-
}
|
|
149
|
-
v.$geoWithin = {
|
|
150
|
-
$geometry: {
|
|
151
|
-
type: 'Polygon',
|
|
152
|
-
coordinates: [coords]
|
|
153
|
-
}
|
|
154
|
-
};
|
|
126
|
+
} else if (firstKey === '$regex') {
|
|
127
|
+
v.$regex = DataUtil.toRegex(castTo(v.$regex));
|
|
128
|
+
} else if (firstKey && '$near' in v) {
|
|
129
|
+
const dist: number = castTo(v.$maxDistance);
|
|
130
|
+
const distance = dist / RADIANS_TO[(castTo<DistanceUnit>(v.$unit) ?? 'km')];
|
|
131
|
+
v.$maxDistance = distance;
|
|
132
|
+
delete v.$unit;
|
|
133
|
+
} else if (firstKey && '$geoWithin' in v) {
|
|
134
|
+
const coords: [number, number][] = castTo(v.$geoWithin);
|
|
135
|
+
const first = coords[0];
|
|
136
|
+
const last = coords[coords.length - 1];
|
|
137
|
+
// Connect if not
|
|
138
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
139
|
+
coords.push(first);
|
|
155
140
|
}
|
|
156
|
-
|
|
141
|
+
v.$geoWithin = {
|
|
142
|
+
$geometry: {
|
|
143
|
+
type: 'Polygon',
|
|
144
|
+
coordinates: [coords]
|
|
145
|
+
}
|
|
146
|
+
};
|
|
157
147
|
}
|
|
148
|
+
out[subpath === 'id' ? '_id' : subpath] = v;
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
return out;
|
|
@@ -197,7 +188,7 @@ export class MongoUtil {
|
|
|
197
188
|
].map(x => [...x]);
|
|
198
189
|
}
|
|
199
190
|
|
|
200
|
-
static prepareCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor<T
|
|
191
|
+
static prepareCursor<T extends ModelType>(cls: Class<T>, cursor: FindCursor<T | MongoWithId<T>>, query: PageableModelQuery<T>): FindCursor<T> {
|
|
201
192
|
if (query.select) {
|
|
202
193
|
const selectKey = Object.keys(query.select)[0];
|
|
203
194
|
const select = typeof selectKey === 'string' && selectKey.startsWith('$') ? query.select : this.extractSimple(cls, query.select);
|
|
@@ -221,6 +212,6 @@ export class MongoUtil {
|
|
|
221
212
|
cursor = cursor.skip(Math.trunc(query.offset ?? 0));
|
|
222
213
|
}
|
|
223
214
|
|
|
224
|
-
return cursor;
|
|
215
|
+
return castTo(cursor);
|
|
225
216
|
}
|
|
226
217
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { pipeline } from 'node:stream/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type Db, GridFSBucket, MongoClient, type GridFSFile, type Collection,
|
|
5
|
+
type ObjectId, type Binary, type RootFilterOperators, type Filter,
|
|
6
|
+
type WithId as MongoWithId
|
|
7
|
+
} from 'mongodb';
|
|
4
8
|
|
|
5
9
|
import {
|
|
6
10
|
ModelRegistry, ModelType, OptionalId, ModelCrudSupport, ModelStorageSupport,
|
|
@@ -32,7 +36,7 @@ import { MODEL_BLOB, ModelBlobNamespace, ModelBlobUtil } from '@travetto/model/s
|
|
|
32
36
|
import { MongoUtil, PlainIdx, WithId } from './internal/util';
|
|
33
37
|
import { MongoModelConfig } from './config';
|
|
34
38
|
|
|
35
|
-
const
|
|
39
|
+
const ListIndexSymbol = Symbol.for('@travetto/mongo-model:list-index');
|
|
36
40
|
|
|
37
41
|
type BlobRaw = GridFSFile & { metadata?: BlobMeta };
|
|
38
42
|
|
|
@@ -56,6 +60,40 @@ export class MongoModelService implements
|
|
|
56
60
|
|
|
57
61
|
constructor(public readonly config: MongoModelConfig) { }
|
|
58
62
|
|
|
63
|
+
restoreId(item: { id?: string, _id?: unknown }): void {
|
|
64
|
+
if (item._id) {
|
|
65
|
+
item.id ??= MongoUtil.idToString(castTo(item._id));
|
|
66
|
+
delete item._id;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async postLoad<T extends ModelType>(cls: Class<T>, item: T | MongoWithId<T>): Promise<T> {
|
|
71
|
+
this.restoreId(item);
|
|
72
|
+
return await ModelCrudUtil.load(cls, item);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
postUpdate<T extends ModelType>(item: T, id?: string): T {
|
|
76
|
+
if (id) {
|
|
77
|
+
item.id ??= id;
|
|
78
|
+
}
|
|
79
|
+
this.restoreId(item);
|
|
80
|
+
return item;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id: string }): string;
|
|
84
|
+
preUpdate<T extends OptionalId<ModelType>>(item: Omit<T, 'id'> & { _id?: Binary }): undefined;
|
|
85
|
+
preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id: undefined }): undefined;
|
|
86
|
+
preUpdate<T extends OptionalId<ModelType>>(item: T & { _id?: Binary, id?: string }): string | undefined {
|
|
87
|
+
if (item && item.id) {
|
|
88
|
+
const id = item.id;
|
|
89
|
+
item._id = MongoUtil.uuid(id);
|
|
90
|
+
if (!this.config.storeId) {
|
|
91
|
+
delete item.id;
|
|
92
|
+
}
|
|
93
|
+
return id;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
59
97
|
async #describeBlobRaw(location: string): Promise<BlobRaw> {
|
|
60
98
|
const files: BlobRaw[] = await this.#bucket.find({ filename: location }, { limit: 1 }).toArray();
|
|
61
99
|
|
|
@@ -78,8 +116,12 @@ export class MongoModelService implements
|
|
|
78
116
|
ModelExpiryUtil.registerCull(this);
|
|
79
117
|
}
|
|
80
118
|
|
|
81
|
-
getWhereFilter<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true):
|
|
82
|
-
return MongoUtil.extractWhereFilter(cls, where, checkExpiry);
|
|
119
|
+
getWhereFilter<T extends ModelType>(cls: Class<T>, where: WhereClause<T>, checkExpiry = true): Filter<T> {
|
|
120
|
+
return castTo(MongoUtil.extractWhereFilter(cls, where, checkExpiry));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getIdFilter<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true): Filter<T> {
|
|
124
|
+
return this.getWhereFilter(cls, castTo({ _id: MongoUtil.uuid(id) }), checkExpiry);
|
|
83
125
|
}
|
|
84
126
|
|
|
85
127
|
// Storage
|
|
@@ -120,73 +162,75 @@ export class MongoModelService implements
|
|
|
120
162
|
/**
|
|
121
163
|
* Get mongo collection
|
|
122
164
|
*/
|
|
123
|
-
async getStore<T extends ModelType>(cls: Class<T>): Promise<Collection
|
|
165
|
+
async getStore<T extends ModelType>(cls: Class<T>): Promise<Collection<T>> {
|
|
124
166
|
return this.#db.collection(ModelRegistry.getStore(cls).toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_'));
|
|
125
167
|
}
|
|
126
168
|
|
|
127
169
|
// Crud
|
|
128
170
|
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
129
171
|
const store = await this.getStore(cls);
|
|
130
|
-
const result = await store.findOne(this.
|
|
172
|
+
const result = await store.findOne(this.getIdFilter(cls, id), {});
|
|
131
173
|
if (result) {
|
|
132
|
-
const res = await
|
|
174
|
+
const res = await this.postLoad(cls, result);
|
|
133
175
|
if (res) {
|
|
134
|
-
return
|
|
176
|
+
return res;
|
|
135
177
|
}
|
|
136
178
|
}
|
|
137
179
|
throw new NotFoundError(cls, id);
|
|
138
180
|
}
|
|
139
181
|
|
|
140
182
|
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
141
|
-
const cleaned
|
|
142
|
-
|
|
183
|
+
const cleaned = await ModelCrudUtil.preStore<WithId<T, Binary>>(cls, item, this);
|
|
184
|
+
const id = this.preUpdate(cleaned);
|
|
143
185
|
|
|
144
186
|
const store = await this.getStore(cls);
|
|
145
187
|
const result = await store.insertOne(castTo(cleaned));
|
|
146
188
|
if (!result.insertedId) {
|
|
147
|
-
throw new ExistsError(cls,
|
|
189
|
+
throw new ExistsError(cls, id);
|
|
148
190
|
}
|
|
149
|
-
|
|
150
|
-
return cleaned;
|
|
191
|
+
return this.postUpdate(cleaned, id);
|
|
151
192
|
}
|
|
152
193
|
|
|
153
194
|
async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
|
|
154
195
|
item = await ModelCrudUtil.preStore(cls, item, this);
|
|
196
|
+
const id = this.preUpdate(item);
|
|
155
197
|
const store = await this.getStore(cls);
|
|
156
|
-
const res = await store.replaceOne(this.
|
|
198
|
+
const res = await store.replaceOne(this.getIdFilter(cls, id), item);
|
|
157
199
|
if (res.matchedCount === 0) {
|
|
158
|
-
throw new NotFoundError(cls,
|
|
200
|
+
throw new NotFoundError(cls, id);
|
|
159
201
|
}
|
|
160
|
-
return item;
|
|
202
|
+
return this.postUpdate(item, id);
|
|
161
203
|
}
|
|
162
204
|
|
|
163
205
|
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
164
|
-
const cleaned = await ModelCrudUtil.preStore(cls, item, this);
|
|
206
|
+
const cleaned = await ModelCrudUtil.preStore<WithId<T, Binary>>(cls, item, this);
|
|
207
|
+
const id = this.preUpdate(cleaned);
|
|
165
208
|
const store = await this.getStore(cls);
|
|
209
|
+
|
|
166
210
|
try {
|
|
167
211
|
await store.updateOne(
|
|
168
|
-
this.
|
|
212
|
+
this.getIdFilter(cls, id, false),
|
|
169
213
|
{ $set: cleaned },
|
|
170
214
|
{ upsert: true }
|
|
171
215
|
);
|
|
172
216
|
} catch (err) {
|
|
173
217
|
if (err instanceof Error && err.message.includes('duplicate key error')) {
|
|
174
|
-
throw new ExistsError(cls,
|
|
218
|
+
throw new ExistsError(cls, id);
|
|
175
219
|
} else {
|
|
176
220
|
throw err;
|
|
177
221
|
}
|
|
178
222
|
}
|
|
179
|
-
return cleaned;
|
|
223
|
+
return this.postUpdate(cleaned, id);
|
|
180
224
|
}
|
|
181
225
|
|
|
182
226
|
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
|
|
183
227
|
const store = await this.getStore(cls);
|
|
184
228
|
|
|
185
|
-
|
|
229
|
+
const final = await ModelCrudUtil.prePartialUpdate(cls, item, view);
|
|
230
|
+
const simple = MongoUtil.extractSimple(cls, final, undefined, false);
|
|
186
231
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
.entries(items)
|
|
232
|
+
const operation: Partial<T> = castTo(Object
|
|
233
|
+
.entries(simple)
|
|
190
234
|
.reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>((acc, [k, v]) => {
|
|
191
235
|
if (v === null || v === undefined) {
|
|
192
236
|
(acc.$unset ??= {})[k] = v;
|
|
@@ -194,13 +238,13 @@ export class MongoModelService implements
|
|
|
194
238
|
(acc.$set ??= {})[k] = v;
|
|
195
239
|
}
|
|
196
240
|
return acc;
|
|
197
|
-
}, {});
|
|
241
|
+
}, {}));
|
|
198
242
|
|
|
199
243
|
const id = item.id;
|
|
200
244
|
|
|
201
245
|
const res = await store.findOneAndUpdate(
|
|
202
|
-
this.
|
|
203
|
-
|
|
246
|
+
this.getIdFilter(cls, id),
|
|
247
|
+
operation,
|
|
204
248
|
{ returnDocument: 'after', includeResultMetadata: true }
|
|
205
249
|
);
|
|
206
250
|
|
|
@@ -213,7 +257,7 @@ export class MongoModelService implements
|
|
|
213
257
|
|
|
214
258
|
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
215
259
|
const store = await this.getStore(cls);
|
|
216
|
-
const result = await store.deleteOne(this.
|
|
260
|
+
const result = await store.deleteOne(this.getIdFilter(cls, id, false));
|
|
217
261
|
if (result.deletedCount === 0) {
|
|
218
262
|
throw new NotFoundError(cls, id);
|
|
219
263
|
}
|
|
@@ -224,7 +268,7 @@ export class MongoModelService implements
|
|
|
224
268
|
const cursor = store.find(this.getWhereFilter(cls, {}), { timeout: true }).batchSize(100);
|
|
225
269
|
for await (const el of cursor) {
|
|
226
270
|
try {
|
|
227
|
-
yield
|
|
271
|
+
yield await this.postLoad(cls, el);
|
|
228
272
|
} catch (err) {
|
|
229
273
|
if (!(err instanceof NotFoundError)) {
|
|
230
274
|
throw err;
|
|
@@ -294,7 +338,7 @@ export class MongoModelService implements
|
|
|
294
338
|
return out;
|
|
295
339
|
}
|
|
296
340
|
|
|
297
|
-
const store = await this.getStore(cls);
|
|
341
|
+
const store = await this.getStore<Partial<T> & ModelType>(cls);
|
|
298
342
|
const bulk = store.initializeUnorderedBulkOp({ writeConcern: { w: 1 } });
|
|
299
343
|
const { upsertedIds, insertedIds } = await ModelBulkUtil.preStore(cls, operations, this);
|
|
300
344
|
|
|
@@ -302,11 +346,14 @@ export class MongoModelService implements
|
|
|
302
346
|
|
|
303
347
|
for (const op of operations) {
|
|
304
348
|
if (op.insert) {
|
|
305
|
-
|
|
349
|
+
this.preUpdate(op.insert);
|
|
350
|
+
bulk.insert(op.insert);
|
|
306
351
|
} else if (op.upsert) {
|
|
307
|
-
|
|
352
|
+
const id = this.preUpdate(op.upsert);
|
|
353
|
+
bulk.find({ _id: MongoUtil.uuid(id!) }).upsert().updateOne({ $set: op.upsert });
|
|
308
354
|
} else if (op.update) {
|
|
309
|
-
|
|
355
|
+
const id = this.preUpdate(op.update);
|
|
356
|
+
bulk.find({ _id: MongoUtil.uuid(id) }).update({ $set: op.update });
|
|
310
357
|
} else if (op.delete) {
|
|
311
358
|
bulk.find({ _id: MongoUtil.uuid(op.delete.id) }).deleteOne();
|
|
312
359
|
}
|
|
@@ -314,13 +361,16 @@ export class MongoModelService implements
|
|
|
314
361
|
|
|
315
362
|
const res = await bulk.execute({});
|
|
316
363
|
|
|
364
|
+
// Restore all ids
|
|
317
365
|
for (const op of operations) {
|
|
318
|
-
|
|
319
|
-
|
|
366
|
+
const core = op.insert ?? op.upsert ?? op.update;
|
|
367
|
+
if (core) {
|
|
368
|
+
this.postUpdate(asFull(core));
|
|
320
369
|
}
|
|
321
370
|
}
|
|
322
|
-
|
|
323
|
-
|
|
371
|
+
|
|
372
|
+
for (const [index, _id] of TypedObject.entries<Record<string, string>>(res.upsertedIds)) {
|
|
373
|
+
out.insertedIds.set(+index, MongoUtil.idToString(_id));
|
|
324
374
|
}
|
|
325
375
|
|
|
326
376
|
if (out.counts) {
|
|
@@ -361,7 +411,7 @@ export class MongoModelService implements
|
|
|
361
411
|
if (!result) {
|
|
362
412
|
throw new NotFoundError(`${cls.name}: ${idx}`, key);
|
|
363
413
|
}
|
|
364
|
-
return await
|
|
414
|
+
return await this.postLoad(cls, result);
|
|
365
415
|
}
|
|
366
416
|
|
|
367
417
|
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
|
|
@@ -392,11 +442,11 @@ export class MongoModelService implements
|
|
|
392
442
|
castTo(ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }))
|
|
393
443
|
);
|
|
394
444
|
|
|
395
|
-
const sort = castTo<{ [
|
|
445
|
+
const sort = castTo<{ [ListIndexSymbol]: PlainIdx }>(idxCfg)[ListIndexSymbol] ??= MongoUtil.getPlainIndex(idxCfg);
|
|
396
446
|
const cursor = store.find(where, { timeout: true }).batchSize(100).sort(castTo(sort));
|
|
397
447
|
|
|
398
448
|
for await (const el of cursor) {
|
|
399
|
-
yield
|
|
449
|
+
yield await this.postLoad(cls, el);
|
|
400
450
|
}
|
|
401
451
|
}
|
|
402
452
|
|
|
@@ -406,9 +456,9 @@ export class MongoModelService implements
|
|
|
406
456
|
|
|
407
457
|
const col = await this.getStore(cls);
|
|
408
458
|
const filter = MongoUtil.extractWhereFilter(cls, query.where);
|
|
409
|
-
const cursor = col.find
|
|
459
|
+
const cursor = col.find(filter, {});
|
|
410
460
|
const items = await MongoUtil.prepareCursor(cls, cursor, query).toArray();
|
|
411
|
-
return await Promise.all(items.map(r =>
|
|
461
|
+
return await Promise.all(items.map(r => this.postLoad(cls, r)));
|
|
412
462
|
}
|
|
413
463
|
|
|
414
464
|
async queryCount<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
|
|
@@ -430,15 +480,16 @@ export class MongoModelService implements
|
|
|
430
480
|
|
|
431
481
|
const col = await this.getStore(cls);
|
|
432
482
|
const item = await ModelCrudUtil.preStore(cls, data, this);
|
|
483
|
+
const id = this.preUpdate(item);
|
|
433
484
|
const where = ModelQueryUtil.getWhereClause(cls, query.where);
|
|
434
|
-
where.id =
|
|
485
|
+
where.id = id;
|
|
435
486
|
|
|
436
487
|
const filter = MongoUtil.extractWhereFilter(cls, where);
|
|
437
488
|
const res = await col.replaceOne(filter, item);
|
|
438
489
|
if (res.matchedCount === 0) {
|
|
439
|
-
throw new NotFoundError(cls,
|
|
490
|
+
throw new NotFoundError(cls, id);
|
|
440
491
|
}
|
|
441
|
-
return item;
|
|
492
|
+
return this.postUpdate(item, id);
|
|
442
493
|
}
|
|
443
494
|
|
|
444
495
|
async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
|
|
@@ -454,7 +505,6 @@ export class MongoModelService implements
|
|
|
454
505
|
await QueryVerifier.verify(cls, query);
|
|
455
506
|
|
|
456
507
|
const item = await ModelCrudUtil.prePartialUpdate(cls, data);
|
|
457
|
-
|
|
458
508
|
const col = await this.getStore(cls);
|
|
459
509
|
const items = MongoUtil.extractSimple(cls, item);
|
|
460
510
|
const final = Object.entries(items).reduce<Partial<Record<'$unset' | '$set', Record<string, unknown>>>>(
|
|
@@ -468,7 +518,7 @@ export class MongoModelService implements
|
|
|
468
518
|
}, {});
|
|
469
519
|
|
|
470
520
|
const filter = MongoUtil.extractWhereFilter(cls, query.where);
|
|
471
|
-
const res = await col.updateMany(filter, final);
|
|
521
|
+
const res = await col.updateMany(filter, castTo(final));
|
|
472
522
|
return res.matchedCount;
|
|
473
523
|
}
|
|
474
524
|
|
|
@@ -532,13 +582,12 @@ export class MongoModelService implements
|
|
|
532
582
|
search = { $search: search, $language: 'en' };
|
|
533
583
|
}
|
|
534
584
|
|
|
535
|
-
(query.sort ??= []).unshift({
|
|
536
|
-
// @ts-expect-error
|
|
585
|
+
(query.sort ??= []).unshift(castTo<(typeof query.sort[0])>({
|
|
537
586
|
score: { $meta: 'textScore' }
|
|
538
|
-
});
|
|
587
|
+
}));
|
|
539
588
|
|
|
540
|
-
const cursor = col.find
|
|
589
|
+
const cursor = col.find(castTo({ $and: [{ $text: search }, filter] }), {});
|
|
541
590
|
const items = await MongoUtil.prepareCursor(cls, cursor, query).toArray();
|
|
542
|
-
return await Promise.all(items.map(r =>
|
|
591
|
+
return await Promise.all(items.map(r => this.postLoad(cls, r)));
|
|
543
592
|
}
|
|
544
593
|
}
|
package/support/service.mongo.ts
CHANGED