@loopback/repository 1.15.4 → 1.18.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 (95) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/common-types.d.ts +10 -1
  3. package/dist/common-types.js +1 -0
  4. package/dist/common-types.js.map +1 -1
  5. package/dist/decorators/model.decorator.js +7 -6
  6. package/dist/decorators/model.decorator.js.map +1 -1
  7. package/dist/decorators/repository.decorator.js +5 -2
  8. package/dist/decorators/repository.decorator.js.map +1 -1
  9. package/dist/define-model-class.d.ts +55 -0
  10. package/dist/define-model-class.js +60 -0
  11. package/dist/define-model-class.js.map +1 -0
  12. package/dist/errors/invalid-relation.error.js +2 -1
  13. package/dist/errors/invalid-relation.error.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/mixins/repository.mixin.js +11 -4
  18. package/dist/mixins/repository.mixin.js.map +1 -1
  19. package/dist/model.js +2 -2
  20. package/dist/model.js.map +1 -1
  21. package/dist/query.js +8 -5
  22. package/dist/query.js.map +1 -1
  23. package/dist/relations/belongs-to/belongs-to-accessor.js +12 -3
  24. package/dist/relations/belongs-to/belongs-to-accessor.js.map +1 -1
  25. package/dist/relations/belongs-to/belongs-to.helpers.js +5 -2
  26. package/dist/relations/belongs-to/belongs-to.helpers.js.map +1 -1
  27. package/dist/relations/has-many/has-many-repository.factory.js +5 -2
  28. package/dist/relations/has-many/has-many-repository.factory.js.map +1 -1
  29. package/dist/relations/has-many/has-many-through.repository.d.ts +66 -0
  30. package/dist/relations/has-many/has-many-through.repository.js +7 -0
  31. package/dist/relations/has-many/has-many-through.repository.js.map +1 -0
  32. package/dist/relations/has-many/has-many.helpers.js +18 -4
  33. package/dist/relations/has-many/has-many.helpers.js.map +1 -1
  34. package/dist/relations/has-many/has-many.inclusion-resolver.js +5 -2
  35. package/dist/relations/has-many/has-many.inclusion-resolver.js.map +1 -1
  36. package/dist/relations/has-one/has-one-repository.factory.js +5 -2
  37. package/dist/relations/has-one/has-one-repository.factory.js.map +1 -1
  38. package/dist/relations/has-one/has-one.helpers.js +14 -3
  39. package/dist/relations/has-one/has-one.helpers.js.map +1 -1
  40. package/dist/relations/relation.decorator.js +2 -1
  41. package/dist/relations/relation.decorator.js.map +1 -1
  42. package/dist/relations/relation.helpers.d.ts +1 -1
  43. package/dist/relations/relation.helpers.js +18 -13
  44. package/dist/relations/relation.helpers.js.map +1 -1
  45. package/dist/relations/relation.types.d.ts +50 -3
  46. package/dist/relations/relation.types.js.map +1 -1
  47. package/dist/repositories/kv.repository.bridge.d.ts +3 -3
  48. package/dist/repositories/kv.repository.bridge.js +1 -1
  49. package/dist/repositories/kv.repository.bridge.js.map +1 -1
  50. package/dist/repositories/legacy-juggler-bridge.d.ts +20 -1
  51. package/dist/repositories/legacy-juggler-bridge.js +82 -20
  52. package/dist/repositories/legacy-juggler-bridge.js.map +1 -1
  53. package/dist/types/array.js +5 -2
  54. package/dist/types/array.js.map +1 -1
  55. package/dist/types/buffer.d.ts +1 -1
  56. package/dist/types/buffer.js +8 -4
  57. package/dist/types/buffer.js.map +1 -1
  58. package/dist/types/date.js +5 -2
  59. package/dist/types/date.js.map +1 -1
  60. package/dist/types/number.js +5 -2
  61. package/dist/types/number.js.map +1 -1
  62. package/dist/types/object.d.ts +1 -1
  63. package/dist/types/object.js +5 -2
  64. package/dist/types/object.js.map +1 -1
  65. package/dist/types/union.js +5 -2
  66. package/dist/types/union.js.map +1 -1
  67. package/package.json +12 -12
  68. package/src/common-types.ts +15 -1
  69. package/src/decorators/model.decorator.ts +5 -5
  70. package/src/decorators/repository.decorator.ts +1 -1
  71. package/src/define-model-class.ts +88 -0
  72. package/src/errors/invalid-relation.error.ts +1 -1
  73. package/src/index.ts +1 -0
  74. package/src/mixins/repository.mixin.ts +7 -3
  75. package/src/model.ts +2 -2
  76. package/src/query.ts +3 -3
  77. package/src/relations/belongs-to/belongs-to-accessor.ts +8 -2
  78. package/src/relations/belongs-to/belongs-to.helpers.ts +1 -1
  79. package/src/relations/has-many/has-many-repository.factory.ts +1 -1
  80. package/src/relations/has-many/has-many-through.repository.ts +100 -0
  81. package/src/relations/has-many/has-many.helpers.ts +15 -4
  82. package/src/relations/has-many/has-many.inclusion-resolver.ts +1 -1
  83. package/src/relations/has-one/has-one-repository.factory.ts +1 -1
  84. package/src/relations/has-one/has-one.helpers.ts +11 -2
  85. package/src/relations/relation.decorator.ts +1 -1
  86. package/src/relations/relation.helpers.ts +13 -12
  87. package/src/relations/relation.types.ts +56 -2
  88. package/src/repositories/kv.repository.bridge.ts +5 -8
  89. package/src/repositories/legacy-juggler-bridge.ts +81 -13
  90. package/src/types/array.ts +1 -1
  91. package/src/types/buffer.ts +4 -4
  92. package/src/types/date.ts +1 -1
  93. package/src/types/number.ts +1 -1
  94. package/src/types/object.ts +2 -2
  95. package/src/types/union.ts +1 -1
