@loopback/repository 2.6.0 → 2.10.0
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/CHANGELOG.md +63 -0
- package/README.md +1 -1
- package/dist/decorators/metadata.d.ts +1 -1
- package/dist/decorators/metadata.js +6 -6
- package/dist/decorators/metadata.js.map +1 -1
- package/dist/decorators/model.decorator.d.ts +1 -1
- package/dist/decorators/model.decorator.js +10 -10
- package/dist/decorators/model.decorator.js.map +1 -1
- package/dist/decorators/repository.decorator.js +4 -4
- package/dist/decorators/repository.decorator.js.map +1 -1
- package/dist/define-model-class.d.ts +2 -2
- package/dist/mixins/repository.mixin.d.ts +23 -12
- package/dist/mixins/repository.mixin.js +13 -7
- package/dist/mixins/repository.mixin.js.map +1 -1
- package/dist/model.d.ts +19 -2
- package/dist/model.js +46 -2
- package/dist/model.js.map +1 -1
- package/dist/query.d.ts +30 -0
- package/dist/query.js +50 -0
- package/dist/query.js.map +1 -1
- package/dist/relations/belongs-to/belongs-to.decorator.js +2 -2
- package/dist/relations/belongs-to/belongs-to.decorator.js.map +1 -1
- package/dist/relations/belongs-to/belongs-to.helpers.d.ts +1 -0
- package/dist/relations/belongs-to/belongs-to.helpers.js +13 -6
- package/dist/relations/belongs-to/belongs-to.helpers.js.map +1 -1
- package/dist/relations/belongs-to/belongs-to.repository.d.ts +1 -1
- package/dist/relations/has-many/has-many-through-repository.factory.d.ts +11 -0
- package/dist/relations/has-many/has-many-through-repository.factory.js +31 -0
- package/dist/relations/has-many/has-many-through-repository.factory.js.map +1 -0
- package/dist/relations/has-many/has-many-through.helpers.d.ts +122 -14
- package/dist/relations/has-many/has-many-through.helpers.js +153 -20
- package/dist/relations/has-many/has-many-through.helpers.js.map +1 -1
- package/dist/relations/has-many/has-many-through.repository.d.ts +39 -2
- package/dist/relations/has-many/has-many-through.repository.js +87 -0
- package/dist/relations/has-many/has-many-through.repository.js.map +1 -1
- package/dist/relations/has-many/has-many.helpers.js +2 -1
- package/dist/relations/has-many/has-many.helpers.js.map +1 -1
- package/dist/relations/has-many/has-many.repository.d.ts +1 -1
- package/dist/relations/has-many/index.d.ts +4 -2
- package/dist/relations/has-many/index.js +4 -2
- package/dist/relations/has-many/index.js.map +1 -1
- package/dist/relations/has-one/has-one.helpers.js +2 -1
- package/dist/relations/has-one/has-one.helpers.js.map +1 -1
- package/dist/relations/has-one/has-one.repository.d.ts +1 -1
- package/dist/relations/relation.decorator.js +6 -6
- package/dist/relations/relation.decorator.js.map +1 -1
- package/dist/relations/relation.types.d.ts +2 -2
- package/dist/relations/relation.types.js +2 -2
- package/dist/relations/relation.types.js.map +1 -1
- package/dist/repositories/constraint-utils.d.ts +10 -0
- package/dist/repositories/constraint-utils.js +16 -1
- package/dist/repositories/constraint-utils.js.map +1 -1
- package/dist/repositories/legacy-juggler-bridge.d.ts +33 -2
- package/dist/repositories/legacy-juggler-bridge.js +34 -0
- package/dist/repositories/legacy-juggler-bridge.js.map +1 -1
- package/package.json +15 -13
- package/src/decorators/metadata.ts +1 -1
- package/src/decorators/model.decorator.ts +1 -1
- package/src/decorators/repository.decorator.ts +1 -1
- package/src/define-model-class.ts +2 -2
- package/src/mixins/repository.mixin.ts +8 -2
- package/src/model.ts +64 -4
- package/src/query.ts +55 -0
- package/src/relations/belongs-to/belongs-to.decorator.ts +1 -1
- package/src/relations/belongs-to/belongs-to.helpers.ts +19 -8
- package/src/relations/belongs-to/belongs-to.repository.ts +1 -1
- package/src/relations/has-many/has-many-through-repository.factory.ts +104 -0
- package/src/relations/has-many/has-many-through.helpers.ts +182 -21
- package/src/relations/has-many/has-many-through.repository.ts +191 -2
- package/src/relations/has-many/has-many.helpers.ts +1 -2
- package/src/relations/has-many/has-many.repository.ts +1 -1
- package/src/relations/has-many/index.ts +4 -2
- package/src/relations/has-one/has-one.helpers.ts +1 -2
- package/src/relations/has-one/has-one.repository.ts +1 -1
- package/src/relations/relation.decorator.ts +1 -1
- package/src/relations/relation.types.ts +2 -2
- package/src/repositories/constraint-utils.ts +17 -0
- package/src/repositories/legacy-juggler-bridge.ts +59 -1
|
@@ -7,6 +7,7 @@ import debugFactory from 'debug';
|
|
|
7
7
|
import {camelCase} from 'lodash';
|
|
8
8
|
import {
|
|
9
9
|
DataObject,
|
|
10
|
+
deduplicate,
|
|
10
11
|
Entity,
|
|
11
12
|
HasManyDefinition,
|
|
12
13
|
InvalidRelationError,
|
|
@@ -26,10 +27,9 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Creates constraint
|
|
30
|
-
* @param relationMeta - hasManyThrough metadata
|
|
31
|
-
* @param throughInstances -
|
|
32
|
-
* @internal
|
|
30
|
+
* Creates target constraint based on through models
|
|
31
|
+
* @param relationMeta - resolved hasManyThrough metadata
|
|
32
|
+
* @param throughInstances - an array of through instances
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* ```ts
|
|
@@ -43,33 +43,40 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {
|
|
|
43
43
|
* keyTo: 'productId',
|
|
44
44
|
* },
|
|
45
45
|
* };
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
* createTargetConstraintFromThrough(resolvedMetadata,[{
|
|
47
|
+
id: 2,
|
|
48
|
+
categoryId: 2,
|
|
49
|
+
productId: 8,
|
|
50
|
+
}]);
|
|
51
|
+
* >>> {id: 8}
|
|
52
|
+
* createTargetConstraintFromThrough(resolvedMetadata, [
|
|
48
53
|
{
|
|
49
54
|
id: 2,
|
|
50
55
|
categoryId: 2,
|
|
51
56
|
productId: 8,
|
|
52
57
|
}, {
|
|
53
|
-
id:
|
|
58
|
+
id: 1,
|
|
54
59
|
categoryId: 2,
|
|
55
60
|
productId: 9,
|
|
56
61
|
}
|
|
57
62
|
]);
|
|
63
|
+
|
|
64
|
+
>>> {id: {inq: [9, 8]}}
|
|
58
65
|
* ```
|
|
59
66
|
*/
|
|
60
|
-
export function
|
|
67
|
+
export function createTargetConstraintFromThrough<
|
|
61
68
|
Target extends Entity,
|
|
62
69
|
Through extends Entity
|
|
63
70
|
>(
|
|
64
71
|
relationMeta: HasManyThroughResolvedDefinition,
|
|
65
72
|
throughInstances: Through[],
|
|
66
73
|
): DataObject<Target> {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(throughInstance: Through) =>
|
|
71
|
-
throughInstance[targetFkName as keyof Through],
|
|
74
|
+
const fkValues = getTargetKeysFromThroughModels(
|
|
75
|
+
relationMeta,
|
|
76
|
+
throughInstances,
|
|
72
77
|
);
|
|
78
|
+
const targetPrimaryKey = relationMeta.keyTo;
|
|
79
|
+
|
|
73
80
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
81
|
const constraint: any = {
|
|
75
82
|
[targetPrimaryKey]: fkValues.length === 1 ? fkValues[0] : {inq: fkValues},
|
|
@@ -78,11 +85,64 @@ export function createTargetConstraint<
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
/**
|
|
81
|
-
*
|
|
88
|
+
* Returns an array of target fks of the given throughInstances.
|
|
82
89
|
*
|
|
83
|
-
* @param relationMeta - hasManyThrough metadata
|
|
84
|
-
* @param
|
|
85
|
-
*
|
|
90
|
+
* @param relationMeta - resolved hasManyThrough metadata
|
|
91
|
+
* @param throughInstances - an array of through instances
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const resolvedMetadata = {
|
|
96
|
+
* // .. other props
|
|
97
|
+
* keyFrom: 'id',
|
|
98
|
+
* keyTo: 'id',
|
|
99
|
+
* through: {
|
|
100
|
+
* model: () => CategoryProductLink,
|
|
101
|
+
* keyFrom: 'categoryId',
|
|
102
|
+
* keyTo: 'productId',
|
|
103
|
+
* },
|
|
104
|
+
* };
|
|
105
|
+
* getTargetKeysFromThroughModels(resolvedMetadata,[{
|
|
106
|
+
id: 2,
|
|
107
|
+
categoryId: 2,
|
|
108
|
+
productId: 8,
|
|
109
|
+
}]);
|
|
110
|
+
* >>> [8]
|
|
111
|
+
* getTargetKeysFromThroughModels(resolvedMetadata, [
|
|
112
|
+
{
|
|
113
|
+
id: 2,
|
|
114
|
+
categoryId: 2,
|
|
115
|
+
productId: 8,
|
|
116
|
+
}, {
|
|
117
|
+
id: 1,
|
|
118
|
+
categoryId: 2,
|
|
119
|
+
productId: 9,
|
|
120
|
+
}
|
|
121
|
+
]);
|
|
122
|
+
>>> [8, 9]
|
|
123
|
+
*/
|
|
124
|
+
export function getTargetKeysFromThroughModels<
|
|
125
|
+
Through extends Entity,
|
|
126
|
+
TargetID
|
|
127
|
+
>(
|
|
128
|
+
relationMeta: HasManyThroughResolvedDefinition,
|
|
129
|
+
throughInstances: Through[],
|
|
130
|
+
): TargetID[] {
|
|
131
|
+
const targetFkName = relationMeta.through.keyTo;
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
let fkValues: any = throughInstances.map(
|
|
134
|
+
(throughInstance: Through) =>
|
|
135
|
+
throughInstance[targetFkName as keyof Through],
|
|
136
|
+
);
|
|
137
|
+
fkValues = deduplicate(fkValues);
|
|
138
|
+
return fkValues as TargetID[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Creates through constraint based on the source key
|
|
143
|
+
*
|
|
144
|
+
* @param relationMeta - resolved hasManyThrough metadata
|
|
145
|
+
* @param fkValue - foreign key of the source instance
|
|
86
146
|
* @internal
|
|
87
147
|
*
|
|
88
148
|
* @example
|
|
@@ -97,12 +157,17 @@ export function createTargetConstraint<
|
|
|
97
157
|
* keyTo: 'productId',
|
|
98
158
|
* },
|
|
99
159
|
* };
|
|
100
|
-
*
|
|
160
|
+
* createThroughConstraintFromSource(resolvedMetadata, 1);
|
|
161
|
+
*
|
|
162
|
+
* >>> {categoryId: 1}
|
|
101
163
|
* ```
|
|
102
164
|
*/
|
|
103
|
-
export function
|
|
165
|
+
export function createThroughConstraintFromSource<
|
|
166
|
+
Through extends Entity,
|
|
167
|
+
SourceID
|
|
168
|
+
>(
|
|
104
169
|
relationMeta: HasManyThroughResolvedDefinition,
|
|
105
|
-
fkValue:
|
|
170
|
+
fkValue: SourceID,
|
|
106
171
|
): DataObject<Through> {
|
|
107
172
|
const sourceFkName = relationMeta.through.keyFrom;
|
|
108
173
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -110,6 +175,102 @@ export function createThroughConstraint<Through extends Entity, ForeignKeyType>(
|
|
|
110
175
|
return constraint;
|
|
111
176
|
}
|
|
112
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Returns an array of target ids of the given target instances.
|
|
180
|
+
*
|
|
181
|
+
* @param relationMeta - resolved hasManyThrough metadata
|
|
182
|
+
* @param targetInstances - an array of target instances
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* const resolvedMetadata = {
|
|
187
|
+
* // .. other props
|
|
188
|
+
* keyFrom: 'id',
|
|
189
|
+
* keyTo: 'id',
|
|
190
|
+
* through: {
|
|
191
|
+
* model: () => CategoryProductLink,
|
|
192
|
+
* keyFrom: 'categoryId',
|
|
193
|
+
* keyTo: 'productId',
|
|
194
|
+
* },
|
|
195
|
+
* };
|
|
196
|
+
* getTargetKeysFromTargetModels(resolvedMetadata,[{
|
|
197
|
+
id: 2,
|
|
198
|
+
des: 'a target',
|
|
199
|
+
}]);
|
|
200
|
+
* >>> [2]
|
|
201
|
+
* getTargetKeysFromTargetModels(resolvedMetadata, [
|
|
202
|
+
{
|
|
203
|
+
id: 2,
|
|
204
|
+
des: 'a target',
|
|
205
|
+
}, {
|
|
206
|
+
id: 1,
|
|
207
|
+
des: 'a target',
|
|
208
|
+
}
|
|
209
|
+
]);
|
|
210
|
+
>>> [2, 1]
|
|
211
|
+
*/
|
|
212
|
+
export function getTargetIdsFromTargetModels<Target extends Entity, TargetID>(
|
|
213
|
+
relationMeta: HasManyThroughResolvedDefinition,
|
|
214
|
+
targetInstances: Target[],
|
|
215
|
+
): TargetID[] {
|
|
216
|
+
const targetId = relationMeta.keyTo;
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
let ids = [] as any;
|
|
219
|
+
ids = targetInstances.map(
|
|
220
|
+
(targetInstance: Target) => targetInstance[targetId as keyof Target],
|
|
221
|
+
);
|
|
222
|
+
ids = deduplicate(ids);
|
|
223
|
+
return ids as TargetID[];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates through constraint based on the target foreign key
|
|
228
|
+
*
|
|
229
|
+
* @param relationMeta - resolved hasManyThrough metadata
|
|
230
|
+
* @param fkValue an array of the target instance foreign keys
|
|
231
|
+
* @internal
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const resolvedMetadata = {
|
|
236
|
+
* // .. other props
|
|
237
|
+
* keyFrom: 'id',
|
|
238
|
+
* keyTo: 'id',
|
|
239
|
+
* through: {
|
|
240
|
+
* model: () => CategoryProductLink,
|
|
241
|
+
* keyFrom: 'categoryId',
|
|
242
|
+
* keyTo: 'productId',
|
|
243
|
+
* },
|
|
244
|
+
* };
|
|
245
|
+
* createThroughConstraintFromTarget(resolvedMetadata, [3]);
|
|
246
|
+
*
|
|
247
|
+
* >>> {productId: 3}
|
|
248
|
+
*
|
|
249
|
+
* createThroughConstraintFromTarget(resolvedMetadata, [3,4]);
|
|
250
|
+
*
|
|
251
|
+
* >>> {productId: {inq:[3,4]}}
|
|
252
|
+
*/
|
|
253
|
+
export function createThroughConstraintFromTarget<
|
|
254
|
+
Through extends Entity,
|
|
255
|
+
TargetID
|
|
256
|
+
>(
|
|
257
|
+
relationMeta: HasManyThroughResolvedDefinition,
|
|
258
|
+
fkValues: TargetID[],
|
|
259
|
+
): DataObject<Through> {
|
|
260
|
+
if (fkValues === undefined || fkValues.length === 0) {
|
|
261
|
+
throw new Error('"fkValue" must be provided');
|
|
262
|
+
}
|
|
263
|
+
const targetFkName = relationMeta.through.keyTo;
|
|
264
|
+
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
|
+
const constraint: any =
|
|
267
|
+
fkValues.length === 1
|
|
268
|
+
? {[targetFkName]: fkValues[0]}
|
|
269
|
+
: {[targetFkName]: {inq: fkValues}};
|
|
270
|
+
|
|
271
|
+
return constraint as DataObject<Through>;
|
|
272
|
+
}
|
|
273
|
+
|
|
113
274
|
/**
|
|
114
275
|
* Resolves given hasMany metadata if target is specified to be a resolver.
|
|
115
276
|
* Mainly used to infer what the `keyTo` property should be from the target's
|
|
@@ -147,7 +308,7 @@ export function resolveHasManyThroughMetadata(
|
|
|
147
308
|
relationMeta.keyTo &&
|
|
148
309
|
targetModelProperties[relationMeta.keyTo]
|
|
149
310
|
) {
|
|
150
|
-
// The
|
|
311
|
+
// The explicit cast is needed because of a limitation of type inference
|
|
151
312
|
return relationMeta as HasManyThroughResolvedDefinition;
|
|
152
313
|
}
|
|
153
314
|
|
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
constrainDataObject,
|
|
8
|
+
constrainFilter,
|
|
9
|
+
constrainWhere,
|
|
10
|
+
constrainWhereOr,
|
|
11
|
+
Count,
|
|
12
|
+
DataObject,
|
|
13
|
+
Entity,
|
|
14
|
+
EntityCrudRepository,
|
|
15
|
+
Filter,
|
|
16
|
+
Getter,
|
|
17
|
+
Options,
|
|
18
|
+
Where,
|
|
19
|
+
} from '../..';
|
|
7
20
|
|
|
8
21
|
/**
|
|
9
22
|
* CRUD operations for a target repository of a HasManyThrough relation
|
|
@@ -83,7 +96,7 @@ export interface HasManyThroughRepository<
|
|
|
83
96
|
throughData?: DataObject<Through>;
|
|
84
97
|
throughOptions?: Options;
|
|
85
98
|
},
|
|
86
|
-
): Promise<
|
|
99
|
+
): Promise<void>;
|
|
87
100
|
|
|
88
101
|
/**
|
|
89
102
|
* Removes an association to an existing target model instance
|
|
@@ -98,3 +111,179 @@ export interface HasManyThroughRepository<
|
|
|
98
111
|
},
|
|
99
112
|
): Promise<void>;
|
|
100
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* a class for CRUD operations for hasManyThrough relation.
|
|
117
|
+
*
|
|
118
|
+
* Warning: The hasManyThrough interface is experimental and is subject to change.
|
|
119
|
+
* If backwards-incompatible changes are made, a new major version may not be
|
|
120
|
+
* released.
|
|
121
|
+
*/
|
|
122
|
+
export class DefaultHasManyThroughRepository<
|
|
123
|
+
TargetEntity extends Entity,
|
|
124
|
+
TargetID,
|
|
125
|
+
TargetRepository extends EntityCrudRepository<TargetEntity, TargetID>,
|
|
126
|
+
ThroughEntity extends Entity,
|
|
127
|
+
ThroughID,
|
|
128
|
+
ThroughRepository extends EntityCrudRepository<ThroughEntity, ThroughID>
|
|
129
|
+
> implements HasManyThroughRepository<TargetEntity, TargetID, ThroughEntity> {
|
|
130
|
+
constructor(
|
|
131
|
+
public getTargetRepository: Getter<TargetRepository>,
|
|
132
|
+
public getThroughRepository: Getter<ThroughRepository>,
|
|
133
|
+
public getTargetConstraintFromThroughModels: (
|
|
134
|
+
throughInstances: ThroughEntity[],
|
|
135
|
+
) => DataObject<TargetEntity>,
|
|
136
|
+
public getTargetKeys: (throughInstances: ThroughEntity[]) => TargetID[],
|
|
137
|
+
public getThroughConstraintFromSource: () => DataObject<ThroughEntity>,
|
|
138
|
+
public getTargetIds: (targetInstances: TargetEntity[]) => TargetID[],
|
|
139
|
+
public getThroughConstraintFromTarget: (
|
|
140
|
+
targetID: TargetID[],
|
|
141
|
+
) => DataObject<ThroughEntity>,
|
|
142
|
+
) {}
|
|
143
|
+
|
|
144
|
+
async create(
|
|
145
|
+
targetModelData: DataObject<TargetEntity>,
|
|
146
|
+
options?: Options & {
|
|
147
|
+
throughData?: DataObject<ThroughEntity>;
|
|
148
|
+
throughOptions?: Options;
|
|
149
|
+
},
|
|
150
|
+
): Promise<TargetEntity> {
|
|
151
|
+
const targetRepository = await this.getTargetRepository();
|
|
152
|
+
const targetInstance = await targetRepository.create(
|
|
153
|
+
targetModelData,
|
|
154
|
+
options,
|
|
155
|
+
);
|
|
156
|
+
await this.link(targetInstance.getId(), options);
|
|
157
|
+
return targetInstance;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async find(
|
|
161
|
+
filter?: Filter<TargetEntity>,
|
|
162
|
+
options?: Options & {
|
|
163
|
+
throughOptions?: Options;
|
|
164
|
+
},
|
|
165
|
+
): Promise<TargetEntity[]> {
|
|
166
|
+
const targetRepository = await this.getTargetRepository();
|
|
167
|
+
const throughRepository = await this.getThroughRepository();
|
|
168
|
+
const sourceConstraint = this.getThroughConstraintFromSource();
|
|
169
|
+
const throughInstances = await throughRepository.find(
|
|
170
|
+
constrainFilter(undefined, sourceConstraint),
|
|
171
|
+
options?.throughOptions,
|
|
172
|
+
);
|
|
173
|
+
const targetConstraint = this.getTargetConstraintFromThroughModels(
|
|
174
|
+
throughInstances,
|
|
175
|
+
);
|
|
176
|
+
return targetRepository.find(
|
|
177
|
+
constrainFilter(filter, targetConstraint),
|
|
178
|
+
options,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async delete(
|
|
183
|
+
where?: Where<TargetEntity>,
|
|
184
|
+
options?: Options & {
|
|
185
|
+
throughOptions?: Options;
|
|
186
|
+
},
|
|
187
|
+
): Promise<Count> {
|
|
188
|
+
const targetRepository = await this.getTargetRepository();
|
|
189
|
+
const throughRepository = await this.getThroughRepository();
|
|
190
|
+
const sourceConstraint = this.getThroughConstraintFromSource();
|
|
191
|
+
const throughInstances = await throughRepository.find(
|
|
192
|
+
constrainFilter(undefined, sourceConstraint),
|
|
193
|
+
options?.throughOptions,
|
|
194
|
+
);
|
|
195
|
+
if (where) {
|
|
196
|
+
// only delete related through models
|
|
197
|
+
// TODO(Agnes): this performance can be improved by only fetching related data
|
|
198
|
+
// TODO: add target ids to the `where` constraint
|
|
199
|
+
const targets = await targetRepository.find({where});
|
|
200
|
+
const targetIds = this.getTargetIds(targets);
|
|
201
|
+
if (targetIds.length > 0) {
|
|
202
|
+
const targetConstraint = this.getThroughConstraintFromTarget(targetIds);
|
|
203
|
+
const constraints = {...targetConstraint, ...sourceConstraint};
|
|
204
|
+
await throughRepository.deleteAll(
|
|
205
|
+
constrainDataObject({}, constraints as DataObject<ThroughEntity>),
|
|
206
|
+
options?.throughOptions,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
// otherwise, delete through models that relate to the sourceId
|
|
211
|
+
const targetFkValues = this.getTargetKeys(throughInstances);
|
|
212
|
+
// delete through instances that have the targets that are going to be deleted
|
|
213
|
+
const throughFkConstraint = this.getThroughConstraintFromTarget(
|
|
214
|
+
targetFkValues,
|
|
215
|
+
);
|
|
216
|
+
await throughRepository.deleteAll(
|
|
217
|
+
constrainWhereOr({}, [sourceConstraint, throughFkConstraint]),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
// delete target(s)
|
|
221
|
+
const targetConstraint = this.getTargetConstraintFromThroughModels(
|
|
222
|
+
throughInstances,
|
|
223
|
+
);
|
|
224
|
+
return targetRepository.deleteAll(
|
|
225
|
+
constrainWhere(where, targetConstraint as Where<TargetEntity>),
|
|
226
|
+
options,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
// only allows patch target instances for now
|
|
230
|
+
async patch(
|
|
231
|
+
dataObject: DataObject<TargetEntity>,
|
|
232
|
+
where?: Where<TargetEntity>,
|
|
233
|
+
options?: Options & {
|
|
234
|
+
throughOptions?: Options;
|
|
235
|
+
},
|
|
236
|
+
): Promise<Count> {
|
|
237
|
+
const targetRepository = await this.getTargetRepository();
|
|
238
|
+
const throughRepository = await this.getThroughRepository();
|
|
239
|
+
const sourceConstraint = this.getThroughConstraintFromSource();
|
|
240
|
+
const throughInstances = await throughRepository.find(
|
|
241
|
+
constrainFilter(undefined, sourceConstraint),
|
|
242
|
+
options?.throughOptions,
|
|
243
|
+
);
|
|
244
|
+
const targetConstraint = this.getTargetConstraintFromThroughModels(
|
|
245
|
+
throughInstances,
|
|
246
|
+
);
|
|
247
|
+
return targetRepository.updateAll(
|
|
248
|
+
constrainDataObject(dataObject, targetConstraint),
|
|
249
|
+
constrainWhere(where, targetConstraint as Where<TargetEntity>),
|
|
250
|
+
options,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async link(
|
|
255
|
+
targetId: TargetID,
|
|
256
|
+
options?: Options & {
|
|
257
|
+
throughData?: DataObject<ThroughEntity>;
|
|
258
|
+
throughOptions?: Options;
|
|
259
|
+
},
|
|
260
|
+
): Promise<void> {
|
|
261
|
+
const throughRepository = await this.getThroughRepository();
|
|
262
|
+
const sourceConstraint = this.getThroughConstraintFromSource();
|
|
263
|
+
const targetConstraint = this.getThroughConstraintFromTarget([targetId]);
|
|
264
|
+
const constraints = {...targetConstraint, ...sourceConstraint};
|
|
265
|
+
await throughRepository.create(
|
|
266
|
+
constrainDataObject(
|
|
267
|
+
options?.throughData ?? {},
|
|
268
|
+
constraints as DataObject<ThroughEntity>,
|
|
269
|
+
),
|
|
270
|
+
options?.throughOptions,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async unlink(
|
|
275
|
+
targetId: TargetID,
|
|
276
|
+
options?: Options & {
|
|
277
|
+
throughOptions?: Options;
|
|
278
|
+
},
|
|
279
|
+
): Promise<void> {
|
|
280
|
+
const throughRepository = await this.getThroughRepository();
|
|
281
|
+
const sourceConstraint = this.getThroughConstraintFromSource();
|
|
282
|
+
const targetConstraint = this.getThroughConstraintFromTarget([targetId]);
|
|
283
|
+
const constraints = {...targetConstraint, ...sourceConstraint};
|
|
284
|
+
await throughRepository.deleteAll(
|
|
285
|
+
constrainDataObject({}, constraints as DataObject<ThroughEntity>),
|
|
286
|
+
options?.throughOptions,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -34,8 +34,7 @@ export function resolveHasManyMetadata(
|
|
|
34
34
|
relationMeta = resolveHasManyMetaHelper(relationMeta);
|
|
35
35
|
|
|
36
36
|
const targetModel = relationMeta.target();
|
|
37
|
-
const targetModelProperties =
|
|
38
|
-
targetModel.definition && targetModel.definition.properties;
|
|
37
|
+
const targetModelProperties = targetModel.definition?.properties;
|
|
39
38
|
|
|
40
39
|
const sourceModel = relationMeta.source;
|
|
41
40
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {Getter} from '@loopback/
|
|
6
|
+
import {Getter} from '@loopback/core';
|
|
7
7
|
import {Count, DataObject, Options} from '../../common-types';
|
|
8
8
|
import {Entity} from '../../model';
|
|
9
9
|
import {Filter, Where} from '../../query';
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
export * from './has-many.decorator';
|
|
7
|
-
export * from './has-many.repository';
|
|
8
6
|
export * from './has-many-repository.factory';
|
|
7
|
+
export * from './has-many-through-repository.factory';
|
|
8
|
+
export * from './has-many-through.repository';
|
|
9
|
+
export * from './has-many.decorator';
|
|
9
10
|
export * from './has-many.inclusion-resolver';
|
|
11
|
+
export * from './has-many.repository';
|
|
@@ -41,8 +41,7 @@ export function resolveHasOneMetadata(
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const targetModel = relationMeta.target();
|
|
44
|
-
const targetModelProperties =
|
|
45
|
-
targetModel.definition && targetModel.definition.properties;
|
|
44
|
+
const targetModelProperties = targetModel.definition?.properties;
|
|
46
45
|
|
|
47
46
|
const sourceModel = relationMeta.source;
|
|
48
47
|
if (!sourceModel || !sourceModel.modelName) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {Getter} from '@loopback/
|
|
6
|
+
import {Getter} from '@loopback/core';
|
|
7
7
|
import {Count, DataObject, Options} from '../../common-types';
|
|
8
8
|
import {EntityNotFoundError} from '../../errors';
|
|
9
9
|
import {Entity} from '../../model';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {PropertyDecoratorFactory} from '@loopback/
|
|
6
|
+
import {PropertyDecoratorFactory} from '@loopback/core';
|
|
7
7
|
import {buildModelDefinition} from '../decorators';
|
|
8
8
|
import {Model, RelationDefinitionMap} from '../model';
|
|
9
9
|
import {RelationType} from './relation.types';
|
|
@@ -117,7 +117,7 @@ export interface BelongsToDefinition extends RelationDefinitionBase {
|
|
|
117
117
|
/*
|
|
118
118
|
* The foreign key in the source model, e.g. Order#customerId.
|
|
119
119
|
*/
|
|
120
|
-
keyFrom
|
|
120
|
+
keyFrom?: string;
|
|
121
121
|
|
|
122
122
|
/*
|
|
123
123
|
* The primary key of the target model, e.g Customer#id.
|
|
@@ -155,7 +155,7 @@ export type RelationMetadata =
|
|
|
155
155
|
| RelationDefinitionBase;
|
|
156
156
|
|
|
157
157
|
// Re-export Getter so that users don't have to import from @loopback/context
|
|
158
|
-
export {Getter} from '@loopback/
|
|
158
|
+
export {Getter} from '@loopback/core';
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
161
|
* @returns An array of resolved values, the items must be ordered in the same
|
|
@@ -41,6 +41,23 @@ export function constrainWhere<T extends object>(
|
|
|
41
41
|
const builder = new WhereBuilder<T>(where);
|
|
42
42
|
return builder.impose(constraint).build();
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A utility function which takes a where filter and enforces constraint(s)
|
|
46
|
+
* on it with OR clause
|
|
47
|
+
* @param originalWhere - the where filter to apply the constrain(s) to
|
|
48
|
+
* @param constraint - the constraint which is to be applied on the filter with
|
|
49
|
+
* or clause
|
|
50
|
+
* @returns Filter the modified filter with the constraint, otherwise
|
|
51
|
+
* the original filter
|
|
52
|
+
*/
|
|
53
|
+
export function constrainWhereOr<T extends object>(
|
|
54
|
+
originalWhere: Where<T> | undefined,
|
|
55
|
+
constraint: Where<T>[],
|
|
56
|
+
): Where<T> {
|
|
57
|
+
const where = cloneDeep(originalWhere);
|
|
58
|
+
const builder = new WhereBuilder<T>(where);
|
|
59
|
+
return builder.or(constraint).build();
|
|
60
|
+
}
|
|
44
61
|
/**
|
|
45
62
|
* A utility function which takes a model instance data and enforces constraint(s)
|
|
46
63
|
* on it
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {Getter} from '@loopback/
|
|
6
|
+
import {Getter} from '@loopback/core';
|
|
7
7
|
import assert from 'assert';
|
|
8
8
|
import legacy from 'loopback-datasource-juggler';
|
|
9
9
|
import {
|
|
@@ -29,9 +29,11 @@ import {
|
|
|
29
29
|
BelongsToDefinition,
|
|
30
30
|
createBelongsToAccessor,
|
|
31
31
|
createHasManyRepositoryFactory,
|
|
32
|
+
createHasManyThroughRepositoryFactory,
|
|
32
33
|
createHasOneRepositoryFactory,
|
|
33
34
|
HasManyDefinition,
|
|
34
35
|
HasManyRepositoryFactory,
|
|
36
|
+
HasManyThroughRepositoryFactory,
|
|
35
37
|
HasOneDefinition,
|
|
36
38
|
HasOneRepositoryFactory,
|
|
37
39
|
includeRelatedModels,
|
|
@@ -283,6 +285,62 @@ export class DefaultCrudRepository<
|
|
|
283
285
|
);
|
|
284
286
|
}
|
|
285
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Function to create a constrained hasManyThrough relation repository factory
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* class CustomerRepository extends DefaultCrudRepository<
|
|
294
|
+
* Customer,
|
|
295
|
+
* typeof Customer.prototype.id,
|
|
296
|
+
* CustomerRelations
|
|
297
|
+
* > {
|
|
298
|
+
* public readonly cartItems: HasManyRepositoryFactory<CartItem, typeof Customer.prototype.id>;
|
|
299
|
+
*
|
|
300
|
+
* constructor(
|
|
301
|
+
* protected db: juggler.DataSource,
|
|
302
|
+
* cartItemRepository: EntityCrudRepository<CartItem, typeof, CartItem.prototype.id>,
|
|
303
|
+
* throughRepository: EntityCrudRepository<Through, typeof Through.prototype.id>,
|
|
304
|
+
* ) {
|
|
305
|
+
* super(Customer, db);
|
|
306
|
+
* this.cartItems = this.createHasManyThroughRepositoryFactoryFor(
|
|
307
|
+
* 'cartItems',
|
|
308
|
+
* cartItemRepository,
|
|
309
|
+
* );
|
|
310
|
+
* }
|
|
311
|
+
* }
|
|
312
|
+
* ```
|
|
313
|
+
*
|
|
314
|
+
* @param relationName - Name of the relation defined on the source model
|
|
315
|
+
* @param targetRepo - Target repository instance
|
|
316
|
+
* @param throughRepo - Through repository instance
|
|
317
|
+
*/
|
|
318
|
+
protected createHasManyThroughRepositoryFactoryFor<
|
|
319
|
+
Target extends Entity,
|
|
320
|
+
TargetID,
|
|
321
|
+
Through extends Entity,
|
|
322
|
+
ThroughID,
|
|
323
|
+
ForeignKeyType
|
|
324
|
+
>(
|
|
325
|
+
relationName: string,
|
|
326
|
+
targetRepoGetter: Getter<EntityCrudRepository<Target, TargetID>>,
|
|
327
|
+
throughRepoGetter: Getter<EntityCrudRepository<Through, ThroughID>>,
|
|
328
|
+
): HasManyThroughRepositoryFactory<
|
|
329
|
+
Target,
|
|
330
|
+
TargetID,
|
|
331
|
+
Through,
|
|
332
|
+
ForeignKeyType
|
|
333
|
+
> {
|
|
334
|
+
const meta = this.entityClass.definition.relations[relationName];
|
|
335
|
+
return createHasManyThroughRepositoryFactory<
|
|
336
|
+
Target,
|
|
337
|
+
TargetID,
|
|
338
|
+
Through,
|
|
339
|
+
ThroughID,
|
|
340
|
+
ForeignKeyType
|
|
341
|
+
>(meta as HasManyDefinition, targetRepoGetter, throughRepoGetter);
|
|
342
|
+
}
|
|
343
|
+
|
|
286
344
|
/**
|
|
287
345
|
* @deprecated
|
|
288
346
|
* Function to create a belongs to accessor
|