@loopback/repository 2.5.1 → 2.9.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 (81) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/decorators/metadata.d.ts +1 -1
  3. package/dist/decorators/metadata.js +6 -6
  4. package/dist/decorators/metadata.js.map +1 -1
  5. package/dist/decorators/model.decorator.d.ts +1 -1
  6. package/dist/decorators/model.decorator.js +10 -10
  7. package/dist/decorators/model.decorator.js.map +1 -1
  8. package/dist/decorators/repository.decorator.js +4 -4
  9. package/dist/decorators/repository.decorator.js.map +1 -1
  10. package/dist/define-model-class.d.ts +2 -2
  11. package/dist/mixins/repository.mixin.d.ts +82 -43
  12. package/dist/mixins/repository.mixin.js +52 -15
  13. package/dist/mixins/repository.mixin.js.map +1 -1
  14. package/dist/model.js +3 -2
  15. package/dist/model.js.map +1 -1
  16. package/dist/query.d.ts +30 -0
  17. package/dist/query.js +50 -0
  18. package/dist/query.js.map +1 -1
  19. package/dist/relations/belongs-to/belongs-to.decorator.js +2 -2
  20. package/dist/relations/belongs-to/belongs-to.decorator.js.map +1 -1
  21. package/dist/relations/belongs-to/belongs-to.repository.d.ts +1 -1
  22. package/dist/relations/has-many/has-many-through-repository.factory.d.ts +11 -0
  23. package/dist/relations/has-many/has-many-through-repository.factory.js +28 -0
  24. package/dist/relations/has-many/has-many-through-repository.factory.js.map +1 -0
  25. package/dist/relations/has-many/has-many-through.helpers.d.ts +87 -14
  26. package/dist/relations/has-many/has-many-through.helpers.js +114 -20
  27. package/dist/relations/has-many/has-many-through.helpers.js.map +1 -1
  28. package/dist/relations/has-many/has-many-through.repository.d.ts +38 -2
  29. package/dist/relations/has-many/has-many-through.repository.js +71 -0
  30. package/dist/relations/has-many/has-many-through.repository.js.map +1 -1
  31. package/dist/relations/has-many/has-many.helpers.js +3 -2
  32. package/dist/relations/has-many/has-many.helpers.js.map +1 -1
  33. package/dist/relations/has-many/has-many.repository.d.ts +1 -1
  34. package/dist/relations/has-many/index.d.ts +4 -2
  35. package/dist/relations/has-many/index.js +4 -2
  36. package/dist/relations/has-many/index.js.map +1 -1
  37. package/dist/relations/has-one/has-one.helpers.js +2 -1
  38. package/dist/relations/has-one/has-one.helpers.js.map +1 -1
  39. package/dist/relations/has-one/has-one.repository.d.ts +1 -1
  40. package/dist/relations/relation.decorator.js +7 -7
  41. package/dist/relations/relation.decorator.js.map +1 -1
  42. package/dist/relations/relation.types.d.ts +1 -1
  43. package/dist/relations/relation.types.js +2 -2
  44. package/dist/relations/relation.types.js.map +1 -1
  45. package/dist/repositories/constraint-utils.d.ts +10 -0
  46. package/dist/repositories/constraint-utils.js +16 -1
  47. package/dist/repositories/constraint-utils.js.map +1 -1
  48. package/dist/repositories/legacy-juggler-bridge.d.ts +33 -2
  49. package/dist/repositories/legacy-juggler-bridge.js +34 -0
  50. package/dist/repositories/legacy-juggler-bridge.js.map +1 -1
  51. package/dist/type-resolver.js +2 -1
  52. package/dist/type-resolver.js.map +1 -1
  53. package/dist/types/index.js +1 -1
  54. package/dist/types/index.js.map +1 -1
  55. package/dist/types/null.js +1 -1
  56. package/dist/types/null.js.map +1 -1
  57. package/package.json +14 -12
  58. package/src/decorators/metadata.ts +1 -1
  59. package/src/decorators/model.decorator.ts +1 -1
  60. package/src/decorators/repository.decorator.ts +1 -1
  61. package/src/define-model-class.ts +2 -2
  62. package/src/mixins/repository.mixin.ts +70 -41
  63. package/src/model.ts +2 -2
  64. package/src/query.ts +55 -0
  65. package/src/relations/belongs-to/belongs-to.decorator.ts +1 -1
  66. package/src/relations/belongs-to/belongs-to.repository.ts +1 -1
  67. package/src/relations/has-many/has-many-through-repository.factory.ts +100 -0
  68. package/src/relations/has-many/has-many-through.helpers.ts +138 -21
  69. package/src/relations/has-many/has-many-through.repository.ts +174 -2
  70. package/src/relations/has-many/has-many.helpers.ts +2 -3
  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 +2 -2
  76. package/src/relations/relation.types.ts +1 -1
  77. package/src/repositories/constraint-utils.ts +17 -0
  78. package/src/repositories/legacy-juggler-bridge.ts +59 -1
  79. package/src/type-resolver.ts +2 -1
  80. package/src/types/index.ts +1 -1
  81. package/src/types/null.ts +1 -1
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@loopback/repository",
3
- "version": "2.5.1",
4
- "description": "Repository based persistence for LoopBack 4",
3
+ "version": "2.9.0",
4
+ "description": "Define and implement a common set of interfaces for interacting with databases",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "engines": {
8
- "node": ">=10"
8
+ "node": ">=10.16"
9
9
  },
