@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/dist/decorators/metadata.d.ts +1 -1
  4. package/dist/decorators/metadata.js +6 -6
  5. package/dist/decorators/metadata.js.map +1 -1
  6. package/dist/decorators/model.decorator.d.ts +1 -1
  7. package/dist/decorators/model.decorator.js +10 -10
  8. package/dist/decorators/model.decorator.js.map +1 -1
  9. package/dist/decorators/repository.decorator.js +4 -4
  10. package/dist/decorators/repository.decorator.js.map +1 -1
  11. package/dist/define-model-class.d.ts +2 -2
  12. package/dist/mixins/repository.mixin.d.ts +23 -12
  13. package/dist/mixins/repository.mixin.js +13 -7
  14. package/dist/mixins/repository.mixin.js.map +1 -1
  15. package/dist/model.d.ts +19 -2
  16. package/dist/model.js +46 -2
  17. package/dist/model.js.map +1 -1
  18. package/dist/query.d.ts +30 -0
  19. package/dist/query.js +50 -0
  20. package/dist/query.js.map +1 -1
  21. package/dist/relations/belongs-to/belongs-to.decorator.js +2 -2
  22. package/dist/relations/belongs-to/belongs-to.decorator.js.map +1 -1
  23. package/dist/relations/belongs-to/belongs-to.helpers.d.ts +1 -0
  24. package/dist/relations/belongs-to/belongs-to.helpers.js +13 -6
  25. package/dist/relations/belongs-to/belongs-to.helpers.js.map +1 -1
  26. package/dist/relations/belongs-to/belongs-to.repository.d.ts +1 -1
  27. package/dist/relations/has-many/has-many-through-repository.factory.d.ts +11 -0
  28. package/dist/relations/has-many/has-many-through-repository.factory.js +31 -0
  29. package/dist/relations/has-many/has-many-through-repository.factory.js.map +1 -0
  30. package/dist/relations/has-many/has-many-through.helpers.d.ts +122 -14
  31. package/dist/relations/has-many/has-many-through.helpers.js +153 -20
  32. package/dist/relations/has-many/has-many-through.helpers.js.map +1 -1
  33. package/dist/relations/has-many/has-many-through.repository.d.ts +39 -2
  34. package/dist/relations/has-many/has-many-through.repository.js +87 -0
  35. package/dist/relations/has-many/has-many-through.repository.js.map +1 -1
  36. package/dist/relations/has-many/has-many.helpers.js +2 -1
  37. package/dist/relations/has-many/has-many.helpers.js.map +1 -1
  38. package/dist/relations/has-many/has-many.repository.d.ts +1 -1
  39. package/dist/relations/has-many/index.d.ts +4 -2
  40. package/dist/relations/has-many/index.js +4 -2
  41. package/dist/relations/has-many/index.js.map +1 -1
  42. package/dist/relations/has-one/has-one.helpers.js +2 -1
  43. package/dist/relations/has-one/has-one.helpers.js.map +1 -1
  44. package/dist/relations/has-one/has-one.repository.d.ts +1 -1
  45. package/dist/relations/relation.decorator.js +6 -6
  46. package/dist/relations/relation.decorator.js.map +1 -1
  47. package/dist/relations/relation.types.d.ts +2 -2
  48. package/dist/relations/relation.types.js +2 -2
  49. package/dist/relations/relation.types.js.map +1 -1
  50. package/dist/repositories/constraint-utils.d.ts +10 -0
  51. package/dist/repositories/constraint-utils.js +16 -1
  52. package/dist/repositories/constraint-utils.js.map +1 -1
  53. package/dist/repositories/legacy-juggler-bridge.d.ts +33 -2
  54. package/dist/repositories/legacy-juggler-bridge.js +34 -0
  55. package/dist/repositories/legacy-juggler-bridge.js.map +1 -1
  56. package/package.json +15 -13
  57. package/src/decorators/metadata.ts +1 -1
  58. package/src/decorators/model.decorator.ts +1 -1
  59. package/src/decorators/repository.decorator.ts +1 -1
  60. package/src/define-model-class.ts +2 -2
  61. package/src/mixins/repository.mixin.ts +8 -2
  62. package/src/model.ts +64 -4
  63. package/src/query.ts +55 -0
  64. package/src/relations/belongs-to/belongs-to.decorator.ts +1 -1
  65. package/src/relations/belongs-to/belongs-to.helpers.ts +19 -8
  66. package/src/relations/belongs-to/belongs-to.repository.ts +1 -1
  67. package/src/relations/has-many/has-many-through-repository.factory.ts +104 -0
  68. package/src/relations/has-many/has-many-through.helpers.ts +182 -21
  69. package/src/relations/has-many/has-many-through.repository.ts +191 -2
  70. package/src/relations/has-many/has-many.helpers.ts +1 -2
  71. package/src/relations/has-many/has-many.repository.ts +1 -1
  72. package/src/relations/has-many/index.ts +4 -2
  73. package/src/relations/has-one/has-one.helpers.ts +1 -2
  74. package/src/relations/has-one/has-one.repository.ts +1 -1
  75. package/src/relations/relation.decorator.ts +1 -1
  76. package/src/relations/relation.types.ts +2 -2
  77. package/src/repositories/constraint-utils.ts +17 -0
  78. 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 used to query target
30
- * @param relationMeta - hasManyThrough metadata to resolve
31
- * @param throughInstances - Instances of through entities used to constrain the target
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
- * createTargetConstraint(resolvedMetadata, [
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: 2,
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 createTargetConstraint<
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 targetPrimaryKey = relationMeta.keyTo;
68
- const targetFkName = relationMeta.through.keyTo;
69
- const fkValues = throughInstances.map(
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
- * Creates constraint used to query through model
88
+ * Returns an array of target fks of the given throughInstances.
82
89
  *
83
- * @param relationMeta - hasManyThrough metadata to resolve
84
- * @param fkValue - Value of the foreign key of the source model used to constrain through
85
- * @param targetInstance - Instance of target entity used to constrain through
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
- * createThroughConstraint(resolvedMetadata, 1);
160
+ * createThroughConstraintFromSource(resolvedMetadata, 1);
161
+ *
162
+ * >>> {categoryId: 1}
101
163
  * ```
102
164
  */
103
- export function createThroughConstraint<Through extends Entity, ForeignKeyType>(
165
+ export function createThroughConstraintFromSource<
166
+ Through extends Entity,
167
+ SourceID
168
+ >(
104
169
  relationMeta: HasManyThroughResolvedDefinition,
105
- fkValue: ForeignKeyType,
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 explict cast is needed because of a limitation of type inference
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 {Count, DataObject, Entity, Filter, Options, Where} from '../..';
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<Target>;
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/context';
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/context';
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/context';
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: string;
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/context';
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/context';
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