@@ -0,0 +1,88 @@
1
+ // Copyright IBM Corp. 2019. 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
+
6
+ import * as assert from 'assert';
7
+ import {DataObject, PrototypeOf} from './common-types';
8
+ import {Model, ModelDefinition} from './model';
9
+
10
+ /**
11
+ * Create (define) a new model class with the given name and definition.
12
+ *
13
+ * Example usage:
14
+ *
15
+ * ```ts
16
+ * const Product = defineModelClass(Entity, new ModelDefinition('Product'));
17
+ * ```
18
+ *
19
+ * To enable type safety, you should describe properties of your model:
20
+ *
21
+ * ```ts
22
+ * const Product = defineModelClass<
23
+ * typeof Entity,
24
+ * {id: number, name: string}
25
+ * >(Entity, new ModelDefinition('Product'));
26
+ * ```
27
+ *
28
+ * If your model allows arbitrary (free-form) properties, then add `AnyObject`
29
+ * to the type describing model properties.
30
+ *
31
+ * ```ts
32
+ * const Product = defineModelClass<
33
+ * typeof Entity,
34
+ * AnyObject & {id: number},
35
+ * >(Entity, new ModelDefinition('Product'));
36
+ * ```
37
+ *
38
+ * @param base The base model to extend, typically Model or Entity.
39
+ * You can also use your own base class, e.g. `User`.
40
+ * @param definition Definition of the model to create.
41
+ * @template BaseCtor Constructor type of the base class,
42
+ * e.g `typeof Model` or `typeof Entity`
43
+ * @template Props Interface describing model properties,
44
+ * e.g. `{title: string}` or `AnyObject & {id: number}`.
45
+ */
46
+ export function defineModelClass<
47
+ BaseCtor extends typeof Model,
48
+ Props extends object = {}
49
+ >(
50
+ base: BaseCtor /* Model or Entity */,
51
+ definition: ModelDefinition,
52
+ ): DynamicModelCtor<BaseCtor, Props> {
53
+ const modelName = definition.name;
54
+ const defineNamedModelClass = new Function(
55
+ base.name,
56
+ `return class ${modelName} extends ${base.name} {}`,
57
+ );
58
+ const modelClass = defineNamedModelClass(base) as DynamicModelCtor<
59
+ BaseCtor,
60
+ Props
61
+ >;
62
+ assert.equal(modelClass.name, modelName);
63
+ modelClass.definition = definition;
64
+ return modelClass;
65
+ }
66
+
67
+ /**
68
+ * A type describing a model class created via `defineModelClass`.
69
+ *
70
+ * Assuming template arguments `BaseCtor` and `Props`, this type describes
71
+ * a class constructor with the following properties:
72
+ * - a constructor function accepting `DataObject<Props>` as the only argument,
73
+ * this argument is optional
74
+ * - all static fields (properties, methods) from `BaseCtor` are inherited and
75
+ * available as static fields on the dynamic class
76
+ * - all prototype fields from `BaseCtor` prototype are inherited and available
77
+ * as prototype fields on the dynamic class
78
+ */
79
+ export type DynamicModelCtor<
80
+ BaseCtor extends typeof Model,
81
+ Props extends object
82
+ > = BaseCtor & {
83
+ /** Model constructor accepting partial model data. */
84
+ new (data?: DataObject<PrototypeOf<BaseCtor> & Props>): PrototypeOf<
85
+ BaseCtor
86
+ > &
87
+ Props;
88
+ };
@@ -18,7 +18,7 @@ export class InvalidRelationError<Props extends object = {}> extends Error {
18
18
  extraProperties?: Props,
19
19
  ) {
20
20
  const {name, type, source} = relationMeta;
21
- const model = (source && source.modelName) || '<Unknown Model>';
21
+ const model = source?.modelName || '<Unknown Model>';
22
22
  const message = `Invalid ${type} definition for ${model}#${name}: ${reason}`;
23
23
  super(message);
24
24
 
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './common-types';
7
7
  export * from './connectors';
8
8
  export * from './datasource';
9
9
  export * from './decorators';
10
+ export * from './define-model-class';
10
11
  export * from './errors';
11
12
  export * from './mixins';
12
13
  export * from './model';
@@ -5,7 +5,7 @@
5
5
 
6
6
  import {Binding, BindingScope, createBindingFromClass} from '@loopback/context';
7
7
  import {Application} from '@loopback/core';
8
- import * as debugFactory from 'debug';
8
+ import debugFactory from 'debug';
9
9
  import {Class} from '../common-types';
10
10
  import {SchemaMigrationOptions} from '../datasource';
11
11
  import {juggler, Repository} from '../repositories';
@@ -110,13 +110,17 @@ export function RepositoryMixin<T extends Class<any>>(superClass: T) {
110
110
  ): Binding<D> {
111
111
  // We have an instance of
112
112
  if (dataSource instanceof juggler.DataSource) {
113
- const key = `datasources.${name || dataSource.name}`;
113
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
114
+ name = name || dataSource.name;
115
+ const key = `datasources.${name}`;
114
116
  return this.bind(key)
115
117
  .to(dataSource)
116
118
  .tag('datasource');
117
119
  } else if (typeof dataSource === 'function') {
120
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
121
+ name = name || dataSource.dataSourceName;
118
122
  const binding = createBindingFromClass(dataSource, {
119
- name: name || dataSource.dataSourceName,
123
+ name,
120
124
  namespace: 'datasources',
121
125
  type: 'datasource',
122
126
  defaultScope: BindingScope.SINGLETON,
package/src/model.ts CHANGED
@@ -90,8 +90,8 @@ export class ModelDefinition {
90
90
  }
91
91
  }
92
92
 
93
- this.settings = settings || new Map();
94
- this.relations = relations || {};
93
+ this.settings = settings ?? new Map();
94
+ this.relations = relations ?? {};
95
95
  }
96
96
 
97
97
  /**
package/src/query.ts CHANGED
@@ -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 * as assert from 'assert';
6
+ import assert from 'assert';
7
7
  import {AnyObject} from './common-types';
8
8
 
9
9
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -258,7 +258,7 @@ export class WhereBuilder<MT extends object = AnyObject> {
258
258
  where: Where<MT>;
259
259
 
260
260
  constructor(w?: Where<MT>) {
261
- this.where = w || {};
261
+ this.where = w ?? {};
262
262
  }
263
263
 
264
264
  private add(w: Where<MT>): this {
@@ -464,7 +464,7 @@ export class FilterBuilder<MT extends object = AnyObject> {
464
464
  filter: Filter<MT>;
465
465
 
466
466
  constructor(f?: Filter<MT>) {
467
- this.filter = f || {};
467
+ this.filter = f ?? {};
468
468
  }
469
469
 
470
470
  /**
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {DataObject} from '../../common-types';
8
8
  import {Entity} from '../../model';
9
9
  import {EntityCrudRepository} from '../../repositories/repository';
@@ -13,8 +13,8 @@ import {
13
13
  InclusionResolver,
14
14
  } from '../relation.types';
15
15
  import {resolveBelongsToMetadata} from './belongs-to.helpers';
16
- import {DefaultBelongsToRepository} from './belongs-to.repository';
17
16
  import {createBelongsToInclusionResolver} from './belongs-to.inclusion-resolver';
17
+ import {DefaultBelongsToRepository} from './belongs-to.repository';
18
18
 
19
19
  const debug = debugFactory('loopback:repository:belongs-to-accessor');
20
20
 
@@ -53,6 +53,12 @@ export function createBelongsToAccessor<
53
53
  const primaryKey = meta.keyTo;
54
54
  const sourceModel = await sourceRepository.findById(sourceId);
55
55
  const foreignKeyValue = sourceModel[foreignKey as keyof Source];
56
+ // workaround to check referential integrity.
57
+ // should be removed once the memory connector ref integrity is done
58
+ // GH issue: https://github.com/strongloop/loopback-next/issues/2333
59
+ if (!foreignKeyValue) {
60
+ return (undefined as unknown) as Target;
61
+ }
56
62
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
63
  const constraint: any = {[primaryKey]: foreignKeyValue};
58
64
  const constrainedRepo = new DefaultBelongsToRepository(
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {InvalidRelationError} from '../../errors';
8
8
  import {isTypeResolver} from '../../type-resolver';
9
9
  import {BelongsToDefinition, RelationType} from '../relation.types';
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {DataObject} from '../../common-types';
8
8
  import {Entity} from '../../model';
9
9
  import {EntityCrudRepository} from '../../repositories/repository';
@@ -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
+
6
+ import {Count, DataObject, Entity, Filter, Options, Where} from '../..';
7
+
8
+ /**
9
+ * CRUD operations for a target repository of a HasManyThrough relation
10
+ *
11
+ * EXPERIMENTAL: This interface is not stable and may change in the near future.
12
+ * Backwards-incompatible changes may be introduced in semver-minor versions.
13
+ */
14
+ export interface HasManyThroughRepository<
15
+ Target extends Entity,
16
+ TargetID,
17
+ Through extends Entity
18
+ > {
19
+ /**
20
+ * Create a target model instance
21
+ * @param targetModelData - The target model data
22
+ * @param options - Options for the operation
23
+ * @returns A promise which resolves to the newly created target model instance
24
+ */
25
+ create(
26
+ targetModelData: DataObject<Target>,
27
+ options?: Options & {
28
+ throughData?: DataObject<Through>;
29
+ throughOptions?: Options;
30
+ },
31
+ ): Promise<Target>;
32
+
33
+ /**
34
+ * Find target model instance(s)
35
+ * @param filter - A filter object for where, order, limit, etc.
36
+ * @param options - Options for the operation
37
+ * @returns A promise which resolves with the found target instance(s)
38
+ */
39
+ find(
40
+ filter?: Filter<Target>,
41
+ options?: Options & {
42
+ throughOptions?: Options;
43
+ },
44
+ ): Promise<Target[]>;
45
+
46
+ /**
47
+ * Delete multiple target model instances
48
+ * @param where - Instances within the where scope are deleted
49
+ * @param options
50
+ * @returns A promise which resolves the deleted target model instances
51
+ */
52
+ delete(
53
+ where?: Where<Target>,
54
+ options?: Options & {
55
+ throughOptions?: Options;
56
+ },
57
+ ): Promise<Count>;
58
+
59
+ /**
60
+ * Patch multiple target model instances
61
+ * @param dataObject - The fields and their new values to patch
62
+ * @param where - Instances within the where scope are patched
63
+ * @param options
64
+ * @returns A promise which resolves the patched target model instances
65
+ */
66
+ patch(
67
+ dataObject: DataObject<Target>,
68
+ where?: Where<Target>,
69
+ options?: Options & {
70
+ throughOptions?: Options;
71
+ },
72
+ ): Promise<Count>;
73
+
74
+ /**
75
+ * Creates a new many-to-many association to an existing target model instance
76
+ * @param targetModelId - The target model ID to link
77
+ * @param options
78
+ * @returns A promise which resolves to the linked target model instance
79
+ */
80
+ link(
81
+ targetModelId: TargetID,
82
+ options?: Options & {
83
+ throughData?: DataObject<Through>;
84
+ throughOptions?: Options;
85
+ },
86
+ ): Promise<Target>;
87
+
88
+ /**
89
+ * Removes an association to an existing target model instance
90
+ * @param targetModelId - The target model to unlink
91
+ * @param options
92
+ * @returns A promise which resolves to null
93
+ */
94
+ unlink(
95
+ targetModelId: TargetID,
96
+ options?: Options & {
97
+ throughOptions?: Options;
98
+ },
99
+ ): Promise<void>;
100
+ }
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {camelCase} from 'lodash';
8
8
  import {InvalidRelationError} from '../../errors';
9
9
  import {isTypeResolver} from '../../type-resolver';
@@ -50,8 +50,16 @@ export function resolveHasManyMetadata(
50
50
  throw new InvalidRelationError(reason, relationMeta);
51
51
  }
52
52
 
53
- const keyFrom = sourceModel.getIdProperties()[0];
54
-
53
+ // keyFrom defaults to id property
54
+ let keyFrom;
55
+ if (
56
+ relationMeta.keyFrom &&
57
+ relationMeta.source.definition.properties[relationMeta.keyFrom]
58
+ ) {
59
+ keyFrom = relationMeta.keyFrom;
60
+ } else {
61
+ keyFrom = sourceModel.getIdProperties()[0];
62
+ }
55
63
  // Make sure that if it already keys to the foreign key property,
56
64
  // the key exists in the target model
57
65
  if (relationMeta.keyTo && targetModelProperties[relationMeta.keyTo]) {
@@ -72,5 +80,8 @@ export function resolveHasManyMetadata(
72
80
  throw new InvalidRelationError(reason, relationMeta);
73
81
  }
74
82
 
75
- return Object.assign(relationMeta, {keyFrom, keyTo: defaultFkName});
83
+ return Object.assign(relationMeta, {
84
+ keyFrom,
85
+ keyTo: defaultFkName,
86
+ } as HasManyResolvedDefinition);
76
87
  }
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {AnyObject, Options} from '../../common-types';
8
8
  import {Entity} from '../../model';
9
9
  import {Filter, Inclusion} from '../../query';
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {DataObject} from '../../common-types';
8
8
  import {Entity} from '../../model';
9
9
  import {EntityCrudRepository} from '../../repositories/repository';
@@ -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 * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {camelCase} from 'lodash';
8
8
  import {InvalidRelationError} from '../../errors';
9
9
  import {isTypeResolver} from '../../type-resolver';
@@ -50,7 +50,16 @@ export function resolveHasOneMetadata(
50
50
  throw new InvalidRelationError(reason, relationMeta);
51
51
  }
52
52
 
53
- const keyFrom = sourceModel.getIdProperties()[0];
53
+ // keyFrom defaults to id property
54
+ let keyFrom;
55
+ if (
56
+ relationMeta.keyFrom &&
57
+ relationMeta.source.definition.properties[relationMeta.keyFrom]
58
+ ) {
59
+ keyFrom = relationMeta.keyFrom;
60
+ } else {
61
+ keyFrom = sourceModel.getIdProperties()[0];
62
+ }
54
63
 
55
64
  // Make sure that if it already keys to the foreign key property,
56
65
  // the key exists in the target model
@@ -33,7 +33,7 @@ export function getModelRelations(
33
33
  ): RelationDefinitionMap {
34
34
  // Build model definitions if `@model` is missing
35
35
  const modelDef = buildModelDefinition(modelCtor);
36
- return (modelDef && modelDef.relations) || {};
36
+ return modelDef?.relations || {};
37
37
  }
38
38
 
39
39
  //
@@ -3,9 +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
- import * as assert from 'assert';
7
- import * as debugFactory from 'debug';
8
- import * as _ from 'lodash';
6
+ import assert from 'assert';
7
+ import debugFactory from 'debug';
8
+ import _ from 'lodash';
9
9
  import {
10
10
  AnyObject,
11
11
  Entity,
@@ -14,13 +14,14 @@ import {
14
14
  Inclusion,
15
15
  Options,
16
16
  Where,
17
+ FilterBuilder,
17
18
  } from '..';
18
19
  const debug = debugFactory('loopback:repository:relation-helpers');
19
20
 
20
21
  /**
21
22
  * Finds model instances that contain any of the provided foreign key values.
22
23
  *
23
- * @param targetRepository - The target repository where the model instances are found
24
+ * @param targetRepository - The target repository where the related model instances are found
24
25
  * @param fkName - Name of the foreign key
25
26
  * @param fkValues - One value or array of values of the foreign key to be included
26
27
  * @param scope - Additional scope constraints (not currently supported)
@@ -37,12 +38,6 @@ export async function findByForeignKeys<
37
38
  scope?: Filter<Target>,
38
39
  options?: Options,
39
40
  ): Promise<(Target & TargetRelations)[]> {
40
- // throw error if scope is defined and non-empty
41
- // see https://github.com/strongloop/loopback-next/issues/3453
42
- if (scope && !_.isEmpty(scope)) {
43
- throw new Error('scope is not supported');
44
- }
45
-
46
41
  let value;
47
42
 
48
43
  if (Array.isArray(fkValues)) {
@@ -60,9 +55,15 @@ export async function findByForeignKeys<
60
55
  }
61
56
 
62
57
  const where = ({[fkName]: value} as unknown) as Where<Target>;
63
- const targetFilter = {where};
64
58
 
65
- return targetRepository.find(targetFilter, options);
59
+ if (scope && !_.isEmpty(scope)) {
60
+ // combine where clause to scope filter
61
+ scope = new FilterBuilder(scope).impose({where}).filter;
62
+ } else {
63
+ scope = {where} as Filter<Target>;
64
+ }
65
+
66
+ return targetRepository.find(scope, options);
66
67
  }
67
68
 
68
69
  export type StringKeyOf<T> = Extract<keyof T, string>;
@@ -59,13 +59,62 @@ export interface HasManyDefinition extends RelationDefinitionBase {
59
59
  targetsMany: true;
60
60
 
61
61
  /**
62
- * The foreign key used by the target model.
62
+ * keyTo: The foreign key used by the target model for this relation.
63
+ * keyFrom: The source key used by the source model for this relation.
63
64
  *
64
65
  * E.g. when a Customer has many Order instances, then keyTo is "customerId".
65
66
  * Note that "customerId" is the default FK assumed by the framework, users
66
67
  * can provide a custom FK name by setting "keyTo".
68
+ * And Customer.id is keyFrom. keyFrom defaults to the id property of a model.
69
+ * Users can provide a custom source key name by setting "keyTo".
70
+ *
67
71
  */
68
72
  keyTo?: string;
73
+ keyFrom?: string;
74
+ }
75
+
76
+ /**
77
+ * A `hasManyThrough` relation defines a many-to-many connection with another model.
78
+ * This relation indicates that the declaring model can be matched with zero or more
79
+ * instances of another model by proceeding through a third model.
80
+ *
81
+ * Warning: The hasManyThrough interface is experimental and is subject to change.
82
+ * If backwards-incompatible changes are made, a new major version may not be
83
+ * released.
84
+ */
85
+ export interface HasManyThroughDefinition extends RelationDefinitionBase {
86
+ type: RelationType.hasMany;
87
+ targetsMany: true;
88
+
89
+ /**
90
+ * The foreign key in the source model, e.g. Customer#id.
91
+ */
92
+ keyFrom: string;
93
+
94
+ /**
95
+ * The primary key of the target model, e.g Seller#id.
96
+ */
97
+ keyTo: string;
98
+
99
+ through: {
100
+ /**
101
+ * The through model of this relation.
102
+ *
103
+ * E.g. when a Customer has many Order instances and a Seller has many Order instances,
104
+ * then Order is through.
105
+ */
106
+ model: TypeResolver<Entity, typeof Entity>;
107
+
108
+ /**
109
+ * The foreign key of the source model defined in the through model, e.g. Order#customerId
110
+ */
111
+ keyFrom: string;
112
+
113
+ /**
114
+ * The foreign key of the target model defined in the through model, e.g. Order#sellerId
115
+ */
116
+ keyTo: string;
117
+ };
69
118
  }
70
119
 
71
120
  export interface BelongsToDefinition extends RelationDefinitionBase {
@@ -88,13 +137,17 @@ export interface HasOneDefinition extends RelationDefinitionBase {
88
137
  targetsMany: false;
89
138
 
90
139
  /**
91
- * The foreign key used by the target model.
140
+ * keyTo: The foreign key used by the target model for this relation.
141
+ * keyFrom: The source key used by the source model for this relation.
92
142
  *
93
143
  * E.g. when a Customer has one Address instance, then keyTo is "customerId".
94
144
  * Note that "customerId" is the default FK assumed by the framework, users
95
145
  * can provide a custom FK name by setting "keyTo".
146
+ * And Customer.id is keyFrom. keyFrom defaults to the id property of a model.
147
+ * Users can provide a custom source key name by setting "keyTo".
96
148
  */
97
149
  keyTo?: string;
150
+ keyFrom?: string;
98
151
  }
99
152
 
100
153
  /**
@@ -102,6 +155,7 @@ export interface HasOneDefinition extends RelationDefinitionBase {
102
155
  */
103
156
  export type RelationMetadata =
104
157
  | HasManyDefinition
158
+ | HasManyThroughDefinition
105
159
  | BelongsToDefinition
106
160
  | HasOneDefinition
107
161
  // TODO(bajtos) add other relation types and remove RelationDefinitionBase once
@@ -3,14 +3,11 @@
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 * as legacy from 'loopback-datasource-juggler';
7
-
8
- import {Options, DataObject} from '../common-types';
6
+ import legacy from 'loopback-datasource-juggler';
7
+ import {DataObject, Options} from '../common-types';
9
8
  import {Entity} from '../model';
10
-
11
- import {KeyValueRepository, KeyValueFilter} from './kv.repository';
12
-
13
- import {juggler, ensurePromise} from './legacy-juggler-bridge';
9
+ import {KeyValueFilter, KeyValueRepository} from './kv.repository';
10
+ import {ensurePromise, juggler} from './legacy-juggler-bridge';
14
11
 
15
12
  /**
16
13
  * Polyfill for Symbol.asyncIterator
@@ -103,7 +100,7 @@ class AsyncKeyIteratorImpl implements AsyncIterator<string> {
103
100
  next() {
104
101
  const key = ensurePromise<string | undefined>(this.keys.next());
105
102
  return key.then(k => {
106
- return {done: k === undefined, value: k || ''};
103
+ return {done: k === undefined, value: k ?? ''};
107
104
  });
108
105
  }
109
106
  }