10
10
  "scripts": {
11
11
  "acceptance": "lb-mocha \"dist/__tests__/acceptance/**/*.js\"",
@@ -18,19 +18,21 @@
18
18
  "author": "IBM Corp.",
19
19
  "copyright.owner": "IBM Corp.",
20
20
  "license": "MIT",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
21
24
  "devDependencies": {
22
- "@loopback/build": "^5.4.1",
23
- "@loopback/eslint-config": "^7.0.1",
24
- "@loopback/testlab": "^3.1.5",
25
+ "@loopback/build": "^6.1.0",
26
+ "@loopback/eslint-config": "^8.0.3",
27
+ "@loopback/testlab": "^3.2.0",
25
28
  "@types/bson": "^4.0.2",
26
- "@types/json-schema": "^7.0.4",
27
- "@types/lodash": "^4.14.152",
28
- "@types/node": "^10.17.24",
29
+ "@types/json-schema": "^7.0.5",
30
+ "@types/lodash": "^4.14.157",
31
+ "@types/node": "^10.17.26",
29
32
  "bson": "4.0.4"
30
33
  },
31
34
  "dependencies": {
32
- "@loopback/context": "^3.8.1",
33
- "@loopback/core": "^2.7.0",
35
+ "@loopback/core": "^2.9.1",
34
36
  "@types/debug": "^4.1.5",
35
37
  "debug": "^4.1.1",
36
38
  "lodash": "^4.17.15",
@@ -48,5 +50,5 @@
48
50
  "url": "https://github.com/strongloop/loopback-next.git",
49
51
  "directory": "packages/repository"
50
52
  },
51
- "gitHead": "62aea854bf85c5a5995b59e6908fe5409f7eea96"
53
+ "gitHead": "b89db3d3b8be6a36e63e91c2331d217fda7538de"
52
54
  }
@@ -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 {InspectionOptions, MetadataInspector} from '@loopback/context';
6
+ import {InspectionOptions, MetadataInspector} from '@loopback/core';
7
7
  import {ModelDefinition, RelationDefinitionMap} from '../model';
8
8
  import {RELATIONS_KEY} from '../relations';
9
9
  import {
@@ -9,7 +9,7 @@ import {
9
9
  MetadataInspector,
10
10
  MetadataMap,
11
11
  PropertyDecoratorFactory,
12
- } from '@loopback/context';
12
+ } from '@loopback/core';
13
13
  import {
14
14
  ModelDefinition,
15
15
  ModelDefinitionSyntax,
@@ -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 {Context, inject, Injection} from '@loopback/context';
6
+ import {Context, inject, Injection} from '@loopback/core';
7
7
  import assert from 'assert';
8
8
  import {Class} from '../common-types';
9
9
  import {DataSource} from '../datasource';
@@ -79,10 +79,10 @@ export function defineModelClass<
79
79
  export type DynamicModelCtor<
80
80
  BaseCtor extends typeof Model,
81
81
  Props extends object
82
- > = BaseCtor & {
82
+ > = {
83
83
  /** Model constructor accepting partial model data. */
84
84
  new (data?: DataObject<PrototypeOf<BaseCtor> & Props>): PrototypeOf<
85
85
  BaseCtor
86
86
  > &
87
87
  Props;
88
- };
88
+ } & BaseCtor;
@@ -8,7 +8,7 @@ import {
8
8
  BindingFromClassOptions,
9
9
  BindingScope,
10
10
  createBindingFromClass,
11
- } from '@loopback/context';
11
+ } from '@loopback/core';
12
12
  import {
13
13
  Application,
14
14
  Component,
@@ -27,35 +27,9 @@ const debug = debugFactory('loopback:repository:mixin');
27
27
 
28
28
  // FIXME(rfeng): Workaround for https://github.com/microsoft/rushstack/pull/1867
29
29
  /* eslint-disable @typescript-eslint/no-unused-vars */
30
- import {
31
- BindingAddress,
32
- BindingFilter,
33
- JSONObject,
34
- Provider,
35
- Context,
36
- ContextSubscriptionManager,
37
- ContextEvent,
38
- Interceptor,
39
- InterceptorBindingOptions,
40
- ResolutionOptions,
41
- BindingKey,
42
- ValueOrPromise,
43
- ContextEventObserver,
44
- ContextObserver,
45
- Subscription,
46
- BindingComparator,
47
- ContextView,
48
- ResolutionSession,
49
- BindingCreationPolicy,
50
- ContextInspectOptions,
51
- } from '@loopback/context';
52
- import {
53
- Server,
54
- ApplicationConfig,
55
- ApplicationMetadata,
56
- LifeCycleObserver,
57
- ServiceOptions,
58
- } from '@loopback/core';
30
+ import * as loopbackContext from '@loopback/core';
31
+ import * as loopbackCore from '@loopback/core';
32
+ /* eslint-enable @typescript-eslint/no-unused-vars */
59
33
 
60
34
  /**
61
35
  * A mixin class for Application that creates a .repository()
@@ -70,6 +44,12 @@ import {
70
44
  * Please note: the members in the mixin function are documented in a dummy class
71
45
  * called <a href="#RepositoryMixinDoc">RepositoryMixinDoc</a>
72
46
  *
47
+ * @param superClass - Application class
48
+ * @returns A new class that extends the super class with repository related
49
+ * methods
50
+ *
51
+ * @typeParam T - Type of the application class as the target for the mixin
52
+ *
73
53
  */
74
54
  export function RepositoryMixin<T extends MixinTarget<Application>>(
75
55
  superClass: T,
@@ -214,35 +194,66 @@ export function RepositoryMixin<T extends MixinTarget<Application>>(
214
194
  */
215
195
  // Unfortunately, TypeScript does not allow overriding methods inherited
216
196
  // from mapped types. https://github.com/microsoft/TypeScript/issues/38496
217
- // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
197
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
218
198
  // @ts-ignore
219
199
  public component<C extends Component = Component>(
220
200
  componentCtor: Constructor<C>,
221
201
  nameOrOptions?: string | BindingFromClassOptions,
222
202
  ) {
223
203
  const binding = super.component(componentCtor, nameOrOptions);
224
- this.mountComponentRepositories(componentCtor);
204
+ const instance = this.getSync<C & RepositoryComponent>(binding.key);
205
+ this.mountComponentRepositories(instance);
206
+ this.mountComponentModels(instance);
225
207
  return binding;
226
208
  }
227
209
 
228
210
  /**
229
211
  * Get an instance of a component and mount all it's
230
212
  * repositories. This function is intended to be used internally
231
- * by component()
213
+ * by `component()`.
214
+ *
215
+ * NOTE: Calling `mountComponentRepositories` with a component class
216
+ * constructor is deprecated. You should instantiate the component
217
+ * yourself and provide the component instance instead.
232
218
  *
233
- * @param component - The component to mount repositories of
219
+ * @param componentInstanceOrClass - The component to mount repositories of
220
+ * @internal
234
221
  */
235
- mountComponentRepositories(component: Class<unknown>) {
236
- const componentKey = `${CoreBindings.COMPONENTS}.${component.name}`;
237
- const compInstance = this.getSync<{
238
- repositories?: Class<Repository<Model>>[];
239
- }>(componentKey);
222
+ mountComponentRepositories(
223
+ // accept also component class to preserve backwards compatibility
224
+ // TODO(semver-major) Remove support for component class constructor
225
+ componentInstanceOrClass: Class<unknown> | RepositoryComponent,
226
+ ) {
227
+ const component = resolveComponentInstance(this);
240
228
 
241
- if (compInstance.repositories) {
242
- for (const repo of compInstance.repositories) {
229
+ if (component.repositories) {
230
+ for (const repo of component.repositories) {
243
231
  this.repository(repo);
244
232
  }
245
233
  }
234
+
235
+ // `Readonly<Application>` is a hack to remove protected members
236
+ // and thus allow `this` to be passed as a value for `ctx`
237
+ function resolveComponentInstance(ctx: Readonly<Application>) {
238
+ if (typeof componentInstanceOrClass !== 'function')
239
+ return componentInstanceOrClass;
240
+
241
+ const componentName = componentInstanceOrClass.name;
242
+ const componentKey = `${CoreBindings.COMPONENTS}.${componentName}`;
243
+ return ctx.getSync<RepositoryComponent>(componentKey);
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Bind all model classes provided by a component.
249
+ * @param component
250
+ * @internal
251
+ */
252
+ mountComponentModels(component: RepositoryComponent) {
253
+ if (!component.models) return;
254
+ for (const m of component.models) {
255
+ this.model(m);
256
+ }
246
257
  }
247
258
 
248
259
  /**
@@ -287,6 +298,24 @@ export function RepositoryMixin<T extends MixinTarget<Application>>(
287
298
  };
288
299
  }
289
300
 
301
+ /**
302
+ * This interface describes additional Component properties
303
+ * allowing components to contribute Repository-related artifacts.
304
+ */
305
+ export interface RepositoryComponent {
306
+ /**
307
+ * An optional list of Repository classes to bind for dependency injection
308
+ * via `app.repository()` API.
309
+ */
310
+ repositories?: Class<Repository<Model>>[];
311
+
312
+ /**
313
+ * An optional list of Model classes to bind for dependency injection
314
+ * via `app.model()` API.
315
+ */
316
+ models?: Class<Model>[];
317
+ }
318
+
290
319
  /**
291
320
  * Normalize name or options to `BindingFromClassOptions`
292
321
  * @param nameOrOptions - Name or options for binding from class
package/src/model.ts CHANGED
@@ -216,7 +216,7 @@ function asObject(value: any, options?: Options): any {
216
216
  */
217
217
  export abstract class Model {
218
218
  static get modelName(): string {
219
- return (this.definition && this.definition.name) || this.name;
219
+ return this.definition?.name || this.name;
220
220
  }
221
221
 
222
222
  static definition: ModelDefinition;
@@ -268,7 +268,7 @@ export abstract class Model {
268
268
  const def = (this.constructor as typeof Model).definition;
269
269
  const obj: AnyObject = {};
270
270
 
271
- if (options && options.ignoreUnknownProperties === false) {
271
+ if (options?.ignoreUnknownProperties === false) {
272
272
  const hiddenProperties: string[] = def?.settings.hiddenProperties || [];
273
273
  for (const p in this) {
274
274
  if (!hiddenProperties.includes(p)) {
package/src/query.ts CHANGED
@@ -442,6 +442,61 @@ export class WhereBuilder<MT extends object = AnyObject> {
442
442
  return this;
443
443
  }
444
444
 
445
+ /**
446
+ * Add a `like` condition
447
+ * @param key - Property name
448
+ * @param val - Regexp condition
449
+ */
450
+ like<K extends KeyOf<MT>>(key: K, val: MT[K]): this {
451
+ const w: Where<MT> = {};
452
+ w[key] = {like: val};
453
+ return this.add(w);
454
+ }
455
+
456
+ /**
457
+ * Add a `nlike` condition
458
+ * @param key - Property name
459
+ * @param val - Regexp condition
460
+ */
461
+ nlike<K extends KeyOf<MT>>(key: K, val: MT[K]): this {
462
+ const w: Where<MT> = {};
463
+ w[key] = {nlike: val};
464
+ return this.add(w);
465
+ }
466
+
467
+ /**
468
+ * Add a `ilike` condition
469
+ * @param key - Property name
470
+ * @param val - Regexp condition
471
+ */
472
+ ilike<K extends KeyOf<MT>>(key: K, val: MT[K]): this {
473
+ const w: Where<MT> = {};
474
+ w[key] = {ilike: val};
475
+ return this.add(w);
476
+ }
477
+
478
+ /**
479
+ * Add a `nilike` condition
480
+ * @param key - Property name
481
+ * @param val - Regexp condition
482
+ */
483
+ nilike<K extends KeyOf<MT>>(key: K, val: MT[K]): this {
484
+ const w: Where<MT> = {};
485
+ w[key] = {nilike: val};
486
+ return this.add(w);
487
+ }
488
+
489
+ /**
490
+ * Add a `regexp` condition
491
+ * @param key - Property name
492
+ * @param val - Regexp condition
493
+ */
494
+ regexp<K extends KeyOf<MT>>(key: K, val: string | RegExp): this {
495
+ const w: Where<MT> = {};
496
+ w[key] = {regexp: val};
497
+ return this.add(w);
498
+ }
499
+
445
500
  /**
446
501
  * Get the where object
447
502
  */
@@ -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 {MetadataInspector} from '@loopback/context';
6
+ import {MetadataInspector} from '@loopback/core';
7
7
  import {property} from '../../decorators/model.decorator';
8
8
  import {Entity, EntityResolver, PropertyDefinition} from '../../model';
9
9
  import {relation} from '../relation.decorator';
@@ -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 {DataObject, Options} from '../../common-types';
8
8
  import {EntityNotFoundError} from '../../errors';
9
9
  import {Entity} from '../../model';
@@ -0,0 +1,100 @@
1
+ // Copyright IBM Corp. 2020. All Rights Reserved.
2
+ // Node module: @loopback/repository
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+ import {
6
+ DataObject,
7
+ Entity,
8
+ EntityCrudRepository,
9
+ Getter,
10
+ HasManyDefinition,
11
+ } from '../..';
12
+ import {
13
+ createTargetConstraintFromThrough,
14
+ createThroughConstraintFromSource,
15
+ createThroughConstraintFromTarget,
16
+ getTargetKeysFromThroughModels,
17
+ resolveHasManyThroughMetadata,
18
+ } from './has-many-through.helpers';
19
+ import {
20
+ DefaultHasManyThroughRepository,
21
+ HasManyThroughRepository,
22
+ } from './has-many-through.repository';
23
+
24
+ /**
25
+ * a factory to generate hasManyThrough repository class.
26
+ *
27
+ * Warning: The hasManyThrough interface is experimental and is subject to change.
28
+ * If backwards-incompatible changes are made, a new major version may not be
29
+ * released.
30
+ */
31
+
32
+ export type HasManyThroughRepositoryFactory<
33
+ TargetEntity extends Entity,
34
+ TargetID,
35
+ ThroughEntity extends Entity,
36
+ SourceID
37
+ > = (
38
+ fkValue: SourceID,
39
+ ) => HasManyThroughRepository<TargetEntity, TargetID, ThroughEntity>;
40
+
41
+ export function createHasManyThroughRepositoryFactory<
42
+ Target extends Entity,
43
+ TargetID,
44
+ Through extends Entity,
45
+ ThroughID,
46
+ SourceID
47
+ >(
48
+ relationMetadata: HasManyDefinition,
49
+ targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
50
+ throughRepositoryGetter: Getter<EntityCrudRepository<Through, ThroughID>>,
51
+ ): HasManyThroughRepositoryFactory<Target, TargetID, Through, SourceID> {
52
+ const meta = resolveHasManyThroughMetadata(relationMetadata);
53
+ const result = function (fkValue: SourceID) {
54
+ function getTargetConstraintFromThroughModels(
55
+ throughInstances: Through[],
56
+ ): DataObject<Target> {
57
+ return createTargetConstraintFromThrough<Target, Through>(
58
+ meta,
59
+ throughInstances,
60
+ );
61
+ }
62
+ function getTargetKeys(throughInstances: Through[]): TargetID[] {
63
+ return getTargetKeysFromThroughModels(meta, throughInstances);
64
+ }
65
+ function getThroughConstraintFromSource(): DataObject<Through> {
66
+ const constraint: DataObject<Through> = createThroughConstraintFromSource<
67
+ Through,
68
+ SourceID
69
+ >(meta, fkValue);
70
+ return constraint;
71
+ }
72
+
73
+ function getThroughConstraintFromTarget(
74
+ fkValues: TargetID[],
75
+ ): DataObject<Through> {
76
+ const constraint: DataObject<Through> = createThroughConstraintFromTarget<
77
+ Through,
78
+ TargetID
79
+ >(meta, fkValues);
80
+ return constraint;
81
+ }
82
+
83
+ return new DefaultHasManyThroughRepository<
84
+ Target,
85
+ TargetID,
86
+ EntityCrudRepository<Target, TargetID>,
87
+ Through,
88
+ ThroughID,
89
+ EntityCrudRepository<Through, ThroughID>
90
+ >(
91
+ targetRepositoryGetter,
92
+ throughRepositoryGetter,
93
+ getTargetConstraintFromThroughModels,
94
+ getTargetKeys,
95
+ getThroughConstraintFromSource,
96
+ getThroughConstraintFromTarget,
97
+ );
98
+ };
99
+ return result;
100
+ }
@@ -1,7 +1,13 @@
1
+ // Copyright IBM Corp. 2020. All Rights Reserved.
2
+ // Node module: @loopback/repository
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
1
6
  import debugFactory from 'debug';
2
7
  import {camelCase} from 'lodash';
3
8
  import {
4
9
  DataObject,
10
+ deduplicate,
5
11
  Entity,
6
12
  HasManyDefinition,
7
13
  InvalidRelationError,
@@ -21,10 +27,9 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {
21
27
  };
22
28
 
23
29
  /**
24
- * Creates constraint used to query target
25
- * @param relationMeta - hasManyThrough metadata to resolve
26
- * @param throughInstances - Instances of through entities used to constrain the target
27
- * @internal
30
+ * Creates target constraint based on through models
31
+ * @param relationMeta - resolved hasManyThrough metadata
32
+ * @param throughInstances - an array of through instances
28
33
  *
29
34
  * @example
30
35
  * ```ts
@@ -38,33 +43,40 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {
38
43
  * keyTo: 'productId',
39
44
  * },
40
45
  * };
41
-
42
- * createTargetConstraint(resolvedMetadata, [
46
+ * createTargetConstraintFromThrough(resolvedMetadata,[{
47
+ id: 2,
48
+ categoryId: 2,
49
+ productId: 8,
50
+ }]);
51
+ * >>> {id: 8}
52
+ * createTargetConstraintFromThrough(resolvedMetadata, [
43
53
  {
44
54
  id: 2,
45
55
  categoryId: 2,
46
56
  productId: 8,
47
57
  }, {
48
- id: 2,
58
+ id: 1,
49
59
  categoryId: 2,
50
60
  productId: 9,
51
61
  }
52
62
  ]);
63
+
64
+ >>> {id: {inq: [9, 8]}}
53
65
  * ```
54
66
  */
55
- export function createTargetConstraint<
67
+ export function createTargetConstraintFromThrough<
56
68
  Target extends Entity,
57
69
  Through extends Entity
58
70
  >(
59
71
  relationMeta: HasManyThroughResolvedDefinition,
60
72
  throughInstances: Through[],
61
73
  ): DataObject<Target> {
62
- const targetPrimaryKey = relationMeta.keyTo;
63
- const targetFkName = relationMeta.through.keyTo;
64
- const fkValues = throughInstances.map(
65
- (throughInstance: Through) =>
66
- throughInstance[targetFkName as keyof Through],
74
+ const fkValues = getTargetKeysFromThroughModels(
75
+ relationMeta,
76
+ throughInstances,
67
77
  );
78
+ const targetPrimaryKey = relationMeta.keyTo;
79
+
68
80
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
81
  const constraint: any = {
70
82
  [targetPrimaryKey]: fkValues.length === 1 ? fkValues[0] : {inq: fkValues},
@@ -73,11 +85,64 @@ export function createTargetConstraint<
73
85
  }
74
86
 
75
87
  /**
76
- * Creates constraint used to query through model
88
+ * Returns an array of target fks of the given throughInstances.
77
89
  *
78
- * @param relationMeta - hasManyThrough metadata to resolve
79
- * @param fkValue - Value of the foreign key of the source model used to constrain through
80
- * @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
81
146
  * @internal
82
147
  *
83
148
  * @example
@@ -92,18 +157,70 @@ export function createTargetConstraint<
92
157
  * keyTo: 'productId',
93
158
  * },
94
159
  * };
95
- * createThroughConstraint(resolvedMetadata, 1);
160
+ * createThroughConstraintFromSource(resolvedMetadata, 1);
161
+ *
162
+ * >>> {categoryId: 1}
96
163
  * ```
97
164
  */
98
- export function createThroughConstraint<Through extends Entity, ForeignKeyType>(
165
+ export function createThroughConstraintFromSource<
166
+ Through extends Entity,
167
+ SourceID
168
+ >(
99
169
  relationMeta: HasManyThroughResolvedDefinition,
100
- fkValue: ForeignKeyType,
170
+ fkValue: SourceID,
101
171
  ): DataObject<Through> {
102
172
  const sourceFkName = relationMeta.through.keyFrom;
103
173
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
174
  const constraint: any = {[sourceFkName]: fkValue};
105
175
  return constraint;
106
176
  }
177
+ /**
178
+ * Creates through constraint based on the target foreign key
179
+ *
180
+ * @param relationMeta - resolved hasManyThrough metadata
181
+ * @param fkValue an array of the target instance foreign keys
182
+ * @internal
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
+ * createThroughConstraintFromTarget(resolvedMetadata, [3]);
197
+ *
198
+ * >>> {productId: 3}
199
+ *
200
+ * createThroughConstraintFromTarget(resolvedMetadata, [3,4]);
201
+ *
202
+ * >>> {productId: {inq:[3,4]}}
203
+ */
204
+ export function createThroughConstraintFromTarget<
205
+ Through extends Entity,
206
+ TargetID
207
+ >(
208
+ relationMeta: HasManyThroughResolvedDefinition,
209
+ fkValues: TargetID[],
210
+ ): DataObject<Through> {
211
+ if (fkValues === undefined || fkValues.length === 0) {
212
+ throw new Error('"fkValue" must be provided');
213
+ }
214
+ const targetFkName = relationMeta.through.keyTo;
215
+
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ const constraint: any =
218
+ fkValues.length === 1
219
+ ? {[targetFkName]: fkValues[0]}
220
+ : {[targetFkName]: {inq: fkValues}};
221
+
222
+ return constraint as DataObject<Through>;
223
+ }
107
224
 
108
225
  /**
109
226
  * Resolves given hasMany metadata if target is specified to be a resolver.
@@ -142,7 +259,7 @@ export function resolveHasManyThroughMetadata(
142
259
  relationMeta.keyTo &&
143
260
  targetModelProperties[relationMeta.keyTo]
144
261
  ) {
145
- // The explict cast is needed because of a limitation of type inference
262
+ // The explicit cast is needed because of a limitation of type inference
146
263
  return relationMeta as HasManyThroughResolvedDefinition;
147
264
  }
148
265