diff --git a/CODEOWNERS b/CODEOWNERS
index 483b38b6e33f..2d4bef1249f5 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -202,6 +202,7 @@
/packages/repository/src/__tests__/integration/repositories/relation.factory.integration.ts @agnes512 @hacksparrow @nabdelgadir
/packages/cli/generators/relation @agnes512 @hacksparrow @nabdelgadir
/examples/todo-list @agnes512 @hacksparrow @nabdelgadir
+/examples/references-many @agnes512 @hacksparrow @nabdelgadir
/docs/site/tutorials/todo-list/ @agnes512 @hacksparrow @nabdelgadir
#
diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md
index b4518e71f7fa..00aaf2ab8f91 100644
--- a/docs/site/MONOREPO.md
+++ b/docs/site/MONOREPO.md
@@ -40,6 +40,7 @@ one in the monorepo: `npm run update-monorepo-file`
| [examples/todo-jwt](https://github.com/loopbackio/loopback-next/tree/master/examples/todo-jwt) | @loopback/example-todo-jwt | A modified Todo tutorial how to build an application with JWT authentication and LoopBack 4 |
| [examples/todo-list](https://github.com/loopbackio/loopback-next/tree/master/examples/todo-list) | @loopback/example-todo-list | Continuation of the todo example using relations in LoopBack 4. |
| [examples/validation-app](https://github.com/loopbackio/loopback-next/tree/master/examples/validation-app) | @loopback/example-validation-app | An example demonstrating how to add validation in a LoopBack 4 application |
+| [examples/references-many](https://github.com/loopbackio/loopback-next/tree/master/examples/references-many) | @loopback/example-references-many | An example of references many relation in a LoopBack 4 |
| [examples/webpack](https://github.com/loopbackio/loopback-next/tree/master/examples/webpack) | @loopback/example-webpack | An example to bundle @loopback/core using webpack |
| [extensions/apiconnect](https://github.com/loopbackio/loopback-next/tree/master/extensions/apiconnect) | @loopback/apiconnect | An extension for IBM API Connect |
| [extensions/authentication-jwt](https://github.com/loopbackio/loopback-next/tree/master/extensions/authentication-jwt) | @loopback/authentication-jwt | Extension for the prototype of JWT authentication |
diff --git a/docs/site/ReferencesMany-relation.md b/docs/site/ReferencesMany-relation.md
new file mode 100644
index 000000000000..fcee4d0b80f0
--- /dev/null
+++ b/docs/site/ReferencesMany-relation.md
@@ -0,0 +1,379 @@
+---
+lang: en
+title: 'referencesMany Relation'
+keywords: LoopBack 4.0, LoopBack 4, Node.js, TypeScript, OpenAPI, Model Relation
+sidebar: lb4_sidebar
+permalink: /doc/en/lb4/ReferencesMany-relation.html
+---
+
+## Overview
+
+{% include important.html content="Please read [Relations](Relations.md) first." %}
+
+LoopBack relations enable you to create connections between models and provide
+navigation APIs to deal with a graph of model instances. In addition to the
+traditional ones, LoopBack supports `referencesMany` relation that embeds an
+array of foreign keys to reference other objects. For example:
+
+```json
+{
+ "id": 1,
+ "name": "John Smith",
+ "accountIds": ["saving-01", "checking-01"]
+}
+```
+
+To add a `referencesMany` relation to your LoopBack application, you need to
+perform the following steps:
+
+1. Add a property to your source model to define the array of foreign keys.
+2. Modify the source model repository class to provide an accessor function for
+ obtaining the target model instances.
+3. Call the accessor function in your controller methods.
+
+## Defining a referencesMany relation
+
+This section describes how to define a `referencesMany` relation at the model
+level using the `@referencesMany` decorator to define the array of foreign keys.
+
+LB4 also provides an CLI tool `lb4 relation` to generate `referencesMany`
+relation for you. Before you check out the
+[`Relation Generator`](https://loopback.io/doc/en/lb4/Relation-generator.html)
+page, read on to learn how you can define relations to meet your requirements.
+
+### Relation Metadata
+
+LB4 uses three `keyFrom`, `keyTo` and `name` fields in the `referencesMany`
+relation metadata to configure relations. The relation metadata has its own
+default values for these three fields:
+
+
+
+
+ Field Name
+ Description
+ Default Value
+ Example
+
+
+
+
+ keyFrom
+ the array property of foreign keys
+ the target model name appended with `Ids` in camel case
+ Customer.accountIds
+
+
+ keyTo
+ the source key of the target model
+ the primary key in the target model
+ Account.id
+
+
+ name
+ the name of the relation
+ the plural name of target model
+ accounts
+
+
+
+
+
+We recommend to use default values. If you'd like to customize the name of
+foreign keys or the relation name, you'll need to specify some fields through
+the relation decorator.
+
+The standard naming convention for the property of foreign keys in the source
+model is `target name` + `Ids` (for example, Customer.accountIds).
+
+{% include code-caption.html content="/src/models/customer.model.ts" %}
+
+```ts
+import {referencesMany, Entity, model, property} from '@loopback/repository';
+import {Account} from './account.model';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ })
+ name: string;
+
+ @referencesMany(() => Account)
+ accountIds: number[]; // relation name will default to `accounts`
+
+ constructor(data: Partial) {
+ super(data);
+ }
+}
+
+export interface CustomerRelations {
+ // describe navigational properties here
+}
+
+export type CustomerWithRelations = Customer & CustomerRelations;
+```
+
+If the property name of foreign keys in the source model has to be customized
+(`account_ids` instead of `accountIds` for example), the relation name has to be
+explicitly specified in the `name` attribute of the relation definition.
+Otherwise the _default relation name_ generates by LB4 (`account_ids` in this
+case) will be the same as the customized name of foreign keys, which is invalid.
+
+{% include warning.html content="Make sure that you have different names for the property name of foreign keys and the relation name in ReferencesMany relations."%}
+
+```ts
+// import statements
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ })
+ name: string;
+
+ @referencesMany(
+ () => Account,
+ {name: 'accounts'}, // specify the relation name if fk is customized
+ )
+ account_ids: number[]; // customized foreign keys
+
+ // other properties, constructor, etc.
+}
+```
+
+If you need to use _different names for models and database columns_, to use
+`account_ids` as db column name other than `accountIds` for example, passing the
+column name in the third argument to the `referencesMany` decorator would allow
+you to do so:
+
+```ts
+class Customer extends Entity {
+ // constructor, properties, etc.
+ @referencesMany(() => Account, {keyFrom: 'accountIds'}, {name: 'account_ids'})
+ accountIds: number[];
+}
+```
+
+If you need to use another attribute other than the id property to be the source
+key of the target model (joining two tables on non-primary attribute), the
+`keyTo` attribute in the relation definition has to be stated explicitly.
+
+```ts
+class Customer extends Entity {
+ // constructor, properties, etc.
+ @referencesMany(() => Account, {keyTo: 'customized_target_property'})
+ accountIds: number[];
+}
+
+export interface CustomerRelations {
+ accounts?: AccountWithRelations[];
+}
+```
+
+{% include important.html content="LB4 doesn't support composite keys for now. e.g joining two tables with more than one source key. Related GitHub issue: [Composite primary/foreign keys](https://github.com/loopbackio/loopback-next/issues/1830)" %}
+
+## Configuring a referencesMany relation
+
+The configuration and resolution of a `referencesMany` relation takes place at
+the repository level. Once `referencesMany` relation is defined on the source
+model, then there are a couple of steps involved to configure it and use it. On
+the source repository, the following are required:
+
+- In the constructor of your source repository class, use
+ [Dependency Injection](Dependency-injection.md) to receive a getter function
+ for obtaining an instance of the target repository.
+- Declare a property with the factory function type
+ `ReferencesManyAccessor` on the
+ source repository class.
+- call the `createReferencesManyAccessorFor` function in the constructor of the
+ source repository class with the relation name (decorated relation property on
+ the source model) and target repository instance and assign it the property
+ mentioned above.
+
+The following code snippet shows how it would look like:
+
+{% include code-caption.html
+content="/src/repositories/customer.repository.ts" %}
+
+```ts
+import {Getter, inject} from '@loopback/core';
+import {
+ ReferencesManyAccessor,
+ DefaultCrudRepository,
+ juggler,
+ repository,
+} from '@loopback/repository';
+import {Account, Customer, CustomerRelations} from '../models';
+import {AccountRepository} from '../repositories';
+
+export class CustomerRepository extends DefaultCrudRepository<
+ Customer,
+ typeof Customer.prototype.id,
+ CustomerRelations
+> {
+ public readonly accounts: ReferencesManyAccessor<
+ Account,
+ typeof Customer.prototype.id
+ >;
+
+ constructor(
+ @inject('datasources.db') protected db: juggler.DataSource,
+ @repository.getter('AccountRepository')
+ accountRepositoryGetter: Getter,
+ ) {
+ super(Customer, db);
+ this.accounts = this.createReferencesManyAccessorFor(
+ 'accounts',
+ accountRepositoryGetter,
+ );
+ }
+}
+```
+
+`ReferencesManyAccessor` is a function accepting the primary key (id) of a
+source model instance (e.g. `customer.id`) and returning back the related target
+model instances (e.g. a `Account[]`). See also
+[API Docs](https://loopback.io/doc/en/lb4/apidocs.repository.belongstoaccessor.html)
+
+{% include note.html content="Notice that `CustomerRepository.create()` expects an `Customer` model only, navigational properties are not expected to be included in the target data. For instance, the following request will be rejected:
+`customerRepository.create({`
+` id: 1,`
+` name: 'John'`
+` accountIds: [1]`
+` accounts:[{id: 1, balance: 0}] // rejected`
+`})`" %}
+
+## Querying related models
+
+Different from LB3, LB4 creates a different inclusion resolver for each relation
+type to query related models. Each **relation** has its own inclusion resolver
+`inclusionResolver`. And each **repository** has a built-in property
+`inclusionResolvers` as a registry for its inclusionResolvers.
+
+A `referencesMany` relation has an `inclusionResolver` function as a property.
+It fetches target models for the given list of source model instances.
+
+### Use the relation between `Customer` and `Account` we show above.
+
+After setting up the relation in the repository class, the inclusion resolver
+allows users to retrieve all customers along with their related accounts through
+the following code at the repository level:
+
+```ts
+customerRepo.find({include: ['accounts']});
+```
+
+or use APIs with controllers:
+
+```
+GET http://localhost:3000/customers?filter[include][]=accounts
+```
+
+### Enable/disable the inclusion resolvers
+
+- Base repository classes have a public property `inclusionResolvers`, which
+ maintains a map containing inclusion resolvers for each relation.
+- The `inclusionResolver` of a certain relation is built when the source
+ repository class calls the `createReferencesManyAccessorFor` function in the
+ constructor with the relation name.
+- Call `registerInclusionResolver` to add the resolver of that relation to the
+ `inclusionResolvers` map. (As we realized in LB3, not all relations are
+ allowed to be traversed. Users can decide to which resolvers can be added.)
+ The first parameter is the name of the relation.
+
+The following code snippet shows how to register the inclusion resolver for the
+referencesMany relation 'accounts':
+
+```ts
+export class CustomerRepository extends DefaultCrudRepository {
+ accounts: ReferencesManyAccessor;
+
+ constructor(
+ dataSource: juggler.DataSource,
+ accountRepositoryGetter: Getter,
+ ) {
+ super(Customer, dataSource);
+
+ // we already have this line to create a ReferencesManyRepository factory
+ this.accounts = this.createReferencesManyAccessorFor(
+ 'accounts',
+ accountRepositoryGetter,
+ );
+
+ // add this line to register inclusion resolver.
+ this.registerInclusionResolver('accounts', this.accounts.inclusionResolver);
+ }
+}
+```
+
+- We can simply include the relation in queries via `find()`, `findOne()`, and
+ `findById()` methods. For example, these queries return all customers with
+ their accounts:
+
+ if you process data at the repository level:
+
+ ```ts
+ customerRepository.find({include: ['accounts']});
+ ```
+
+ this is the same as the url:
+
+ ```
+ GET http://localhost:3000/customers?filter[include][]=accounts
+ ```
+
+ which returns:
+
+ ```ts
+ [
+ {
+ id: 1,
+ name: 'John',
+ accountIds: [12, 16],
+ accounts: [
+ {
+ id: 12,
+ balance: 99,
+ },
+ {
+ id: 16,
+ balance: 0,
+ },
+ ],
+ },
+ {
+ id: 2,
+ name: 'Dave',
+ accountIds: [4],
+ accounts: [
+ {
+ id: 4,
+ balance: 10,
+ },
+ ],
+ },
+ ];
+ ```
+
+- You can delete a relation from `inclusionResolvers` to disable the inclusion
+ for a certain relation. e.g
+ `customerRepository.inclusionResolvers.delete('accounts')`
+
+### Query multiple relations
+
+It is possible to query several relations or nested include relations with
+custom scope once you have the inclusion resolver of each relation set up. Check
+[HasMany - Query multiple relations](HasMany-relation.md#query-multiple-relations)
+for the usage and examples.
diff --git a/docs/site/Relation-generator.md b/docs/site/Relation-generator.md
index f4190c1b1ee3..322b41e8e04b 100644
--- a/docs/site/Relation-generator.md
+++ b/docs/site/Relation-generator.md
@@ -138,6 +138,7 @@ The tool will prompt you for:
- [HasManyThrough](HasManyThrough-relation.md)
- [HasOne](HasOne-relation.md)
- [BelongsTo](BelongsTo-relation.md)
+ - [ReferencesMany](ReferencesMany-relation.md)
- Name of the source model (`sourceModel`). Prompts a list of available models
to choose from as the source model of the relation.
diff --git a/docs/site/Relations.md b/docs/site/Relations.md
index 9cac7c2401c4..293337fe57f5 100644
--- a/docs/site/Relations.md
+++ b/docs/site/Relations.md
@@ -45,6 +45,7 @@ Here are the currently supported relations:
- [BelongsTo](BelongsTo-relation.md)
- [HasOne](HasOne-relation.md)
- [HasManyThrough](HasManyThrough-relation.md)
+- [ReferencesMany](ReferencesMany-relation.md)
{% include note.html content="
The `hasMany` relation may alternatively be implemented using the
diff --git a/docs/site/decorators/Decorators_repository.md b/docs/site/decorators/Decorators_repository.md
index 84077acc84a3..c44ae41af998 100644
--- a/docs/site/decorators/Decorators_repository.md
+++ b/docs/site/decorators/Decorators_repository.md
@@ -255,6 +255,31 @@ export class Todo extends Entity {
}
```
+#### ReferencesMany Decorator
+
+Syntax:
+`@referencesMany(targetResolver: EntityResolver, definition?: Partial)`
+
+A ReferencesMany relation embeds an array of foreign keys to reference other
+objects. E.g. a `Customer` model references many `Account` objects. See
+[ReferencesMany relation](../ReferencesMany-relation.md) for more details.
+
+{% include code-caption.html content="customer.model.ts" %}
+
+```ts
+import {referencesMany} from '@loopback/repository';
+import {Account} from './account.model';
+
+export class Customer extends Entity {
+ // properties
+
+ @referencesMany(() => Account)
+ accountIds: number[];
+
+ // etc
+}
+```
+
#### Other Decorators
The following decorators are not implemented yet. To see their progress, please
diff --git a/docs/site/migration/models/relations.md b/docs/site/migration/models/relations.md
index fa8d7b2abd37..e7e2e1ba63f7 100644
--- a/docs/site/migration/models/relations.md
+++ b/docs/site/migration/models/relations.md
@@ -69,6 +69,7 @@ LoopBack 3:
- [HasOne](../../HasOne-relation.md)
- [BelongsTo](../../BelongsTo-relation.md)
- [HasManyThrough](../../HasManyThrough-relation.md)
+- [ReferencesMany](../../ReferencesMany-relation.md)
Other relations types are not supported yet, you can subscribe to our progress
in the high-level tracking issue
@@ -80,5 +81,3 @@ issue, for example:
[loopback-next#2308](https://github.com/loopbackio/loopback-next/issues/2308)
- Polymorphic relations -
[loopback-next#2487](https://github.com/loopbackio/loopback-next/issues/2487)
-- ReferencesMany -
- [loopback-next#2488](https://github.com/loopbackio/loopback-next/issues/1450)
diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml
index 725d3645e33c..5f5bc4ed89b1 100644
--- a/docs/site/sidebars/lb4_sidebar.yml
+++ b/docs/site/sidebars/lb4_sidebar.yml
@@ -757,6 +757,10 @@ children:
url: HasManyThrough-relation.html
output: 'web, pdf'
+ - title: 'ReferencesMany Relation'
+ url: ReferencesMany-relation.html
+ output: 'web, pdf'
+
- title: 'Repository'
url: Repository.html
output: 'web, pdf'
diff --git a/examples/references-many/.dockerignore b/examples/references-many/.dockerignore
new file mode 100644
index 000000000000..7aecc7e3dda5
--- /dev/null
+++ b/examples/references-many/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+npm-debug.log
+/dist
+# Cache used by TypeScript's incremental build
+*.tsbuildinfo
diff --git a/examples/references-many/.eslintrc.js b/examples/references-many/.eslintrc.js
new file mode 100644
index 000000000000..980c8cb5bf14
--- /dev/null
+++ b/examples/references-many/.eslintrc.js
@@ -0,0 +1,8 @@
+// Copyright IBM Corp. 2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+module.exports = {
+ extends: ['@loopback/eslint-config'],
+};
diff --git a/examples/references-many/.npmignore b/examples/references-many/.npmignore
new file mode 100644
index 000000000000..f86f365ab85b
--- /dev/null
+++ b/examples/references-many/.npmignore
@@ -0,0 +1,2 @@
+# Exclude *.tsbuildinfo - cache for tsc incremental builds
+*.tsbuildinfo
diff --git a/examples/references-many/.npmrc b/examples/references-many/.npmrc
new file mode 100644
index 000000000000..cafe685a112d
--- /dev/null
+++ b/examples/references-many/.npmrc
@@ -0,0 +1 @@
+package-lock=true
diff --git a/examples/references-many/.prettierignore b/examples/references-many/.prettierignore
new file mode 100644
index 000000000000..c6911da9e1e8
--- /dev/null
+++ b/examples/references-many/.prettierignore
@@ -0,0 +1,2 @@
+dist
+*.json
diff --git a/examples/references-many/.prettierrc b/examples/references-many/.prettierrc
new file mode 100644
index 000000000000..2e48c76c3184
--- /dev/null
+++ b/examples/references-many/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "bracketSpacing": false,
+ "singleQuote": true,
+ "printWidth": 80,
+ "trailingComma": "all",
+ "arrowParens": "avoid"
+}
diff --git a/examples/references-many/.vscode/settings.json b/examples/references-many/.vscode/settings.json
new file mode 100644
index 000000000000..7c80afccc2b4
--- /dev/null
+++ b/examples/references-many/.vscode/settings.json
@@ -0,0 +1,20 @@
+{
+ "editor.rulers": [80],
+ "editor.tabCompletion": "on",
+ "editor.tabSize": 2,
+ "editor.trimAutoWhitespace": true,
+ "editor.formatOnSave": true,
+
+ "files.exclude": {
+ "**/.DS_Store": true,
+ "**/.git": true,
+ "**/.hg": true,
+ "**/.svn": true,
+ "**/CVS": true,
+ "dist": true,
+ },
+ "files.insertFinalNewline": true,
+ "files.trimTrailingWhitespace": true,
+
+ "typescript.tsdk": "./node_modules/typescript/lib"
+}
diff --git a/examples/references-many/.vscode/tasks.json b/examples/references-many/.vscode/tasks.json
new file mode 100644
index 000000000000..555f092d81eb
--- /dev/null
+++ b/examples/references-many/.vscode/tasks.json
@@ -0,0 +1,29 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Watch and Compile Project",
+ "type": "shell",
+ "command": "npm",
+ "args": ["--silent", "run", "build:watch"],
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "problemMatcher": "$tsc-watch"
+ },
+ {
+ "label": "Build, Test and Lint",
+ "type": "shell",
+ "command": "npm",
+ "args": ["--silent", "run", "test:dev"],
+ "group": {
+ "kind": "test",
+ "isDefault": true
+ },
+ "problemMatcher": ["$tsc", "$eslint-stylish", "$eslint-compact"]
+ }
+ ]
+}
diff --git a/examples/references-many/CHANGELOG.md b/examples/references-many/CHANGELOG.md
new file mode 100644
index 000000000000..e4d87c4d45c4
--- /dev/null
+++ b/examples/references-many/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/examples/references-many/Dockerfile b/examples/references-many/Dockerfile
new file mode 100644
index 000000000000..ae6b17598621
--- /dev/null
+++ b/examples/references-many/Dockerfile
@@ -0,0 +1,28 @@
+# Check out https://hub.docker.com/_/node to select a new base image
+FROM node:16-slim
+
+# Set to a non-root built-in user `node`
+USER node
+
+# Create app directory (with user `node`)
+RUN mkdir -p /home/node/app
+
+WORKDIR /home/node/app
+
+# Install app dependencies
+# A wildcard is used to ensure both package.json AND package-lock.json are copied
+# where available (npm@5+)
+COPY --chown=node package*.json ./
+
+RUN npm install
+
+# Bundle app source code
+COPY --chown=node . .
+
+RUN npm run build
+
+# Bind to all network interfaces so that it can be mapped to the host OS
+ENV HOST=0.0.0.0 PORT=3000
+
+EXPOSE ${PORT}
+CMD [ "node", "." ]
diff --git a/examples/references-many/LICENSE b/examples/references-many/LICENSE
new file mode 100644
index 000000000000..c5ce0c376ceb
--- /dev/null
+++ b/examples/references-many/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) IBM Corp. 2018,2019.
+Node module: @loopback/example-references-many
+This project is licensed under the MIT License, full text below.
+
+--------
+
+MIT license
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/examples/references-many/README.md b/examples/references-many/README.md
new file mode 100644
index 000000000000..709c4724a896
--- /dev/null
+++ b/examples/references-many/README.md
@@ -0,0 +1,49 @@
+# @loopback/example-references-many
+
+## Try it out
+
+1. Run the `lb4 example` command to select and clone the repository:
+
+ ```sh
+ lb4 example references-many
+ ```
+
+2. Switch to the directory.
+
+ ```sh
+ cd loopback4-example-references-many
+ ```
+
+3. Finally, start the application!
+
+ ```sh
+ $ npm start
+
+ Server is running at http://127.0.0.1:3000
+ ```
+
+Feel free to look around in the application's code to get a feel for how it
+works.
+
+### Bugs/Feedback
+
+Open an issue in [loopback-next](https://github.com/loopbackio/loopback-next)
+and we'll take a look!
+
+## Contributions
+
+- [Guidelines](https://github.com/loopbackio/loopback-next/blob/master/docs/CONTRIBUTING.md)
+- [Join the team](https://github.com/loopbackio/loopback-next/issues/110)
+
+## Tests
+
+Run `npm test` from the root folder.
+
+## Contributors
+
+See
+[all contributors](https://github.com/loopbackio/loopback-next/graphs/contributors).
+
+## License
+
+MIT
diff --git a/examples/references-many/data/db.json b/examples/references-many/data/db.json
new file mode 100644
index 000000000000..5a4f28814253
--- /dev/null
+++ b/examples/references-many/data/db.json
@@ -0,0 +1,18 @@
+{
+ "ids": {
+ "Customer": 5,
+ "Account": 3
+ },
+ "models": {
+ "Customer": {
+ "1": "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"accountIds\":[\"1\",\"2\"],\"id\":1}",
+ "2": "{\"firstName\":\"Tommy\",\"lastName\":\"Lee\",\"accountIds\":[\"1\"],\"id\":2}",
+ "3": "{\"firstName\":\"Julian\",\"lastName\":\"Lennon\",\"accountIds\":[],\"id\":3}",
+ "4": "{\"firstName\":\"Monica\",\"lastName\":\"Raymund\",\"accountIds\":[],\"id\":4}"
+ },
+ "Account": {
+ "1": "{\"balance\":0,\"id\":1}",
+ "2": "{\"balance\":20,\"id\":2}"
+ }
+ }
+}
diff --git a/examples/references-many/package-lock.json b/examples/references-many/package-lock.json
new file mode 100644
index 000000000000..c6c6b56850bb
--- /dev/null
+++ b/examples/references-many/package-lock.json
@@ -0,0 +1,3096 @@
+{
+ "name": "@loopback/example-references-many",
+ "version": "5.0.1",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@loopback/example-references-many",
+ "version": "5.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "loopback-connector-rest": "^4.0.1",
+ "tslib": "^2.3.1"
+ },
+ "devDependencies": {
+ "@types/lodash": "^4.14.178",
+ "@types/node": "^10.17.60",
+ "eslint": "^8.8.0",
+ "lodash": "^4.17.21",
+ "typescript": "~4.5.5"
+ },
+ "engines": {
+ "node": "12 || 14 || 16 || 17"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
+ "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.2.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz",
+ "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.14.178",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "10.17.60",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "dev": true
+ },
+ "node_modules/accept-language": {
+ "version": "3.0.18",
+ "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz",
+ "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=",
+ "dependencies": {
+ "bcp47": "^1.1.2",
+ "stable": "^0.1.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+ "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/asn1": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "node_modules/aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws4": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/bcp47": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz",
+ "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dependencies": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/cldrjs": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/cldrjs/-/cldrjs-0.5.5.tgz",
+ "integrity": "sha512-KDwzwbmLIPfCgd8JERVDpQKrUUM1U4KpFJJg2IROv89rF172lLufoJnqJ/Wea6fXL5bO6WjuLMzY8V52UWPvkA=="
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "dependencies": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz",
+ "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/eslintrc": "^1.0.5",
+ "@humanwhocodes/config-array": "^0.9.2",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.0",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.2.0",
+ "espree": "^9.3.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^6.0.1",
+ "globals": "^13.6.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
+ "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz",
+ "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
+ "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.7.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^3.1.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "get-stream": "^5.0.0",
+ "human-signals": "^1.1.1",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.0",
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "engines": [
+ "node >=0.6.0"
+ ]
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
+ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
+ "dev": true
+ },
+ "node_modules/forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+ "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globalize": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/globalize/-/globalize-1.7.0.tgz",
+ "integrity": "sha512-faR46vTIbFCeAemyuc9E6/d7Wrx9k2ae2L60UhakztFg6VuE42gENVJNuPFtt7Sdjrk9m2w8+py7Jj+JTNy59w==",
+ "dependencies": {
+ "cldrjs": "^0.5.4"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/har-validator": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "deprecated": "this library is no longer supported",
+ "dependencies": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.8",
+ "npm": ">=1.3.7"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
+ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
+ "engines": {
+ "node": ">=8.12.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/invert-kv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz",
+ "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/invert-kv?sponsor=1"
+ }
+ },
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "node_modules/isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "node_modules/jsonpath-plus": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-4.0.0.tgz",
+ "integrity": "sha512-e0Jtg4KAzDJKKwzbLaUtinCn0RZseWBVRTRGihSpvFlM3wTR7ExSp+PTdeTsDrLNJUe7L7JYJe8mblHX5SCT6A==",
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/jsprim": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+ "dependencies": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.4.0",
+ "verror": "1.10.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/lcid": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz",
+ "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==",
+ "dependencies": {
+ "invert-kv": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/loopback-connector-rest": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/loopback-connector-rest/-/loopback-connector-rest-4.0.1.tgz",
+ "integrity": "sha512-iAyTlOMtZqo/oI6lUlUIdA5Q0+1cqms81o3LnHRKGiyt4CjUSvXgtBvJBlhiqvwbL+oDTItYwglpiacrdfUSBw==",
+ "dependencies": {
+ "debug": "^4.1.0",
+ "jsonpath-plus": "^4.0.0",
+ "lodash": "^4.17.11",
+ "methods": "^1.1.1",
+ "mime": "^2.3.1",
+ "qs": "^6.1.0",
+ "request": "^2.53.0",
+ "strong-globalize": "^6.0.5",
+ "traverse": "^0.6.6"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "dependencies": {
+ "p-defer": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/md5": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+ "dependencies": {
+ "charenc": "0.0.2",
+ "crypt": "0.0.2",
+ "is-buffer": "~1.1.6"
+ }
+ },
+ "node_modules/mem": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz",
+ "integrity": "sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==",
+ "dependencies": {
+ "map-age-cleaner": "^0.1.3",
+ "mimic-fn": "^2.1.0",
+ "p-is-promise": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.34",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+ "dependencies": {
+ "mime-db": "1.51.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+ "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/os-locale": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz",
+ "integrity": "sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==",
+ "dependencies": {
+ "execa": "^4.0.0",
+ "lcid": "^3.0.0",
+ "mem": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+ "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
+ "dependencies": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/request/node_modules/qs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "node_modules/sshpk": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+ "dependencies": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ },
+ "bin": {
+ "sshpk-conv": "bin/sshpk-conv",
+ "sshpk-sign": "bin/sshpk-sign",
+ "sshpk-verify": "bin/sshpk-verify"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stable": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strong-globalize": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-6.0.5.tgz",
+ "integrity": "sha512-7nfUli41TieV9/TSc0N62ve5Q4nfrpy/T0nNNy6TyD3vst79QWmeylCyd3q1gDxh8dqGEtabLNCdPQP1Iuvecw==",
+ "dependencies": {
+ "accept-language": "^3.0.18",
+ "debug": "^4.2.0",
+ "globalize": "^1.6.0",
+ "lodash": "^4.17.20",
+ "md5": "^2.3.0",
+ "mkdirp": "^1.0.4",
+ "os-locale": "^5.0.0",
+ "yamljs": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "node_modules/tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "dependencies": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/traverse": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
+ "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc="
+ },
+ "node_modules/tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
+ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "node_modules/verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "node_modules/yamljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
+ "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ },
+ "bin": {
+ "json2yaml": "bin/json2yaml",
+ "yaml2json": "bin/yaml2json"
+ }
+ },
+ "node_modules/yamljs/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ }
+ },
+ "dependencies": {
+ "@eslint/eslintrc": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
+ "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.2.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ }
+ }
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz",
+ "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==",
+ "dev": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "@types/lodash": {
+ "version": "4.14.178",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "10.17.60",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "dev": true
+ },
+ "accept-language": {
+ "version": "3.0.18",
+ "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz",
+ "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=",
+ "requires": {
+ "bcp47": "^1.1.2",
+ "stable": "^0.1.6"
+ }
+ },
+ "acorn": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+ "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "asn1": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ },
+ "aws4": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "bcp47": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz",
+ "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "requires": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "charenc": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+ "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+ },
+ "cldrjs": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/cldrjs/-/cldrjs-0.5.5.tgz",
+ "integrity": "sha512-KDwzwbmLIPfCgd8JERVDpQKrUUM1U4KpFJJg2IROv89rF172lLufoJnqJ/Wea6fXL5bO6WjuLMzY8V52UWPvkA=="
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "crypt": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz",
+ "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==",
+ "dev": true,
+ "requires": {
+ "@eslint/eslintrc": "^1.0.5",
+ "@humanwhocodes/config-array": "^0.9.2",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.0",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.2.0",
+ "espree": "^9.3.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^6.0.1",
+ "globals": "^13.6.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-scope": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
+ "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz",
+ "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
+ "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.7.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^3.1.0"
+ }
+ },
+ "esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "execa": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+ "requires": {
+ "cross-spawn": "^7.0.0",
+ "get-stream": "^5.0.0",
+ "human-signals": "^1.1.1",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.0",
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2",
+ "strip-final-newline": "^2.0.0"
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
+ "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
+ "dev": true
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "get-intrinsic": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
+ "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "requires": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globalize": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/globalize/-/globalize-1.7.0.tgz",
+ "integrity": "sha512-faR46vTIbFCeAemyuc9E6/d7Wrx9k2ae2L60UhakztFg6VuE42gENVJNuPFtt7Sdjrk9m2w8+py7Jj+JTNy59w==",
+ "requires": {
+ "cldrjs": "^0.5.4"
+ }
+ },
+ "globals": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ },
+ "har-validator": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "requires": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "human-signals": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
+ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
+ },
+ "ignore": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "invert-kv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz",
+ "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw=="
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsonpath-plus": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-4.0.0.tgz",
+ "integrity": "sha512-e0Jtg4KAzDJKKwzbLaUtinCn0RZseWBVRTRGihSpvFlM3wTR7ExSp+PTdeTsDrLNJUe7L7JYJe8mblHX5SCT6A=="
+ },
+ "jsprim": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+ "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.4.0",
+ "verror": "1.10.0"
+ }
+ },
+ "lcid": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz",
+ "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==",
+ "requires": {
+ "invert-kv": "^3.0.0"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "loopback-connector-rest": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/loopback-connector-rest/-/loopback-connector-rest-4.0.1.tgz",
+ "integrity": "sha512-iAyTlOMtZqo/oI6lUlUIdA5Q0+1cqms81o3LnHRKGiyt4CjUSvXgtBvJBlhiqvwbL+oDTItYwglpiacrdfUSBw==",
+ "requires": {
+ "debug": "^4.1.0",
+ "jsonpath-plus": "^4.0.0",
+ "lodash": "^4.17.11",
+ "methods": "^1.1.1",
+ "mime": "^2.3.1",
+ "qs": "^6.1.0",
+ "request": "^2.53.0",
+ "strong-globalize": "^6.0.5",
+ "traverse": "^0.6.6"
+ }
+ },
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
+ "md5": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+ "requires": {
+ "charenc": "0.0.2",
+ "crypt": "0.0.2",
+ "is-buffer": "~1.1.6"
+ }
+ },
+ "mem": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz",
+ "integrity": "sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==",
+ "requires": {
+ "map-age-cleaner": "^0.1.3",
+ "mimic-fn": "^2.1.0",
+ "p-is-promise": "^2.1.0"
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
+ },
+ "mime-db": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
+ },
+ "mime-types": {
+ "version": "2.1.34",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+ "requires": {
+ "mime-db": "1.51.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+ },
+ "object-inspect": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
+ "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "os-locale": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz",
+ "integrity": "sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==",
+ "requires": {
+ "execa": "^4.0.0",
+ "lcid": "^3.0.0",
+ "mem": "^5.0.0"
+ }
+ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
+ },
+ "p-is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg=="
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "qs": {
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+ "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
+ },
+ "regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true
+ },
+ "request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
+ }
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ },
+ "side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "requires": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ }
+ },
+ "signal-exit": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "sshpk": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
+ "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "stable": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "strong-globalize": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-6.0.5.tgz",
+ "integrity": "sha512-7nfUli41TieV9/TSc0N62ve5Q4nfrpy/T0nNNy6TyD3vst79QWmeylCyd3q1gDxh8dqGEtabLNCdPQP1Iuvecw==",
+ "requires": {
+ "accept-language": "^3.0.18",
+ "debug": "^4.2.0",
+ "globalize": "^1.6.0",
+ "lodash": "^4.17.20",
+ "md5": "^2.3.0",
+ "mkdirp": "^1.0.4",
+ "os-locale": "^5.0.0",
+ "yamljs": "^0.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
+ "traverse": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
+ "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc="
+ },
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
+ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+ },
+ "v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "dev": true
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "yamljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
+ "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
+ "requires": {
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/references-many/package.json b/examples/references-many/package.json
new file mode 100644
index 000000000000..89db885b6589
--- /dev/null
+++ b/examples/references-many/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "@loopback/example-references-many",
+ "description": "Example of the references many relation in LoopBack 4.",
+ "version": "5.0.1",
+ "keywords": [
+ "loopback",
+ "LoopBack",
+ "example",
+ "tutorial",
+ "relations",
+ "CRUD",
+ "models",
+ "referenceMany",
+ "HasMany"
+ ],
+ "license": "MIT",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "author": "IBM Corp.",
+ "copyright.owner": "IBM Corp.",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/loopbackio/loopback-next.git",
+ "directory": "examples/references-many"
+ },
+ "engines": {
+ "node": "12 || 14 || 16 || 17"
+ },
+ "scripts": {
+ "build": "lb-tsc",
+ "build:watch": "lb-tsc --watch",
+ "clean": "lb-clean *example-references-many*.tgz dist *.tsbuildinfo package",
+ "lint": "npm run prettier:check && npm run eslint",
+ "lint:fix": "npm run eslint:fix && npm run prettier:fix",
+ "prettier:cli": "lb-prettier \"**/*.ts\"",
+ "prettier:check": "npm run prettier:cli -- -l",
+ "prettier:fix": "npm run prettier:cli -- --write",
+ "eslint": "lb-eslint --report-unused-disable-directives .",
+ "eslint:fix": "npm run eslint -- --fix",
+ "pretest": "npm run rebuild",
+ "test": "lb-mocha \"dist/__tests__/**/*.js\"",
+ "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest",
+ "verify": "npm pack && tar xf loopback-references-many*.tgz && tree package && npm run clean",
+ "premigrate": "npm run build ",
+ "migrate": "node ./dist/migrate",
+ "preopenapi-spec": "npm run build",
+ "openapi-spec": "node ./dist/openapi-spec",
+ "rebuild": "npm run clean && npm run build",
+ "prestart": "npm run rebuild",
+ "start": "node ."
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@loopback/boot": "^4.0.1",
+ "@loopback/core": "^3.0.1",
+ "@loopback/repository": "^4.0.1",
+ "@loopback/rest": "^11.0.1",
+ "@loopback/rest-explorer": "^4.0.1",
+ "@loopback/service-proxy": "^4.0.1",
+ "loopback-connector-rest": "^4.0.1",
+ "tslib": "^2.3.1"
+ },
+ "devDependencies": {
+ "@loopback/build": "^8.0.1",
+ "@loopback/eslint-config": "^12.0.1",
+ "@loopback/http-caching-proxy": "^3.0.1",
+ "@loopback/repository": "^4.0.1",
+ "@loopback/testlab": "^4.0.1",
+ "@types/lodash": "^4.14.178",
+ "@types/node": "^10.17.60",
+ "eslint": "^8.8.0",
+ "lodash": "^4.17.21",
+ "typescript": "~4.5.5"
+ }
+}
diff --git a/examples/references-many/public/index.html b/examples/references-many/public/index.html
new file mode 100644
index 000000000000..d93d5e277cb5
--- /dev/null
+++ b/examples/references-many/public/index.html
@@ -0,0 +1,72 @@
+
+
+
+
+ myapp
+
+
+
+
+
+
+
+
+
+
+
+
@loopback/example-references-many
+
+
+
+
+
+
+
+
+
diff --git a/examples/references-many/src/__tests__/acceptance/account.acceptance.ts b/examples/references-many/src/__tests__/acceptance/account.acceptance.ts
new file mode 100644
index 000000000000..5b990bd9b84b
--- /dev/null
+++ b/examples/references-many/src/__tests__/acceptance/account.acceptance.ts
@@ -0,0 +1,139 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {EntityNotFoundError} from '@loopback/repository';
+import {Client, createRestAppClient, expect, toJSON} from '@loopback/testlab';
+import {ReferencesManyApplication} from '../../application';
+import {Account} from '../../models/';
+import {AccountRepository} from '../../repositories/';
+import {
+ givenRunningApplicationWithCustomConfiguration,
+ givenAccount,
+ givenAccountInstance,
+ givenAccountRepositories,
+} from '../helpers';
+
+describe('ReferencesManyApplication', () => {
+ let app: ReferencesManyApplication;
+ let client: Client;
+ let accountRepo: AccountRepository;
+
+ before(async () => {
+ app = await givenRunningApplicationWithCustomConfiguration();
+ });
+ after(() => app.stop());
+
+ before(async () => {
+ ({accountRepo} = await givenAccountRepositories(app));
+ });
+ before(() => {
+ client = createRestAppClient(app);
+ });
+
+ beforeEach(async () => {
+ await accountRepo.deleteAll();
+ });
+
+ it('creates an account', async function () {
+ const account = givenAccount();
+ const response = await client.post('/accounts').send(account).expect(200);
+ expect(response.body).to.containDeep(account);
+ const result = await accountRepo.findById(response.body.id);
+ expect(result).to.containDeep(account);
+ });
+
+ it('gets a count of accounts', async function () {
+ await givenAccountInstance(accountRepo, {balance: 22});
+ await givenAccountInstance(accountRepo, {balance: 33});
+ await client.get('/accounts/count').expect(200, {count: 2});
+ });
+
+ context('when dealing with a single persisted account', () => {
+ let persistedAccount: Account;
+
+ beforeEach(async () => {
+ persistedAccount = await givenAccountInstance(accountRepo);
+ });
+
+ it('gets an account by ID', () => {
+ return client
+ .get(`/accounts/${persistedAccount.id}`)
+ .send()
+ .expect(200, toJSON(persistedAccount));
+ });
+
+ it('returns 404 when getting an account that does not exist', () => {
+ return client.get('/accounts/99999').expect(404);
+ });
+
+ it('replaces the account by ID', async () => {
+ const updatedAccount = givenAccount({
+ balance: 44,
+ });
+ await client
+ .put(`/accounts/${persistedAccount.id}`)
+ .send(updatedAccount)
+ .expect(204);
+ const result = await accountRepo.findById(persistedAccount.id);
+ expect(result).to.containEql(updatedAccount);
+ });
+
+ it('returns 404 when replacing an account that does not exist', () => {
+ return client.put('/accounts/99999').send(givenAccount()).expect(404);
+ });
+
+ it('updates the account by ID ', async () => {
+ const updatedAccount = givenAccount({
+ balance: 55,
+ });
+ await client
+ .patch(`/accounts/${persistedAccount.id}`)
+ .send(updatedAccount)
+ .expect(204);
+ const result = await accountRepo.findById(persistedAccount.id);
+ expect(result).to.containEql(updatedAccount);
+ });
+
+ it('returns 404 when updating an account that does not exist', () => {
+ return client.patch('/account/99999').send(givenAccount()).expect(404);
+ });
+
+ it('deletes the account', async () => {
+ await client.del(`/accounts/${persistedAccount.id}`).send().expect(204);
+ await expect(
+ accountRepo.findById(persistedAccount.id),
+ ).to.be.rejectedWith(EntityNotFoundError);
+ });
+
+ it('returns 404 when deleting an account that does not exist', async () => {
+ await client.del(`/accounts/99999`).expect(404);
+ });
+ });
+
+ it('queries accounts with a filter', async () => {
+ await givenAccountInstance(accountRepo, {balance: 77});
+
+ const emptyAccount = await givenAccountInstance(accountRepo, {balance: 0});
+
+ await client
+ .get('/accounts')
+ .query({filter: {where: {balance: 0}}})
+ .expect(200, [toJSON(emptyAccount)]);
+ });
+
+ it('updates accounts using a filter', async () => {
+ await givenAccountInstance(accountRepo, {
+ balance: 1,
+ });
+ await givenAccountInstance(accountRepo, {
+ balance: 2,
+ });
+ await client
+ .patch('/accounts')
+ .query({where: {balance: 2}})
+ .send({balance: 3})
+ .expect(200, {count: 1});
+ });
+});
diff --git a/examples/references-many/src/__tests__/acceptance/customer.acceptance.ts b/examples/references-many/src/__tests__/acceptance/customer.acceptance.ts
new file mode 100644
index 000000000000..a2a127b039a3
--- /dev/null
+++ b/examples/references-many/src/__tests__/acceptance/customer.acceptance.ts
@@ -0,0 +1,198 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {EntityNotFoundError} from '@loopback/repository';
+import {Client, createRestAppClient, expect, toJSON} from '@loopback/testlab';
+import {ReferencesManyApplication} from '../../application';
+import {Customer} from '../../models/';
+import {AccountRepository, CustomerRepository} from '../../repositories/';
+import {
+ givenRunningApplicationWithCustomConfiguration,
+ givenCustomer,
+ givenCustomerInstance,
+ givenCustomerRepositories,
+ givenAccountInstance,
+} from '../helpers';
+
+describe('ReferencesManyApplication', () => {
+ let app: ReferencesManyApplication;
+ let client: Client;
+ let customerRepo: CustomerRepository;
+ let accountRepo: AccountRepository;
+
+ before(async () => {
+ app = await givenRunningApplicationWithCustomConfiguration();
+ });
+ after(() => app.stop());
+
+ before(async () => {
+ ({customerRepo, accountRepo} = await givenCustomerRepositories(app));
+ });
+ before(() => {
+ client = createRestAppClient(app);
+ });
+
+ beforeEach(async () => {
+ await customerRepo.deleteAll();
+ await accountRepo.deleteAll();
+ });
+
+ it('creates a customer', async function () {
+ const customer = givenCustomer();
+ const response = await client.post('/customers').send(customer).expect(200);
+ expect(response.body).to.containDeep(customer);
+ const result = await customerRepo.findById(response.body.id);
+ expect(result).to.containDeep(customer);
+ });
+
+ it('gets a count of customers', async function () {
+ await givenCustomerInstance(customerRepo, {
+ firstName: 'Andrew',
+ lastName: 'Jackson',
+ });
+ await givenCustomerInstance(customerRepo, {
+ firstName: 'James',
+ lastName: 'Gunn',
+ });
+ await client.get('/customers/count').expect(200, {count: 2});
+ });
+
+ context('when dealing with a single persisted customer', () => {
+ let persistedCustomer: Customer;
+
+ beforeEach(async () => {
+ persistedCustomer = await givenCustomerInstance(customerRepo);
+ });
+
+ it('gets a customer by ID', () => {
+ return client
+ .get(`/customers/${persistedCustomer.id}`)
+ .send()
+ .expect(200, toJSON(persistedCustomer));
+ });
+
+ it('returns 404 when getting a customer that does not exist', () => {
+ return client.get('/customers/99999').expect(404);
+ });
+
+ it('replaces the customer by ID', async () => {
+ const updatedCustomer = givenCustomer({
+ firstName: 'Andrew',
+ lastName: 'Jackson',
+ });
+ await client
+ .put(`/customers/${persistedCustomer.id}`)
+ .send(updatedCustomer)
+ .expect(204);
+ const result = await customerRepo.findById(persistedCustomer.id);
+ expect(result).to.containEql(updatedCustomer);
+ });
+
+ it('returns 404 when replacing a customer that does not exist', () => {
+ return client.put('/customers/99999').send(givenCustomer()).expect(404);
+ });
+
+ it('updates the customer by ID ', async () => {
+ const updatedCustomer = givenCustomer({
+ firstName: 'Tommy',
+ lastName: 'Jeans',
+ });
+ await client
+ .patch(`/customers/${persistedCustomer.id}`)
+ .send(updatedCustomer)
+ .expect(204);
+ const result = await customerRepo.findById(persistedCustomer.id);
+ expect(result).to.containEql(updatedCustomer);
+ });
+
+ it('returns 404 when updating a customer that does not exist', () => {
+ return client.patch('/customer/99999').send(givenCustomer()).expect(404);
+ });
+
+ it('deletes the customer', async () => {
+ await client.del(`/customers/${persistedCustomer.id}`).send().expect(204);
+ await expect(
+ customerRepo.findById(persistedCustomer.id),
+ ).to.be.rejectedWith(EntityNotFoundError);
+ });
+
+ it('returns 404 when deleting a customer that does not exist', async () => {
+ await client.del(`/customers/99999`).expect(404);
+ });
+ });
+
+ it('queries customers with a filter', async () => {
+ await givenCustomerInstance(customerRepo, {
+ firstName: 'Andrew',
+ lastName: 'Jackson',
+ });
+
+ const unnamedCustomer = await givenCustomerInstance(customerRepo, {
+ firstName: '',
+ lastName: '',
+ });
+
+ await client
+ .get('/customers')
+ .query({filter: {where: {firstName: ''}}})
+ .expect(200, [toJSON(unnamedCustomer)]);
+ });
+
+ it('updates customers using a filter', async () => {
+ await givenCustomerInstance(customerRepo, {
+ firstName: 'Andrew',
+ lastName: 'Jackson',
+ });
+ await givenCustomerInstance(customerRepo, {
+ firstName: 'James',
+ lastName: 'Gunn',
+ });
+ await client
+ .patch('/customers')
+ .query({where: {firstName: 'Andrew'}})
+ .send({firstName: 'Tommy'})
+ .expect(200, {count: 1});
+ });
+
+ it('includes Accounts in query result', async () => {
+ const firstAccount = await givenAccountInstance(accountRepo, {balance: 10});
+ const secondAccount = await givenAccountInstance(accountRepo, {
+ balance: 20,
+ });
+ const customer = await givenCustomerInstance(customerRepo);
+ const customerWithAccounts = await givenCustomerInstance(customerRepo, {
+ accountIds: [firstAccount.id, secondAccount.id],
+ });
+ const filter = JSON.stringify({include: ['accounts']});
+
+ const response = await client.get('/customers').query({filter: filter});
+
+ expect(response.body).to.have.length(2);
+ expect(response.body[0]).to.deepEqual({
+ ...toJSON(customer),
+ accounts: [],
+ });
+ expect(response.body[1]).to.deepEqual({
+ ...toJSON(customerWithAccounts),
+ accounts: [toJSON(firstAccount), toJSON(secondAccount)],
+ });
+ });
+
+ it('not includes a not existed Account in query result', async () => {
+ const notExistedId = 1;
+ const customer = await givenCustomerInstance(customerRepo, {
+ accountIds: [notExistedId],
+ });
+ const filter = JSON.stringify({include: ['accounts']});
+
+ const response = await client.get('/customers').query({filter: filter});
+
+ expect(response.body).to.have.length(1);
+ expect(response.body[0]).to.deepEqual({
+ ...toJSON(customer),
+ accounts: [],
+ });
+ });
+});
diff --git a/examples/references-many/src/__tests__/acceptance/home-page.acceptance.ts b/examples/references-many/src/__tests__/acceptance/home-page.acceptance.ts
new file mode 100644
index 000000000000..9aeb05412a98
--- /dev/null
+++ b/examples/references-many/src/__tests__/acceptance/home-page.acceptance.ts
@@ -0,0 +1,36 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {Client} from '@loopback/testlab';
+import {ReferencesManyApplication} from '../..';
+import {setupApplication} from './test-helper';
+
+describe('HomePage', () => {
+ let app: ReferencesManyApplication;
+ let client: Client;
+
+ before('setupApplication', async () => {
+ ({app, client} = await setupApplication());
+ });
+
+ after(async () => {
+ await app.stop();
+ });
+
+ it('exposes a default home page', async () => {
+ await client
+ .get('/')
+ .expect(200)
+ .expect('Content-Type', /text\/html/);
+ });
+
+ it('exposes self-hosted explorer', async () => {
+ await client
+ .get('/explorer/')
+ .expect(200)
+ .expect('Content-Type', /text\/html/)
+ .expect(/LoopBack API Explorer/);
+ });
+});
diff --git a/examples/references-many/src/__tests__/acceptance/test-helper.ts b/examples/references-many/src/__tests__/acceptance/test-helper.ts
new file mode 100644
index 000000000000..95c7264941cd
--- /dev/null
+++ b/examples/references-many/src/__tests__/acceptance/test-helper.ts
@@ -0,0 +1,29 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {
+ Client,
+ createRestAppClient,
+ givenHttpServerConfig,
+} from '@loopback/testlab';
+import {ReferencesManyApplication} from '../..';
+
+export async function setupApplication(): Promise {
+ const app = new ReferencesManyApplication({
+ rest: givenHttpServerConfig(),
+ });
+
+ await app.boot();
+ await app.start();
+
+ const client = createRestAppClient(app);
+
+ return {app, client};
+}
+
+export interface AppWithClient {
+ app: ReferencesManyApplication;
+ client: Client;
+}
diff --git a/examples/references-many/src/__tests__/helpers.ts b/examples/references-many/src/__tests__/helpers.ts
new file mode 100644
index 000000000000..8a5946f1f256
--- /dev/null
+++ b/examples/references-many/src/__tests__/helpers.ts
@@ -0,0 +1,119 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {juggler} from '@loopback/repository';
+import {givenHttpServerConfig} from '@loopback/testlab';
+import {ReferencesManyApplication} from '../application';
+import {Account, Customer} from '../models';
+import {AccountRepository, CustomerRepository} from '../repositories';
+
+/*
+ ==============================================================================
+ HELPER FUNCTIONS
+ If you find yourself creating the same helper functions across different
+ test files, then extracting those functions into helper modules is an easy
+ way to reduce duplication.
+
+ Other tips:
+
+ - Using the super awesome Partial type in conjunction with Object.assign
+ means you can:
+ * customize the object you get back based only on what's important
+ to you during a particular test
+ * avoid writing test logic that is brittle with respect to the properties
+ of your object
+ - Making the input itself optional means you don't need to do anything special
+ for tests where the particular details of the input don't matter.
+ ==============================================================================
+ */
+
+/**
+ * Generate a complete Customer object for use with tests.
+ * @param customer - A partial (or complete) Customer object.
+ */
+export function givenCustomer(customer?: Partial) {
+ const data = Object.assign(
+ {
+ firstName: 'John',
+ lastName: 'Doe',
+ },
+ customer,
+ );
+ return new Customer(data);
+}
+
+/**
+ * Generate a complete Account object for use with tests.
+ * @param account - A partial (or complete) Account object.
+ */
+export function givenAccount(account?: Partial) {
+ const data = Object.assign({balance: 999}, account);
+ return new Account(data);
+}
+
+export async function givenRunningApplicationWithCustomConfiguration() {
+ const app = new ReferencesManyApplication({
+ rest: givenHttpServerConfig(),
+ });
+
+ await app.boot();
+
+ /**
+ * Override default config for DataSource for testing so we don't write
+ * test data to file when using the memory connector.
+ */
+ app.bind('datasources.config.db').to({
+ name: 'db',
+ connector: 'memory',
+ });
+
+ // Start Application
+ await app.start();
+ return app;
+}
+
+export async function givenCustomerRepositories(
+ app: ReferencesManyApplication,
+) {
+ const customerRepo = await app.getRepository(CustomerRepository);
+ const accountRepo = await app.getRepository(AccountRepository);
+ return {customerRepo, accountRepo};
+}
+
+export async function givenAccountRepositories(app: ReferencesManyApplication) {
+ const accountRepo = await app.getRepository(AccountRepository);
+ return {accountRepo};
+}
+
+export async function givenCustomerInstance(
+ customerRepo: CustomerRepository,
+ customer?: Partial,
+) {
+ return customerRepo.create(givenCustomer(customer));
+}
+
+export async function givenAccountInstance(
+ accountRepo: AccountRepository,
+ account?: Partial,
+) {
+ return accountRepo.create(givenAccount(account));
+}
+
+export async function givenEmptyDatabase() {
+ const accountRepo: AccountRepository = new AccountRepository(testdb);
+ const customerRepo: CustomerRepository = new CustomerRepository(
+ testdb,
+ async () => accountRepo,
+ async () => customerRepo,
+ );
+
+ await accountRepo.deleteAll();
+ await customerRepo.deleteAll();
+}
+
+export const testdb: juggler.DataSource = new juggler.DataSource({
+ name: 'db',
+ connector: 'memory',
+});
diff --git a/examples/references-many/src/__tests__/integration/customer.repository.integration.ts b/examples/references-many/src/__tests__/integration/customer.repository.integration.ts
new file mode 100644
index 000000000000..07571b45d590
--- /dev/null
+++ b/examples/references-many/src/__tests__/integration/customer.repository.integration.ts
@@ -0,0 +1,80 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {expect, toJSON} from '@loopback/testlab';
+import {AccountRepository, CustomerRepository} from '../../repositories';
+import {
+ givenEmptyDatabase,
+ givenAccountInstance,
+ givenCustomerInstance,
+ testdb,
+} from '../helpers';
+
+describe('ReferencesManyRepository', () => {
+ let accountRepo: AccountRepository;
+ let customerRepo: CustomerRepository;
+
+ before(async () => {
+ accountRepo = new AccountRepository(testdb);
+ customerRepo = new CustomerRepository(
+ testdb,
+ async () => accountRepo,
+ async () => customerRepo,
+ );
+ });
+
+ beforeEach(givenEmptyDatabase);
+
+ it('includes Accounts in find method result', async () => {
+ const account = await givenAccountInstance(accountRepo);
+ const customer = await givenCustomerInstance(customerRepo, {
+ accountIds: [account.id],
+ });
+
+ const response = await customerRepo.find({
+ include: ['accounts'],
+ });
+
+ expect(toJSON(response)).to.deepEqual([
+ {
+ ...toJSON(customer),
+ accounts: [toJSON(account)],
+ },
+ ]);
+ });
+
+ it('includes Accounts in findById result', async () => {
+ const account = await givenAccountInstance(accountRepo);
+ const customer = await givenCustomerInstance(customerRepo, {
+ accountIds: [account.id],
+ });
+
+ const response = await customerRepo.findById(customer.id, {
+ include: ['accounts'],
+ });
+
+ expect(toJSON(response)).to.deepEqual({
+ ...toJSON(customer),
+ accounts: [toJSON(account)],
+ });
+ });
+
+ it('includes Accounts in findOne method result', async () => {
+ const account = await givenAccountInstance(accountRepo);
+ const customer = await givenCustomerInstance(customerRepo, {
+ accountIds: [account.id],
+ });
+
+ const response = await customerRepo.findOne({
+ where: {id: customer.id},
+ include: ['accounts'],
+ });
+
+ expect(toJSON(response)).to.deepEqual({
+ ...toJSON(customer),
+ accounts: [toJSON(account)],
+ });
+ });
+});
diff --git a/examples/references-many/src/__tests__/unit/controllers/account.controller.unit.ts b/examples/references-many/src/__tests__/unit/controllers/account.controller.unit.ts
new file mode 100644
index 000000000000..8d2588c68a98
--- /dev/null
+++ b/examples/references-many/src/__tests__/unit/controllers/account.controller.unit.ts
@@ -0,0 +1,127 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-account-list
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {
+ createStubInstance,
+ expect,
+ sinon,
+ StubbedInstanceWithSinonAccessor,
+} from '@loopback/testlab';
+import {AccountController} from '../../../controllers';
+import {Account} from '../../../models';
+import {AccountRepository} from '../../../repositories';
+import {givenAccount} from '../../helpers';
+
+describe('AccountController', () => {
+ let accountRepo: StubbedInstanceWithSinonAccessor;
+
+ /*
+ =============================================================================
+ TEST VARIABLES
+ Combining top-level objects with our resetRepositories method means we don't
+ need to duplicate several variable assignments (and generation statements)
+ in all of our test logic.
+
+ NOTE: If you wanted to parallelize your test runs, you should avoid this
+ pattern since each of these tests is sharing references.
+ =============================================================================
+ */
+ let controller: AccountController;
+ let aAccount: Account;
+ let aAccountWithId: Account;
+ let aChangedAccount: Account;
+ let aListOfAccounts: Account[];
+
+ beforeEach(resetRepositories);
+
+ describe('createAccount', () => {
+ it('creates a Account', async () => {
+ const create = accountRepo.stubs.create;
+ create.resolves(aAccountWithId);
+ const result = await controller.create(aAccount);
+ expect(result).to.eql(aAccountWithId);
+ sinon.assert.calledWith(create, aAccount);
+ });
+ });
+
+ describe('findAccountById', () => {
+ it('returns a account if it exists', async () => {
+ const findById = accountRepo.stubs.findById;
+ findById.resolves(aAccountWithId);
+ expect(await controller.findById(aAccountWithId.id as number)).to.eql(
+ aAccountWithId,
+ );
+ sinon.assert.calledWith(findById, aAccountWithId.id);
+ });
+ });
+
+ describe('findAccounts', () => {
+ it('returns multiple accounts if they exist', async () => {
+ const find = accountRepo.stubs.find;
+ find.resolves(aListOfAccounts);
+ expect(await controller.find()).to.eql(aListOfAccounts);
+ sinon.assert.called(find);
+ });
+
+ it('returns empty list if no accounts exist', async () => {
+ const find = accountRepo.stubs.find;
+ const expected: Account[] = [];
+ find.resolves(expected);
+ expect(await controller.find()).to.eql(expected);
+ sinon.assert.called(find);
+ });
+ });
+
+ describe('replaceAccount', () => {
+ it('successfully replaces existing items', async () => {
+ const replaceById = accountRepo.stubs.replaceById;
+ replaceById.resolves();
+ await controller.replaceById(
+ aAccountWithId.id as number,
+ aChangedAccount,
+ );
+ sinon.assert.calledWith(replaceById, aAccountWithId.id, aChangedAccount);
+ });
+ });
+
+ describe('updateAccount', () => {
+ it('successfully updates existing items', async () => {
+ const updateById = accountRepo.stubs.updateById;
+ updateById.resolves();
+ await controller.updateById(aAccountWithId.id as number, aChangedAccount);
+ sinon.assert.calledWith(updateById, aAccountWithId.id, aChangedAccount);
+ });
+ });
+
+ describe('deleteAccount', () => {
+ it('successfully deletes existing items', async () => {
+ const deleteById = accountRepo.stubs.deleteById;
+ deleteById.resolves();
+ await controller.deleteById(aAccountWithId.id as number);
+ sinon.assert.calledWith(deleteById, aAccountWithId.id);
+ });
+ });
+
+ function resetRepositories() {
+ accountRepo = createStubInstance(AccountRepository);
+ aAccount = givenAccount();
+ aAccountWithId = givenAccount({
+ id: 1,
+ });
+ aListOfAccounts = [
+ aAccountWithId,
+ givenAccount({
+ id: 2,
+ balance: 5,
+ }),
+ ] as Account[];
+ aChangedAccount = givenAccount({
+ id: aAccountWithId.id,
+ balance: 10,
+ });
+
+ controller = new AccountController(accountRepo);
+ }
+});
diff --git a/examples/references-many/src/__tests__/unit/controllers/customer.controller.unit.ts b/examples/references-many/src/__tests__/unit/controllers/customer.controller.unit.ts
new file mode 100644
index 000000000000..5c2e5303db35
--- /dev/null
+++ b/examples/references-many/src/__tests__/unit/controllers/customer.controller.unit.ts
@@ -0,0 +1,136 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/example-customer-list
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {
+ createStubInstance,
+ expect,
+ sinon,
+ StubbedInstanceWithSinonAccessor,
+} from '@loopback/testlab';
+import {CustomerController} from '../../../controllers';
+import {Customer} from '../../../models';
+import {CustomerRepository} from '../../../repositories';
+import {givenCustomer} from '../../helpers';
+
+describe('CustomerController', () => {
+ let customerRepo: StubbedInstanceWithSinonAccessor;
+
+ /*
+ =============================================================================
+ TEST VARIABLES
+ Combining top-level objects with our resetRepositories method means we don't
+ need to duplicate several variable assignments (and generation statements)
+ in all of our test logic.
+
+ NOTE: If you wanted to parallelize your test runs, you should avoid this
+ pattern since each of these tests is sharing references.
+ =============================================================================
+ */
+ let controller: CustomerController;
+ let aCustomer: Customer;
+ let aCustomerWithId: Customer;
+ let aChangedCustomer: Customer;
+ let aListOfCustomers: Customer[];
+
+ beforeEach(resetRepositories);
+
+ describe('createCustomer', () => {
+ it('creates a Customer', async () => {
+ const create = customerRepo.stubs.create;
+ create.resolves(aCustomerWithId);
+ const result = await controller.create(aCustomer);
+ expect(result).to.eql(aCustomerWithId);
+ sinon.assert.calledWith(create, aCustomer);
+ });
+ });
+
+ describe('findCustomerById', () => {
+ it('returns a customer if it exists', async () => {
+ const findById = customerRepo.stubs.findById;
+ findById.resolves(aCustomerWithId);
+ expect(await controller.findById(aCustomerWithId.id as number)).to.eql(
+ aCustomerWithId,
+ );
+ sinon.assert.calledWith(findById, aCustomerWithId.id);
+ });
+ });
+
+ describe('findCustomers', () => {
+ it('returns multiple customers if they exist', async () => {
+ const find = customerRepo.stubs.find;
+ find.resolves(aListOfCustomers);
+ expect(await controller.find()).to.eql(aListOfCustomers);
+ sinon.assert.called(find);
+ });
+
+ it('returns empty list if no customers exist', async () => {
+ const find = customerRepo.stubs.find;
+ const expected: Customer[] = [];
+ find.resolves(expected);
+ expect(await controller.find()).to.eql(expected);
+ sinon.assert.called(find);
+ });
+ });
+
+ describe('replaceCustomer', () => {
+ it('successfully replaces existing items', async () => {
+ const replaceById = customerRepo.stubs.replaceById;
+ replaceById.resolves();
+ await controller.replaceById(
+ aCustomerWithId.id as number,
+ aChangedCustomer,
+ );
+ sinon.assert.calledWith(
+ replaceById,
+ aCustomerWithId.id,
+ aChangedCustomer,
+ );
+ });
+ });
+
+ describe('updateCustomer', () => {
+ it('successfully updates existing items', async () => {
+ const updateById = customerRepo.stubs.updateById;
+ updateById.resolves();
+ await controller.updateById(
+ aCustomerWithId.id as number,
+ aChangedCustomer,
+ );
+ sinon.assert.calledWith(updateById, aCustomerWithId.id, aChangedCustomer);
+ });
+ });
+
+ describe('deleteCustomer', () => {
+ it('successfully deletes existing items', async () => {
+ const deleteById = customerRepo.stubs.deleteById;
+ deleteById.resolves();
+ await controller.deleteById(aCustomerWithId.id as number);
+ sinon.assert.calledWith(deleteById, aCustomerWithId.id);
+ });
+ });
+
+ function resetRepositories() {
+ customerRepo = createStubInstance(CustomerRepository);
+ aCustomer = givenCustomer();
+ aCustomerWithId = givenCustomer({
+ id: 1,
+ });
+ aListOfCustomers = [
+ aCustomerWithId,
+ givenCustomer({
+ id: 2,
+ firstName: 'Dave',
+ lastName: 'Brubeck',
+ }),
+ ] as Customer[];
+ aChangedCustomer = givenCustomer({
+ id: aCustomerWithId.id,
+ firstName: 'Tim',
+ lastName: 'Benton',
+ });
+
+ controller = new CustomerController(customerRepo);
+ }
+});
diff --git a/examples/references-many/src/application.ts b/examples/references-many/src/application.ts
new file mode 100644
index 000000000000..5a546f6fc8a9
--- /dev/null
+++ b/examples/references-many/src/application.ts
@@ -0,0 +1,49 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {BootMixin} from '@loopback/boot';
+import {ApplicationConfig} from '@loopback/core';
+import {RepositoryMixin} from '@loopback/repository';
+import {RestApplication} from '@loopback/rest';
+import {
+ RestExplorerBindings,
+ RestExplorerComponent,
+} from '@loopback/rest-explorer';
+import {ServiceMixin} from '@loopback/service-proxy';
+import path from 'path';
+import {MySequence} from './sequence';
+
+export {ApplicationConfig};
+
+export class ReferencesManyApplication extends BootMixin(
+ ServiceMixin(RepositoryMixin(RestApplication)),
+) {
+ constructor(options: ApplicationConfig = {}) {
+ super(options);
+
+ // Set up the custom sequence
+ this.sequence(MySequence);
+
+ // Set up default home page
+ this.static('/', path.join(__dirname, '../public'));
+
+ // Customize @loopback/rest-explorer configuration here
+ this.configure(RestExplorerBindings.COMPONENT).to({
+ path: '/explorer',
+ });
+ this.component(RestExplorerComponent);
+
+ this.projectRoot = __dirname;
+ // Customize @loopback/boot Booter Conventions here
+ this.bootOptions = {
+ controllers: {
+ // Customize ControllerBooter Conventions here
+ dirs: ['controllers'],
+ extensions: ['.controller.js'],
+ nested: true,
+ },
+ };
+ }
+}
diff --git a/examples/references-many/src/controllers/account.controller.ts b/examples/references-many/src/controllers/account.controller.ts
new file mode 100644
index 000000000000..14fc6f5d1c78
--- /dev/null
+++ b/examples/references-many/src/controllers/account.controller.ts
@@ -0,0 +1,177 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {
+ Count,
+ CountSchema,
+ Filter,
+ FilterExcludingWhere,
+ repository,
+ Where,
+} from '@loopback/repository';
+import {
+ del,
+ get,
+ getModelSchemaRef,
+ param,
+ patch,
+ post,
+ put,
+ requestBody,
+} from '@loopback/rest';
+import {Account} from '../models';
+import {AccountRepository} from '../repositories';
+
+export class AccountController {
+ constructor(
+ @repository(AccountRepository)
+ public accountRepository: AccountRepository,
+ ) {}
+
+ @post('/accounts', {
+ responses: {
+ '200': {
+ description: 'Account model instance',
+ content: {'application/json': {schema: getModelSchemaRef(Account)}},
+ },
+ },
+ })
+ async create(
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Account, {
+ title: 'NewAccount',
+ exclude: ['id'],
+ }),
+ },
+ },
+ })
+ account: Omit,
+ ): Promise {
+ return this.accountRepository.create(account);
+ }
+
+ @get('/accounts/count', {
+ responses: {
+ '200': {
+ description: 'Account model count',
+ content: {'application/json': {schema: CountSchema}},
+ },
+ },
+ })
+ async count(@param.where(Account) where?: Where): Promise {
+ return this.accountRepository.count(where);
+ }
+
+ @get('/accounts', {
+ responses: {
+ '200': {
+ description: 'Array of Account model instances',
+ content: {
+ 'application/json': {
+ schema: {
+ type: 'array',
+ items: getModelSchemaRef(Account, {includeRelations: true}),
+ },
+ },
+ },
+ },
+ },
+ })
+ async find(
+ @param.filter(Account) filter?: Filter,
+ ): Promise {
+ return this.accountRepository.find(filter);
+ }
+
+ @patch('/accounts', {
+ responses: {
+ '200': {
+ description: 'Account PATCH success count',
+ content: {'application/json': {schema: CountSchema}},
+ },
+ },
+ })
+ async updateAll(
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Account, {partial: true}),
+ },
+ },
+ })
+ account: Account,
+ @param.where(Account) where?: Where,
+ ): Promise {
+ return this.accountRepository.updateAll(account, where);
+ }
+
+ @get('/accounts/{id}', {
+ responses: {
+ '200': {
+ description: 'Account model instance',
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Account, {includeRelations: true}),
+ },
+ },
+ },
+ },
+ })
+ async findById(
+ @param.path.number('id') id: number,
+ @param.filter(Account, {exclude: 'where'})
+ filter?: FilterExcludingWhere,
+ ): Promise {
+ return this.accountRepository.findById(id, filter);
+ }
+
+ @patch('/accounts/{id}', {
+ responses: {
+ '204': {
+ description: 'Account PATCH success',
+ },
+ },
+ })
+ async updateById(
+ @param.path.number('id') id: number,
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Account, {partial: true}),
+ },
+ },
+ })
+ account: Account,
+ ): Promise {
+ await this.accountRepository.updateById(id, account);
+ }
+
+ @del('/accounts/{id}', {
+ responses: {
+ '204': {
+ description: 'Account DELETE success',
+ },
+ },
+ })
+ async deleteById(@param.path.number('id') id: number): Promise {
+ await this.accountRepository.deleteById(id);
+ }
+
+ @put('/accounts/{id}', {
+ responses: {
+ '204': {
+ description: 'Account PUT success',
+ },
+ },
+ })
+ async replaceById(
+ @param.path.number('id') id: number,
+ @requestBody() account: Account,
+ ): Promise {
+ await this.accountRepository.replaceById(id, account);
+ }
+}
diff --git a/examples/references-many/src/controllers/customer.controller.ts b/examples/references-many/src/controllers/customer.controller.ts
new file mode 100644
index 000000000000..4d6d594a7ec2
--- /dev/null
+++ b/examples/references-many/src/controllers/customer.controller.ts
@@ -0,0 +1,177 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {
+ Count,
+ CountSchema,
+ Filter,
+ FilterExcludingWhere,
+ repository,
+ Where,
+} from '@loopback/repository';
+import {
+ del,
+ get,
+ getModelSchemaRef,
+ param,
+ patch,
+ post,
+ put,
+ requestBody,
+} from '@loopback/rest';
+import {Customer} from '../models';
+import {CustomerRepository} from '../repositories';
+
+export class CustomerController {
+ constructor(
+ @repository(CustomerRepository)
+ public customerRepository: CustomerRepository,
+ ) {}
+
+ @post('/customers', {
+ responses: {
+ '200': {
+ description: 'Customer model instance',
+ content: {'application/json': {schema: getModelSchemaRef(Customer)}},
+ },
+ },
+ })
+ async create(
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Customer, {
+ title: 'NewCustomer',
+ exclude: ['id'],
+ }),
+ },
+ },
+ })
+ customer: Omit,
+ ): Promise {
+ return this.customerRepository.create(customer);
+ }
+
+ @get('/customers/count', {
+ responses: {
+ '200': {
+ description: 'Customer model count',
+ content: {'application/json': {schema: CountSchema}},
+ },
+ },
+ })
+ async count(@param.where(Customer) where?: Where): Promise {
+ return this.customerRepository.count(where);
+ }
+
+ @get('/customers', {
+ responses: {
+ '200': {
+ description: 'Array of Customer model instances',
+ content: {
+ 'application/json': {
+ schema: {
+ type: 'array',
+ items: getModelSchemaRef(Customer, {includeRelations: true}),
+ },
+ },
+ },
+ },
+ },
+ })
+ async find(
+ @param.filter(Customer) filter?: Filter,
+ ): Promise {
+ return this.customerRepository.find(filter);
+ }
+
+ @patch('/customers', {
+ responses: {
+ '200': {
+ description: 'Customer PATCH success count',
+ content: {'application/json': {schema: CountSchema}},
+ },
+ },
+ })
+ async updateAll(
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Customer, {partial: true}),
+ },
+ },
+ })
+ customer: Customer,
+ @param.where(Customer) where?: Where,
+ ): Promise {
+ return this.customerRepository.updateAll(customer, where);
+ }
+
+ @get('/customers/{id}', {
+ responses: {
+ '200': {
+ description: 'Customer model instance',
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Customer, {includeRelations: true}),
+ },
+ },
+ },
+ },
+ })
+ async findById(
+ @param.path.number('id') id: number,
+ @param.filter(Customer, {exclude: 'where'})
+ filter?: FilterExcludingWhere,
+ ): Promise {
+ return this.customerRepository.findById(id, filter);
+ }
+
+ @patch('/customers/{id}', {
+ responses: {
+ '204': {
+ description: 'Customer PATCH success',
+ },
+ },
+ })
+ async updateById(
+ @param.path.number('id') id: number,
+ @requestBody({
+ content: {
+ 'application/json': {
+ schema: getModelSchemaRef(Customer, {partial: true}),
+ },
+ },
+ })
+ customer: Customer,
+ ): Promise {
+ await this.customerRepository.updateById(id, customer);
+ }
+
+ @del('/customers/{id}', {
+ responses: {
+ '204': {
+ description: 'Customer DELETE success',
+ },
+ },
+ })
+ async deleteById(@param.path.number('id') id: number): Promise {
+ await this.customerRepository.deleteById(id);
+ }
+
+ @put('/customers/{id}', {
+ responses: {
+ '204': {
+ description: 'Customer PUT success',
+ },
+ },
+ })
+ async replaceById(
+ @param.path.number('id') id: number,
+ @requestBody() customer: Customer,
+ ): Promise {
+ await this.customerRepository.replaceById(id, customer);
+ }
+}
diff --git a/examples/references-many/src/controllers/index.ts b/examples/references-many/src/controllers/index.ts
new file mode 100644
index 000000000000..dbafe00058ae
--- /dev/null
+++ b/examples/references-many/src/controllers/index.ts
@@ -0,0 +1,7 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+export * from './customer.controller';
+export * from './account.controller';
diff --git a/examples/references-many/src/datasources/db.datasource.ts b/examples/references-many/src/datasources/db.datasource.ts
new file mode 100644
index 000000000000..3a7e823144b9
--- /dev/null
+++ b/examples/references-many/src/datasources/db.datasource.ts
@@ -0,0 +1,34 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
+import {juggler} from '@loopback/repository';
+
+const config = {
+ name: 'db',
+ connector: 'memory',
+ localStorage: '',
+ file: './data/db.json',
+};
+
+// Observe application's life cycle to disconnect the datasource when
+// application is stopped. This allows the application to be shut down
+// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
+// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
+@lifeCycleObserver('datasource')
+export class DbDataSource
+ extends juggler.DataSource
+ implements LifeCycleObserver
+{
+ static dataSourceName = 'db';
+ static readonly defaultConfig = config;
+
+ constructor(
+ @inject('datasources.config.db', {optional: true})
+ dsConfig: object = config,
+ ) {
+ super(dsConfig);
+ }
+}
diff --git a/examples/references-many/src/datasources/index.ts b/examples/references-many/src/datasources/index.ts
new file mode 100644
index 000000000000..603a98d6fe7d
--- /dev/null
+++ b/examples/references-many/src/datasources/index.ts
@@ -0,0 +1,6 @@
+// Copyright IBM Corp. 2018,2020 All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+export * from './db.datasource';
diff --git a/examples/references-many/src/index.ts b/examples/references-many/src/index.ts
new file mode 100644
index 000000000000..16cf6ad27706
--- /dev/null
+++ b/examples/references-many/src/index.ts
@@ -0,0 +1,42 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {ApplicationConfig, ReferencesManyApplication} from './application';
+
+export * from './application';
+
+export async function main(options: ApplicationConfig = {}) {
+ const app = new ReferencesManyApplication(options);
+ await app.boot();
+ await app.start();
+
+ const url = app.restServer.url;
+ console.log(`Server is running at ${url}`);
+ return app;
+}
+
+if (require.main === module) {
+ // Run the application
+ const config = {
+ rest: {
+ port: +(process.env.PORT ?? 3000),
+ host: process.env.HOST,
+ // The `gracePeriodForClose` provides a graceful close for http/https
+ // servers with keep-alive clients. The default value is `Infinity`
+ // (don't force-close). If you want to immediately destroy all sockets
+ // upon stop, set its value to `0`.
+ // See https://www.npmjs.com/package/stoppable
+ gracePeriodForClose: 5000, // 5 seconds
+ openApiSpec: {
+ // useful when used with OpenAPI-to-GraphQL to locate your application
+ setServersFromRequest: true,
+ },
+ },
+ };
+ main(config).catch(err => {
+ console.error('Cannot start the application.', err);
+ process.exit(1);
+ });
+}
diff --git a/examples/references-many/src/migrate.ts b/examples/references-many/src/migrate.ts
new file mode 100644
index 000000000000..0771b1197426
--- /dev/null
+++ b/examples/references-many/src/migrate.ts
@@ -0,0 +1,25 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {ReferencesManyApplication} from './application';
+
+export async function migrate(args: string[]) {
+ const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter';
+ console.log('Migrating schemas (%s existing schema)', existingSchema);
+
+ const app = new ReferencesManyApplication();
+ await app.boot();
+ await app.migrateSchema({existingSchema});
+
+ // Connectors usually keep a pool of opened connections,
+ // this keeps the process running even after all work is done.
+ // We need to exit explicitly.
+ process.exit(0);
+}
+
+migrate(process.argv).catch(err => {
+ console.error('Cannot migrate database schema', err);
+ process.exit(1);
+});
diff --git a/examples/references-many/src/models/account.model.ts b/examples/references-many/src/models/account.model.ts
new file mode 100644
index 000000000000..ae2f4bdb449d
--- /dev/null
+++ b/examples/references-many/src/models/account.model.ts
@@ -0,0 +1,31 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+import {Entity, model, property} from '@loopback/repository';
+
+@model()
+export class Account extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ generated: false,
+ })
+ id: number;
+
+ @property({
+ type: 'number',
+ default: 0,
+ })
+ balance: number;
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
+
+export interface AccountRelations {
+ // describe navigational properties here
+}
+
+export type AccountWithRelations = Account & AccountRelations;
diff --git a/examples/references-many/src/models/customer.model.ts b/examples/references-many/src/models/customer.model.ts
new file mode 100644
index 000000000000..1eb769ed358d
--- /dev/null
+++ b/examples/references-many/src/models/customer.model.ts
@@ -0,0 +1,40 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {Entity, model, property, referencesMany} from '@loopback/repository';
+import {Account, AccountWithRelations} from './account.model';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ generated: true,
+ })
+ id: number;
+
+ @property({
+ type: 'string',
+ })
+ firstName: string;
+
+ @property({
+ type: 'string',
+ })
+ lastName: string;
+
+ @referencesMany(() => Account)
+ accountIds?: number[];
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
+
+export interface CustomerRelations {
+ accounts?: AccountWithRelations;
+}
+
+export type CustomerWithRelations = Customer & CustomerRelations;
diff --git a/examples/references-many/src/models/index.ts b/examples/references-many/src/models/index.ts
new file mode 100644
index 000000000000..077c70e60c3b
--- /dev/null
+++ b/examples/references-many/src/models/index.ts
@@ -0,0 +1,7 @@
+// Copyright IBM Corp. 2018,2020 All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+export * from './customer.model';
+export * from './account.model';
diff --git a/examples/references-many/src/openapi-spec.ts b/examples/references-many/src/openapi-spec.ts
new file mode 100644
index 000000000000..8a8c7ce93330
--- /dev/null
+++ b/examples/references-many/src/openapi-spec.ts
@@ -0,0 +1,28 @@
+// Copyright IBM Corp. 2020. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {ApplicationConfig} from '@loopback/core';
+import {ReferencesManyApplication} from './application';
+
+/**
+ * Export the OpenAPI spec from the application
+ */
+async function exportOpenApiSpec(): Promise {
+ const config: ApplicationConfig = {
+ rest: {
+ port: +(process.env.PORT ?? 3000),
+ host: process.env.HOST ?? 'localhost',
+ },
+ };
+ const outFile = process.argv[2] ?? '';
+ const app = new ReferencesManyApplication(config);
+ await app.boot();
+ await app.exportOpenApiSpec(outFile);
+}
+
+exportOpenApiSpec().catch(err => {
+ console.error('Fail to export OpenAPI spec from the application.', err);
+ process.exit(1);
+});
diff --git a/examples/references-many/src/repositories/account.repository.ts b/examples/references-many/src/repositories/account.repository.ts
new file mode 100644
index 000000000000..fe89ca4a7ae7
--- /dev/null
+++ b/examples/references-many/src/repositories/account.repository.ts
@@ -0,0 +1,19 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {inject} from '@loopback/core';
+import {DefaultCrudRepository} from '@loopback/repository';
+import {DbDataSource} from '../datasources';
+import {Account, AccountRelations} from '../models';
+
+export class AccountRepository extends DefaultCrudRepository<
+ Account,
+ typeof Account.prototype.id,
+ AccountRelations
+> {
+ constructor(@inject('datasources.db') dataSource: DbDataSource) {
+ super(Account, dataSource);
+ }
+}
diff --git a/examples/references-many/src/repositories/customer.repository.ts b/examples/references-many/src/repositories/customer.repository.ts
new file mode 100644
index 000000000000..810cea55d5a7
--- /dev/null
+++ b/examples/references-many/src/repositories/customer.repository.ts
@@ -0,0 +1,42 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {Getter, inject} from '@loopback/core';
+import {
+ DefaultCrudRepository,
+ ReferencesManyAccessor,
+ repository,
+} from '@loopback/repository';
+import {DbDataSource} from '../datasources';
+import {Account, Customer, CustomerRelations} from '../models';
+import {AccountRepository} from './account.repository';
+
+export class CustomerRepository extends DefaultCrudRepository<
+ Customer,
+ typeof Customer.prototype.id,
+ CustomerRelations
+> {
+ public readonly accounts: ReferencesManyAccessor<
+ Account,
+ typeof Account.prototype.id
+ >;
+
+ constructor(
+ @inject('datasources.db') dataSource: DbDataSource,
+ @repository.getter('AccountRepository')
+ protected accountRepositoryGetter: Getter,
+ @repository.getter('CustomerRepository')
+ protected customerRepositoryGetter: Getter,
+ ) {
+ super(Customer, dataSource);
+
+ this.accounts = this.createReferencesManyAccessorFor(
+ 'accounts',
+ accountRepositoryGetter,
+ );
+
+ this.registerInclusionResolver('accounts', this.accounts.inclusionResolver);
+ }
+}
diff --git a/examples/references-many/src/repositories/index.ts b/examples/references-many/src/repositories/index.ts
new file mode 100644
index 000000000000..3c42b6f7c08e
--- /dev/null
+++ b/examples/references-many/src/repositories/index.ts
@@ -0,0 +1,7 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+export * from './customer.repository';
+export * from './account.repository';
diff --git a/examples/references-many/src/sequence.ts b/examples/references-many/src/sequence.ts
new file mode 100644
index 000000000000..3860259150e5
--- /dev/null
+++ b/examples/references-many/src/sequence.ts
@@ -0,0 +1,8 @@
+// Copyright IBM Corp. 2022. All Rights Reserved.
+// Node module: @loopback/example-references-many
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {MiddlewareSequence} from '@loopback/rest';
+
+export class MySequence extends MiddlewareSequence {}
diff --git a/examples/references-many/tsconfig.json b/examples/references-many/tsconfig.json
new file mode 100644
index 000000000000..d92a21a77f27
--- /dev/null
+++ b/examples/references-many/tsconfig.json
@@ -0,0 +1,39 @@
+{
+ "$schema": "http://json.schemastore.org/tsconfig",
+ "extends": "@loopback/build/config/tsconfig.common.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "composite": true
+ },
+ "include": [
+ "src/**/*",
+ "src/**/*.json"
+ ],
+ "references": [
+ {
+ "path": "../../packages/boot/tsconfig.json"
+ },
+ {
+ "path": "../../packages/core/tsconfig.json"
+ },
+ {
+ "path": "../../packages/http-caching-proxy/tsconfig.json"
+ },
+ {
+ "path": "../../packages/repository/tsconfig.json"
+ },
+ {
+ "path": "../../packages/rest-explorer/tsconfig.json"
+ },
+ {
+ "path": "../../packages/rest/tsconfig.json"
+ },
+ {
+ "path": "../../packages/service-proxy/tsconfig.json"
+ },
+ {
+ "path": "../../packages/testlab/tsconfig.json"
+ }
+ ]
+}
diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js
index cefd14c2a97d..7b9d1a769c36 100644
--- a/packages/cli/generators/relation/index.js
+++ b/packages/cli/generators/relation/index.js
@@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
-// no translation: HasMany, BelongsTo, HasOne
+// no translation: HasMany, BelongsTo, HasOne, ReferencesMany
'use strict';
const ArtifactGenerator = require('../../lib/artifact-generator');
@@ -18,6 +18,7 @@ const BelongsToRelationGenerator = require('./belongs-to-relation.generator');
const HasManyRelationGenerator = require('./has-many-relation.generator');
const HasManyThroughRelationGenerator = require('./has-many-through-relation.generator');
const HasOneRelationGenerator = require('./has-one-relation.generator');
+const ReferencesManyRelationGenerator = require('./references-many-relation.generator');
const g = require('../../lib/globalize');
@@ -39,7 +40,10 @@ const PROMPT_MESSAGE_RELATION_NAME = g.f('Relation name');
const PROMPT_MESSAGE_FOREIGN_KEY_NAME = g.f(
'Foreign key name to define on the target model',
);
-const PROMPT_MESSAGE_FOREIGN_KEY_NAME_BELONGSTO = g.f(
+const PROMPT_MESSAGE_FOREIGN_KEY_NAME_BELONGS_TO = g.f(
+ 'Foreign key name to define on the source model',
+);
+const PROMPT_MESSAGE_FOREIGN_KEY_NAME_REFERENCES_MANY = g.f(
'Foreign key name to define on the source model',
);
@@ -200,6 +204,13 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
this.artifactInfo.destinationModel,
);
break;
+ case relationUtils.relationType.referencesMany:
+ // this is how the referencesManyAccessor generates the default relation name
+ defaultRelationName = this.artifactInfo.foreignKeyName.replace(
+ /Ids$/,
+ 's',
+ );
+ break;
}
return defaultRelationName;
@@ -380,7 +391,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
* 4. Check is foreign key exist in destination model. If not - prompt.
* Error - if type is not the same.
*
- * For belongsTo this is getting source key not fk.
+ * For belongsTo and referencesMany this is getting source key not fk.
*/
async promptForeignKey() {
if (this.shouldExit()) return false;
@@ -525,17 +536,37 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
this.artifactInfo.foreignKeyName = this.options.foreignKeyName;
}
- this.artifactInfo.defaultForeignKeyName =
- this.artifactInfo.relationType === 'belongsTo'
- ? utils.camelCase(this.artifactInfo.destinationModel) + 'Id'
- : utils.camelCase(this.artifactInfo.sourceModel) + 'Id';
+ switch (this.artifactInfo.relationType) {
+ case 'belongsTo':
+ this.artifactInfo.defaultForeignKeyName =
+ utils.camelCase(this.artifactInfo.destinationModel) + 'Id';
+ break;
+ case 'referencesMany':
+ this.artifactInfo.defaultForeignKeyName =
+ utils.camelCase(this.artifactInfo.destinationModel) + 'Ids';
+ break;
+ default:
+ this.artifactInfo.defaultForeignKeyName =
+ utils.camelCase(this.artifactInfo.sourceModel) + 'Id';
+ break;
+ }
+
+ let msg;
+ switch (this.artifactInfo.relationType) {
+ case 'belongsTo':
+ msg = PROMPT_MESSAGE_FOREIGN_KEY_NAME_BELONGS_TO;
+ break;
+ case 'referencesMany':
+ msg = PROMPT_MESSAGE_FOREIGN_KEY_NAME_REFERENCES_MANY;
+ break;
+ default:
+ msg = PROMPT_MESSAGE_FOREIGN_KEY_NAME;
+ break;
+ }
- const msg =
- this.artifactInfo.relationType === 'belongsTo'
- ? PROMPT_MESSAGE_FOREIGN_KEY_NAME_BELONGSTO
- : PROMPT_MESSAGE_FOREIGN_KEY_NAME;
const foreignKeyModel =
- this.artifactInfo.relationType === 'belongsTo'
+ this.artifactInfo.relationType === 'belongsTo' ||
+ this.artifactInfo.relationType === 'referencesMany'
? this.artifactInfo.sourceModel
: this.artifactInfo.destinationModel;
@@ -563,10 +594,11 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
cl,
this.artifactInfo.foreignKeyName,
);
- // checks if its the case that the fk already exists in source model and decorated by @belongsTo, which should be aborted
+ // checks if it's the case that the fk already exists in source model and decorated by @belongsTo or @referencesMany, which should be aborted
if (
this.artifactInfo.doesForeignKeyExist &&
- this.artifactInfo.relationType === 'belongsTo'
+ (this.artifactInfo.relationType === 'belongsTo' ||
+ this.artifactInfo.relationType === 'referencesMany')
) {
try {
relationUtils.doesRelationExist(cl, this.artifactInfo.foreignKeyName);
@@ -637,7 +669,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
this.artifactInfo.defaultRelationName = this._getDefaultRelationName();
// for hasMany && hasOne, the source key is the same as the relation name
const msg =
- this.artifactInfo.relationType === 'belongsTo'
+ this.artifactInfo.relationType === 'belongsTo' ||
+ this.artifactInfo.relationType === 'referencesMany'
? PROMPT_MESSAGE_RELATION_NAME
: PROMPT_MESSAGE_PROPERTY_NAME;
@@ -740,6 +773,12 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
case relationUtils.relationType.hasOne:
relationGenerator = new HasOneRelationGenerator(this.args, this.opts);
break;
+ case relationUtils.relationType.referencesMany:
+ relationGenerator = new ReferencesManyRelationGenerator(
+ this.args,
+ this.opts,
+ );
+ break;
}
try {
diff --git a/packages/cli/generators/relation/references-many-relation.generator.js b/packages/cli/generators/relation/references-many-relation.generator.js
new file mode 100644
index 000000000000..bbeffae0adbe
--- /dev/null
+++ b/packages/cli/generators/relation/references-many-relation.generator.js
@@ -0,0 +1,142 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/cli
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+
+const BaseRelationGenerator = require('./base-relation.generator');
+const utils = require('../../lib/utils');
+const relationUtils = require('./utils.generator');
+
+module.exports = class ReferencesManyRelationGenerator extends (
+ BaseRelationGenerator
+) {
+ constructor(args, opts) {
+ super(args, opts);
+ }
+
+ async generateControllers(options) {
+ // no controllers
+ }
+
+ async generateModels(options) {
+ // for repo to generate relation name
+ this.artifactInfo.relationName = options.relationName;
+ const modelDir = this.artifactInfo.modelDir;
+ const sourceModel = options.sourceModel;
+
+ const targetModel = options.destinationModel;
+ const relationType = options.relationType;
+ const relationName = options.relationName;
+ const defaultRelationName = options.defaultRelationName;
+ const foreignKeyName = options.foreignKeyName;
+ const fkType = options.destinationModelPrimaryKeyType;
+
+ const project = new relationUtils.AstLoopBackProject();
+ const sourceFile = relationUtils.addFileToProject(
+ project,
+ modelDir,
+ sourceModel,
+ );
+ const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel);
+ // this checks if the foreign key already exists, so the 2nd param should be foreignKeyName
+ relationUtils.doesRelationExist(sourceClass, foreignKeyName);
+
+ const modelProperty = this.getReferencesMany(
+ targetModel,
+ relationName,
+ defaultRelationName,
+ foreignKeyName,
+ fkType,
+ );
+
+ relationUtils.addProperty(sourceClass, modelProperty);
+ const imports = relationUtils.getRequiredImports(targetModel, relationType);
+ relationUtils.addRequiredImports(sourceFile, imports);
+
+ sourceClass.formatText();
+ await sourceFile.save();
+ }
+
+ getReferencesMany(
+ className,
+ relationName,
+ defaultRelationName,
+ foreignKeyName,
+ fkType,
+ ) {
+ // checks if relation name is customized
+ let relationDecorator = [
+ {
+ name: 'referencesMany',
+ arguments: [`() => ${className}`],
+ },
+ ];
+ // already checked if the relation name is the same as the source key before
+ if (defaultRelationName !== relationName) {
+ relationDecorator = [
+ {
+ name: 'referencesMany',
+ arguments: [`() => ${className}, {name: '${relationName}'}`],
+ },
+ ];
+ }
+ return {
+ decorators: relationDecorator,
+ name: foreignKeyName,
+ type: fkType + '[]',
+ };
+ }
+
+ _getRepositoryRequiredImports(dstModelClassName, dstRepositoryClassName) {
+ const importsArray = super._getRepositoryRequiredImports(
+ dstModelClassName,
+ dstRepositoryClassName,
+ );
+ importsArray.push({
+ name: 'ReferencesManyAccessor',
+ module: '@loopback/repository',
+ });
+ return importsArray;
+ }
+
+ _getRepositoryRelationPropertyName() {
+ return this.artifactInfo.relationName;
+ }
+
+ _initializeProperties(options) {
+ super._initializeProperties(options);
+ this.artifactInfo.dstModelPrimaryKey = options.destinationModelPrimaryKey;
+ }
+
+ _getRepositoryRelationPropertyType() {
+ return (
+ `ReferencesManyAccessor<` +
+ `${utils.toClassName(this.artifactInfo.dstModelClass)}` +
+ `, typeof ${utils.toClassName(this.artifactInfo.srcModelClass)}` +
+ `.prototype.${this.artifactInfo.srcModelPrimaryKey}>`
+ );
+ }
+
+ _addCreatorToRepositoryConstructor(classConstructor) {
+ const relationName = this.artifactInfo.relationName;
+ const statement =
+ `this.${relationName} = ` +
+ `this.createReferencesManyAccessorFor('` +
+ `${relationName}',` +
+ ` ${utils.camelCase(this.artifactInfo.dstRepositoryClassName)}` +
+ `Getter,);`;
+ classConstructor.insertStatements(1, statement);
+ }
+
+ _registerInclusionResolverForRelation(classConstructor, options) {
+ const relationName = this.artifactInfo.relationName;
+ if (options.registerInclusionResolver) {
+ const statement =
+ `this.registerInclusionResolver(` +
+ `'${relationName}', this.${relationName}.inclusionResolver);`;
+ classConstructor.insertStatements(2, statement);
+ }
+ }
+};
diff --git a/packages/cli/generators/relation/utils.generator.js b/packages/cli/generators/relation/utils.generator.js
index 8152bf38a823..91a5ec1bdeb6 100644
--- a/packages/cli/generators/relation/utils.generator.js
+++ b/packages/cli/generators/relation/utils.generator.js
@@ -15,6 +15,7 @@ exports.relationType = {
hasMany: 'hasMany',
hasManyThrough: 'hasManyThrough',
hasOne: 'hasOne',
+ referencesMany: 'referencesMany',
};
class AstLoopBackProject extends ast.Project {
diff --git a/packages/cli/snapshots/integration/generators/relation.references-many.integration.snapshots.js b/packages/cli/snapshots/integration/generators/relation.references-many.integration.snapshots.js
new file mode 100644
index 000000000000..3e7f987dc1ad
--- /dev/null
+++ b/packages/cli/snapshots/integration/generators/relation.references-many.integration.snapshots.js
@@ -0,0 +1,141 @@
+// IMPORTANT
+// This snapshot file is auto-generated, but designed for humans.
+// It should be checked into source control and tracked carefully.
+// Re-generate by setting UPDATE_SNAPSHOTS=1 and running tests.
+// Make sure to inspect the changes in the snapshots below.
+// Do not ignore changes!
+
+'use strict';
+
+exports[`lb4 relation checks generated source class repository answers {"relationType":"referencesMany","sourceModel":"Customer","destinationModel":"Account","relationName":"custom_name","registerInclusionResolver":false} generates Customer repository file with different inputs 1`] = `
+import {inject, Getter} from '@loopback/core';
+import {DefaultCrudRepository, repository, ReferencesManyAccessor} from '@loopback/repository';
+import {DbDataSource} from '../datasources';
+import {Customer, Account} from '../models';
+import {AccountRepository} from './account.repository';
+
+export class CustomerRepository extends DefaultCrudRepository<
+ Customer,
+ typeof Customer.prototype.id
+> {
+
+ public readonly custom_name: ReferencesManyAccessor;
+
+ constructor(@inject('datasources.db') dataSource: DbDataSource, @repository.getter('AccountRepository') protected accountRepositoryGetter: Getter,) {
+ super(Customer, dataSource);
+ this.custom_name = this.createReferencesManyAccessorFor('custom_name', accountRepositoryGetter,);
+ }
+}
+
+`;
+
+
+exports[`lb4 relation checks generated source class repository answers {"relationType":"referencesMany","sourceModel":"Customer","destinationModel":"Account"} generates Customer repository file with different inputs 1`] = `
+import {inject, Getter} from '@loopback/core';
+import {DefaultCrudRepository, repository, ReferencesManyAccessor} from '@loopback/repository';
+import {DbDataSource} from '../datasources';
+import {Customer, Account} from '../models';
+import {AccountRepository} from './account.repository';
+
+export class CustomerRepository extends DefaultCrudRepository<
+ Customer,
+ typeof Customer.prototype.id
+> {
+
+ public readonly accounts: ReferencesManyAccessor;
+
+ constructor(@inject('datasources.db') dataSource: DbDataSource, @repository.getter('AccountRepository') protected accountRepositoryGetter: Getter,) {
+ super(Customer, dataSource);
+ this.accounts = this.createReferencesManyAccessorFor('accounts', accountRepositoryGetter,);
+ this.registerInclusionResolver('accounts', this.accounts.inclusionResolver);
+ }
+}
+
+`;
+
+
+exports[`lb4 relation generates model relation for existing property name verifies that a preexisting property will be overwritten 1`] = `
+import {Entity, model, property, referencesMany} from '@loopback/repository';
+import {Account} from './account.model';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ default: 0,
+ })
+ id?: number;
+
+ @property({
+ type: 'string',
+ })
+ name?: string;
+
+ @referencesMany(() => Account)
+ accountIds: number[];
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
+
+`;
+
+
+exports[`lb4 relation generates model relation with custom relation name answers {"relationType":"referencesMany","sourceModel":"Customer","destinationModel":"Account","foreignKeyName":"accountIds","relationName":"my_accounts"} relation name should be my_accounts 1`] = `
+import {Entity, model, property, referencesMany} from '@loopback/repository';
+import {Account} from './account.model';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ default: 0,
+ })
+ id?: number;
+
+ @property({
+ type: 'string',
+ })
+ name?: string;
+
+ @referencesMany(() => Account, {name: 'my_accounts'})
+ accountIds: number[];
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
+
+`;
+
+
+exports[`lb4 relation generates model relation with default values answers {"relationType":"referencesMany","sourceModel":"Customer","destinationModel":"Account"} has correct default imports 1`] = `
+import {Entity, model, property, referencesMany} from '@loopback/repository';
+import {Account} from './account.model';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ default: 0,
+ })
+ id?: number;
+
+ @property({
+ type: 'string',
+ })
+ name?: string;
+
+ @referencesMany(() => Account)
+ accountIds: number[];
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
+
+`;
diff --git a/packages/cli/test/fixtures/relation/controllers/account.controller.ts b/packages/cli/test/fixtures/relation/controllers/account.controller.ts
new file mode 100644
index 000000000000..0f7bfb2b577a
--- /dev/null
+++ b/packages/cli/test/fixtures/relation/controllers/account.controller.ts
@@ -0,0 +1 @@
+export class AccountController {}
diff --git a/packages/cli/test/fixtures/relation/index.js b/packages/cli/test/fixtures/relation/index.js
index df0ce6c85da2..c54b96b9eb0d 100644
--- a/packages/cli/test/fixtures/relation/index.js
+++ b/packages/cli/test/fixtures/relation/index.js
@@ -12,6 +12,17 @@ const fs = require('fs');
const {getSourceForDataSourceClassWithConfig} = require('../../test-utils');
const SourceEntries = {
+ AccountModel: {
+ path: MODEL_APP_PATH,
+ file: 'account.model.ts',
+ content: readSourceFile('./models/account.model.ts'),
+ },
+ AccountRepository: {
+ path: REPOSITORY_APP_PATH,
+ file: 'account.repository.ts',
+ content: readSourceFile('./repositories/account.repository.ts'),
+ },
+
CustomerModel: {
path: MODEL_APP_PATH,
file: 'customer.model.ts',
@@ -27,6 +38,11 @@ const SourceEntries = {
file: 'customer.model.ts',
content: readSourceFile('./models/customer6.model.ts'),
},
+ CustomerModelWithAccountIdsProperty: {
+ path: MODEL_APP_PATH,
+ file: 'customer.model.ts',
+ content: readSourceFile('./models/customer7.model.ts'),
+ },
CustomerRepository: {
path: REPOSITORY_APP_PATH,
file: 'customer.repository.ts',
@@ -175,6 +191,7 @@ exports.SANDBOX_FILES = [
connector: 'loopback-connector-sqlite3',
}),
},
+ SourceEntries.AccountRepository,
SourceEntries.CustomerRepository,
SourceEntries.OrderRepository,
SourceEntries.AddressRepository,
@@ -182,6 +199,7 @@ exports.SANDBOX_FILES = [
SourceEntries.PatientRepository,
SourceEntries.AppointmentRepository,
+ SourceEntries.AccountModel,
SourceEntries.CustomerModel,
SourceEntries.OrderModel,
SourceEntries.AddressModel,
diff --git a/packages/cli/test/fixtures/relation/models/account.model.ts b/packages/cli/test/fixtures/relation/models/account.model.ts
new file mode 100644
index 000000000000..02bc5df5edb2
--- /dev/null
+++ b/packages/cli/test/fixtures/relation/models/account.model.ts
@@ -0,0 +1,21 @@
+import {Entity, model, property} from '@loopback/repository';
+
+@model()
+export class Account extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ default: 0,
+ })
+ id?: number;
+
+ @property({
+ type: 'number',
+ default: 0,
+ })
+ balance?: string;
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
diff --git a/packages/cli/test/fixtures/relation/models/customer7.model.ts b/packages/cli/test/fixtures/relation/models/customer7.model.ts
new file mode 100644
index 000000000000..fe424b40c73c
--- /dev/null
+++ b/packages/cli/test/fixtures/relation/models/customer7.model.ts
@@ -0,0 +1,25 @@
+import {Entity, model, property} from '@loopback/repository';
+
+@model()
+export class Customer extends Entity {
+ @property({
+ type: 'number',
+ id: true,
+ default: 0,
+ })
+ id?: number;
+
+ @property({
+ type: 'string',
+ })
+ name?: string;
+
+ @property({
+ type: 'array',
+ })
+ accountIds: number[];
+
+ constructor(data?: Partial) {
+ super(data);
+ }
+}
diff --git a/packages/cli/test/fixtures/relation/repositories/account.repository.ts b/packages/cli/test/fixtures/relation/repositories/account.repository.ts
new file mode 100644
index 000000000000..db7f51605270
--- /dev/null
+++ b/packages/cli/test/fixtures/relation/repositories/account.repository.ts
@@ -0,0 +1,13 @@
+import {inject} from '@loopback/core';
+import {DefaultCrudRepository} from '@loopback/repository';
+import {DbDataSource} from '../datasources';
+import {Account} from '../models';
+
+export class AccountRepository extends DefaultCrudRepository<
+ Account,
+ typeof Account.prototype.id
+> {
+ constructor(@inject('datasources.db') dataSource: DbDataSource) {
+ super(Account, dataSource);
+ }
+}
diff --git a/packages/cli/test/integration/generators/relation.references-many.integration.js b/packages/cli/test/integration/generators/relation.references-many.integration.js
new file mode 100644
index 000000000000..4adb702d0639
--- /dev/null
+++ b/packages/cli/test/integration/generators/relation.references-many.integration.js
@@ -0,0 +1,236 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/cli
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+'use strict';
+
+const path = require('path');
+const assert = require('yeoman-assert');
+const {expect, TestSandbox} = require('@loopback/testlab');
+const {expectFileToMatchSnapshot} = require('../../snapshots');
+
+const generator = path.join(__dirname, '../../../generators/relation');
+const {SANDBOX_FILES, SourceEntries} = require('../../fixtures/relation');
+const testUtils = require('../../test-utils');
+
+// Test Sandbox
+const MODEL_APP_PATH = 'src/models';
+const REPOSITORY_APP_PATH = 'src/repositories';
+const sandbox = new TestSandbox(path.resolve(__dirname, '../.sandbox'));
+
+const sourceFileName = 'customer.model.ts';
+const repositoryFileName = 'customer.repository.ts';
+// speed up tests by avoiding reading docs
+const options = {
+ sourceModelPrimaryKey: 'id',
+ sourceModelPrimaryKeyType: 'number',
+ destinationModelPrimaryKey: 'id',
+ destinationModelPrimaryKeyType: 'number',
+};
+
+describe('lb4 relation', /** @this {Mocha.Suite} */ function () {
+ this.timeout(30000);
+
+ it('rejects relation when models does not exist', async () => {
+ await sandbox.reset();
+ const prompt = {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'NotExistModel',
+ };
+
+ return expect(
+ testUtils
+ .executeGenerator(generator)
+ .inDir(sandbox.path, () =>
+ testUtils.givenLBProject(sandbox.path, {
+ additionalFiles: [
+ // no model/repository files in this project
+ ],
+ }),
+ )
+ .withPrompts(prompt),
+ ).to.be.rejectedWith(/No models found/);
+ });
+
+ context('generates model relation with default values', () => {
+ const promptArray = [
+ {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'Account',
+ },
+ ];
+
+ promptArray.forEach(function (multiItemPrompt, i) {
+ describe('answers ' + JSON.stringify(multiItemPrompt), () => {
+ suite(multiItemPrompt, i);
+ });
+ });
+
+ function suite(multiItemPrompt, i) {
+ before(async function runGeneratorWithAnswers() {
+ await sandbox.reset();
+ await testUtils
+ .executeGenerator(generator)
+ .inDir(sandbox.path, () =>
+ testUtils.givenLBProject(sandbox.path, {
+ additionalFiles: SANDBOX_FILES,
+ }),
+ )
+ .withPrompts(multiItemPrompt);
+ });
+
+ it('has correct default imports', async () => {
+ const sourceFilePath = path.join(
+ sandbox.path,
+ MODEL_APP_PATH,
+ sourceFileName,
+ );
+
+ assert.file(sourceFilePath);
+ expectFileToMatchSnapshot(sourceFilePath);
+ });
+ }
+ });
+
+ context('generates model relation for existing property name', () => {
+ const promptList = [
+ {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'Account',
+ foreignKeyName: 'accountIds',
+ relationName: 'accounts',
+ },
+ ];
+
+ it('verifies that a preexisting property will be overwritten', async () => {
+ await sandbox.reset();
+
+ await testUtils
+ .executeGenerator(generator)
+ .inDir(sandbox.path, () =>
+ testUtils.givenLBProject(sandbox.path, {
+ additionalFiles: [
+ SourceEntries.AccountModel,
+ SourceEntries.CustomerModelWithAccountIdsProperty,
+ SourceEntries.CustomerRepository,
+ SourceEntries.AccountRepository,
+ ],
+ }),
+ )
+ .withOptions(options)
+ .withPrompts(promptList[0]);
+
+ const sourceFilePath = path.join(
+ sandbox.path,
+ MODEL_APP_PATH,
+ 'customer.model.ts',
+ );
+
+ assert.file(sourceFilePath);
+ expectFileToMatchSnapshot(sourceFilePath);
+ });
+ });
+
+ context('generates model relation with custom relation name', () => {
+ const promptArray = [
+ {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'Account',
+ foreignKeyName: 'accountIds',
+ relationName: 'my_accounts',
+ },
+ ];
+ promptArray.forEach(function (multiItemPrompt, i) {
+ describe('answers ' + JSON.stringify(multiItemPrompt), () => {
+ suite(multiItemPrompt, i);
+ });
+ });
+
+ function suite(multiItemPrompt, i) {
+ before(async function runGeneratorWithAnswers() {
+ await sandbox.reset();
+ await testUtils
+ .executeGenerator(generator)
+ .inDir(sandbox.path, () =>
+ testUtils.givenLBProject(sandbox.path, {
+ additionalFiles: SANDBOX_FILES,
+ }),
+ )
+ .withOptions(options)
+ .withPrompts(multiItemPrompt);
+ });
+
+ it('relation name should be my_accounts', async () => {
+ const sourceFilePath = path.join(
+ sandbox.path,
+ MODEL_APP_PATH,
+ sourceFileName,
+ );
+
+ assert.file(sourceFilePath);
+ expectFileToMatchSnapshot(sourceFilePath);
+ });
+ }
+ });
+
+ context('checks generated source class repository', () => {
+ const promptArray = [
+ {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'Account',
+ },
+ {
+ relationType: 'referencesMany',
+ sourceModel: 'Customer',
+ destinationModel: 'Account',
+ relationName: 'custom_name',
+ registerInclusionResolver: false,
+ },
+ ];
+
+ const sourceClassnames = ['Customer', 'Customer'];
+
+ promptArray.forEach(function (multiItemPrompt, i) {
+ describe('answers ' + JSON.stringify(multiItemPrompt), () => {
+ suite(multiItemPrompt, i);
+ });
+ });
+
+ function suite(multiItemPrompt, i) {
+ before(async function runGeneratorWithAnswers() {
+ await sandbox.reset();
+ await testUtils
+ .executeGenerator(generator)
+ .inDir(sandbox.path, () =>
+ testUtils.givenLBProject(sandbox.path, {
+ additionalFiles: SANDBOX_FILES,
+ }),
+ )
+ .withOptions(options)
+ .withPrompts(multiItemPrompt);
+ });
+
+ it(
+ 'generates ' +
+ sourceClassnames[i] +
+ ' repository file with different inputs',
+ async () => {
+ const sourceFilePath = path.join(
+ sandbox.path,
+ REPOSITORY_APP_PATH,
+ repositoryFileName,
+ );
+
+ assert.file(sourceFilePath);
+ expectFileToMatchSnapshot(sourceFilePath);
+ },
+ );
+ }
+ });
+});
diff --git a/packages/repository/src/__tests__/unit/decorator/model-and-relation.decorator.unit.ts b/packages/repository/src/__tests__/unit/decorator/model-and-relation.decorator.unit.ts
index ae00663fe862..efede998a811 100644
--- a/packages/repository/src/__tests__/unit/decorator/model-and-relation.decorator.unit.ts
+++ b/packages/repository/src/__tests__/unit/decorator/model-and-relation.decorator.unit.ts
@@ -58,8 +58,13 @@ describe('model decorator', () => {
@model()
class Account extends Entity {
+ @property()
id: string;
+
+ @property()
type: string;
+
+ @property()
balance: number;
}
@@ -116,8 +121,8 @@ describe('model decorator', () => {
@embedsMany()
phones: Phone[];
- @referencesMany()
- accounts: Account[];
+ @referencesMany(() => Account)
+ accountIds: string[];
@referencesOne()
profile: Profile;
@@ -277,13 +282,19 @@ describe('model decorator', () => {
it('adds referencesMany metadata', () => {
const meta =
- MetadataInspector.getAllPropertyMetadata(
+ MetadataInspector.getAllPropertyMetadata(
RELATIONS_KEY,
Customer.prototype,
) ?? /* istanbul ignore next */ {};
- expect(meta.accounts).to.eql({
+ const relationDef = meta.accountIds;
+ expect(relationDef).to.containEql({
type: RelationType.referencesMany,
+ name: 'accounts',
+ target: () => Account,
+ keyFrom: 'accountIds',
});
+ expect(relationDef.source).to.be.exactly(Customer);
+ expect(relationDef.target()).to.be.exactly(Account);
});
it('adds referencesOne metadata', () => {
diff --git a/packages/repository/src/model.ts b/packages/repository/src/model.ts
index e5b85b1e28dd..b65e8aad3530 100644
--- a/packages/repository/src/model.ts
+++ b/packages/repository/src/model.ts
@@ -8,6 +8,7 @@ import {
BelongsToDefinition,
HasManyDefinition,
HasOneDefinition,
+ ReferencesManyDefinition,
JsonSchema,
RelationMetadata,
RelationType,
@@ -240,6 +241,24 @@ export class ModelDefinition {
return this.addRelation(meta);
}
+ /**
+ * Define a new referencesMany relation.
+ * @param name - The name of the referencesMany relation.
+ * @param definition - The definition of the referencesMany relation.
+ */
+ referencesMany(
+ name: string,
+ definition: Omit,
+ ): this {
+ const meta: ReferencesManyDefinition = {
+ ...definition,
+ name,
+ type: RelationType.referencesMany,
+ targetsMany: true,
+ };
+ return this.addRelation(meta);
+ }
+
/**
* Get an array of names of ID properties, which are specified in
* the model settings or properties with `id` attribute.
diff --git a/packages/repository/src/relations/index.ts b/packages/repository/src/relations/index.ts
index 401af94f0eb9..5cd590b1131c 100644
--- a/packages/repository/src/relations/index.ts
+++ b/packages/repository/src/relations/index.ts
@@ -6,6 +6,7 @@
export * from './belongs-to';
export * from './has-many';
export * from './has-one';
+export * from './references-many';
export * from './relation.decorator';
export * from './relation.helpers';
export * from './relation.types';
diff --git a/packages/repository/src/relations/references-many/index.ts b/packages/repository/src/relations/references-many/index.ts
new file mode 100644
index 000000000000..51ee5fc0e12d
--- /dev/null
+++ b/packages/repository/src/relations/references-many/index.ts
@@ -0,0 +1,9 @@
+// Copyright IBM Corp. 2018,2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+export * from './references-many.decorator';
+export * from './references-many.repository';
+export * from './references-many.accessor';
+export * from './references-many.inclusion-resolver';
diff --git a/packages/repository/src/relations/references-many/references-many.accessor.ts b/packages/repository/src/relations/references-many/references-many.accessor.ts
new file mode 100644
index 000000000000..2f377ba535ba
--- /dev/null
+++ b/packages/repository/src/relations/references-many/references-many.accessor.ts
@@ -0,0 +1,76 @@
+// Copyright IBM Corp. 2018,2019. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import debugFactory from 'debug';
+import {DataObject} from '../../common-types';
+import {Entity} from '../../model';
+import {EntityCrudRepository} from '../../repositories/repository';
+import {
+ ReferencesManyDefinition,
+ Getter,
+ InclusionResolver,
+} from '../relation.types';
+import {resolveReferencesManyMetadata} from './references-many.helpers';
+import {createReferencesManyInclusionResolver} from './references-many.inclusion-resolver';
+import {DefaultReferencesManyRepository} from './references-many.repository';
+
+const debug = debugFactory(
+ 'loopback:repository:relations:references-many:accessor',
+);
+
+export interface ReferencesManyAccessor {
+ /**
+ * Invoke the function to obtain HasManyRepository.
+ */
+ (sourceId: SourceId): Promise;
+
+ /**
+ * Use `resolver` property to obtain an InclusionResolver for this relation.
+ */
+ inclusionResolver: InclusionResolver;
+}
+
+/**
+ * Enforces a ReferencesMany constraint on a repository
+ */
+export function createReferencesManyAccessor<
+ Target extends Entity,
+ TargetIds,
+ Source extends Entity,
+ SourceId,
+>(
+ referencesManyMetadata: ReferencesManyDefinition,
+ targetRepoGetter: Getter>,
+ sourceRepository: EntityCrudRepository,
+): ReferencesManyAccessor {
+ const meta = resolveReferencesManyMetadata(referencesManyMetadata);
+ debug('Resolved ReferencesMany relation metadata: %o', meta);
+ const result: ReferencesManyAccessor =
+ async function getTargetInstancesOfReferencesMany(sourceId: SourceId) {
+ const foreignKey = meta.keyFrom;
+ const primaryKey = meta.keyTo;
+ const sourceModel = await sourceRepository.findById(sourceId);
+ const foreignKeyValue = sourceModel[foreignKey as keyof Source];
+ // workaround to check referential integrity.
+ // should be removed once the memory connector ref integrity is done
+ // GH issue: https://github.com/loopbackio/loopback-next/issues/2333
+ if (!foreignKeyValue) {
+ return undefined as unknown as Target;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const constraint: any = {[primaryKey]: foreignKeyValue};
+ const constrainedRepo = new DefaultReferencesManyRepository(
+ targetRepoGetter,
+ constraint as DataObject,
+ );
+ return constrainedRepo.get();
+ };
+
+ result.inclusionResolver = createReferencesManyInclusionResolver(
+ meta,
+ targetRepoGetter,
+ );
+ return result;
+}
diff --git a/packages/repository/src/relations/references-many/references-many.decorator.ts b/packages/repository/src/relations/references-many/references-many.decorator.ts
new file mode 100644
index 000000000000..ecb066cbf023
--- /dev/null
+++ b/packages/repository/src/relations/references-many/references-many.decorator.ts
@@ -0,0 +1,100 @@
+// Copyright IBM Corp. 2018,2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {DecoratorFactory, MetadataInspector} from '@loopback/core';
+import {property} from '../../decorators';
+import {relation} from '../relation.decorator';
+import {Entity, EntityResolver, PropertyDefinition} from '../../model';
+import {ReferencesManyDefinition, RelationType} from '../relation.types';
+
+/**
+ * Decorator for referencesMany
+ * @param targetResolver - A resolver function that returns the target model for
+ * a referencesMany relation
+ * @param definition - Optional metadata for setting up a referencesMany relation
+ * @param propertyDefinition - Optional metadata for setting up the property
+ * @returns A property decorator
+ */
+export function referencesMany(
+ targetResolver: EntityResolver,
+ definition?: Partial,
+ propertyDefinition?: Partial,
+) {
+ return function (decoratedTarget: Entity, decoratedKey: string) {
+ const propType =
+ MetadataInspector.getDesignTypeForProperty(
+ decoratedTarget,
+ decoratedKey,
+ ) ?? propertyDefinition?.type;
+
+ if (!propType) {
+ const fullPropName = DecoratorFactory.getTargetName(
+ decoratedTarget,
+ decoratedKey,
+ );
+ throw new Error(
+ `Cannot infer type of model property ${fullPropName} because ` +
+ 'TypeScript compiler option `emitDecoratorMetadata` is not set. ' +
+ 'Please enable `emitDecoratorMetadata` or use the third argument of ' +
+ '`@referencesMany` decorator to specify the property type explicitly.',
+ );
+ }
+
+ const sourceKeyType = MetadataInspector.getDesignTypeForProperty(
+ targetResolver().prototype,
+ definition?.keyTo ?? 'id',
+ );
+
+ if (!sourceKeyType) {
+ const fullPropName = DecoratorFactory.getTargetName(
+ targetResolver().prototype,
+ definition?.keyTo ?? 'id',
+ );
+ throw new Error(
+ `Cannot infer type of model property ${fullPropName} because ` +
+ 'TypeScript compiler option `emitDecoratorMetadata` is not set. ' +
+ 'Please enable `emitDecoratorMetadata` or use the second argument of ' +
+ '`@referencesMany` decorator to specify the property type explicitly.',
+ );
+ }
+
+ const propMeta: PropertyDefinition = Object.assign(
+ {},
+ // properties provided by the caller
+ propertyDefinition,
+ // properties enforced by the decorator
+ {
+ type: propType,
+ itemType: sourceKeyType,
+ // TODO(bajtos) Make the foreign key required once our REST API layer
+ // allows controller methods to exclude required properties
+ // required: true,
+ },
+ );
+ property(propMeta)(decoratedTarget, decoratedKey);
+
+ // @referencesMany() is typically decorating the foreign key property,
+ // e.g. customerIds. We need to strip the trailing "Ids" suffix from the name.
+ const relationName = decoratedKey.replace(/Ids$/, 's');
+
+ const meta: ReferencesManyDefinition = Object.assign(
+ // default values, can be customized by the caller
+ {
+ keyFrom: decoratedKey,
+ name: relationName,
+ },
+ // properties provided by the caller
+ definition,
+ // properties enforced by the decorator
+ {
+ type: RelationType.referencesMany,
+ targetsMany: true,
+ source: decoratedTarget.constructor,
+ target: targetResolver,
+ },
+ );
+ relation(meta)(decoratedTarget, decoratedKey);
+ };
+}
diff --git a/packages/repository/src/relations/references-many/references-many.helpers.ts b/packages/repository/src/relations/references-many/references-many.helpers.ts
new file mode 100644
index 000000000000..2e9257c9f853
--- /dev/null
+++ b/packages/repository/src/relations/references-many/references-many.helpers.ts
@@ -0,0 +1,82 @@
+// Copyright IBM Corp. 2019. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import debugFactory from 'debug';
+import {camelCase} from 'lodash';
+import {InvalidRelationError} from '../../errors';
+import {isTypeResolver} from '../../type-resolver';
+import {ReferencesManyDefinition, RelationType} from '../relation.types';
+
+const debug = debugFactory(
+ 'loopback:repository:relations:references-many:helpers',
+);
+
+/**
+ * Relation definition with optional metadata (e.g. `keyTo`) filled in.
+ * @internal
+ */
+export type ReferencesManyResolvedDefinition = ReferencesManyDefinition & {
+ keyFrom: string;
+ keyTo: string;
+};
+
+/**
+ * Resolves given referencesMany metadata if target is specified to be a resolver.
+ * Mainly used to infer what the `keyTo` property should be from the target's
+ * property id metadata
+ * @param relationMeta - referencesMany metadata to resolve
+ * @internal
+ */
+export function resolveReferencesManyMetadata(
+ relationMeta: ReferencesManyDefinition,
+) {
+ if ((relationMeta.type as RelationType) !== RelationType.referencesMany) {
+ const reason = 'relation type must be ReferencesMany';
+ throw new InvalidRelationError(reason, relationMeta);
+ }
+
+ if (!isTypeResolver(relationMeta.target)) {
+ const reason = 'target must be a type resolver';
+ throw new InvalidRelationError(reason, relationMeta);
+ }
+
+ const sourceModel = relationMeta.source;
+ if (!sourceModel || !sourceModel.modelName) {
+ const reason = 'source model must be defined';
+ throw new InvalidRelationError(reason, relationMeta);
+ }
+
+ const targetModel = relationMeta.target();
+ const targetName = targetModel.modelName;
+ debug('Resolved model %s from given metadata: %o', targetName, targetModel);
+
+ let keyFrom;
+ if (
+ relationMeta.keyFrom &&
+ relationMeta.source.definition.properties[relationMeta.keyFrom]
+ ) {
+ keyFrom = relationMeta.keyFrom;
+ } else {
+ keyFrom = camelCase(targetName + '_ids');
+ }
+
+ const targetProperties = targetModel.definition.properties;
+ debug('relation metadata from %o: %o', targetName, targetProperties);
+
+ if (relationMeta.keyTo && targetProperties[relationMeta.keyTo]) {
+ // The explicit cast is needed because of a limitation of type inference
+ return Object.assign(relationMeta, {
+ keyFrom,
+ }) as ReferencesManyResolvedDefinition;
+ }
+
+ const targetPrimaryKey = targetModel.definition.idProperties()[0];
+ if (!targetPrimaryKey) {
+ const reason = `${targetName} does not have any primary key (id property)`;
+ throw new InvalidRelationError(reason, relationMeta);
+ }
+
+ return Object.assign(relationMeta, {keyFrom, keyTo: targetPrimaryKey});
+}
diff --git a/packages/repository/src/relations/references-many/references-many.inclusion-resolver.ts b/packages/repository/src/relations/references-many/references-many.inclusion-resolver.ts
new file mode 100644
index 000000000000..55ed090cb36b
--- /dev/null
+++ b/packages/repository/src/relations/references-many/references-many.inclusion-resolver.ts
@@ -0,0 +1,80 @@
+// Copyright IBM Corp. 2019,2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {Filter, InclusionFilter} from '@loopback/filter';
+import {AnyObject, Options} from '../../common-types';
+import {Entity} from '../../model';
+import {EntityCrudRepository} from '../../repositories';
+import {
+ deduplicate,
+ findByForeignKeys,
+ flattenTargetsOfOneToOneRelation,
+ StringKeyOf,
+} from '../relation.helpers';
+import {
+ ReferencesManyDefinition,
+ Getter,
+ InclusionResolver,
+} from '../relation.types';
+import {resolveReferencesManyMetadata} from './references-many.helpers';
+
+/**
+ * Creates InclusionResolver for ReferencesMany relation.
+ * Notice that this function only generates the inclusionResolver.
+ * It doesn't register it for the source repository.
+ *
+ * Notice: scope field for inclusion is not supported yet
+ *
+ * @param meta - resolved ReferencesManyMetadata
+ * @param getTargetRepo - target repository i.e where related instances are
+ */
+export function createReferencesManyInclusionResolver<
+ Target extends Entity,
+ TargetIds,
+ TargetRelations extends object,
+>(
+ meta: ReferencesManyDefinition,
+ getTargetRepo: Getter<
+ EntityCrudRepository
+ >,
+): InclusionResolver {
+ const relationMeta = resolveReferencesManyMetadata(meta);
+
+ return async function fetchIncludedModels(
+ entities: Entity[],
+ inclusion: InclusionFilter,
+ options?: Options,
+ ): Promise<(Target & TargetRelations)[][]> {
+ if (!entities.length) return [];
+
+ const sourceKey = relationMeta.keyFrom;
+ const sourceMap = entities.map(e => (e as AnyObject)[sourceKey]);
+ const sourceIds = sourceMap.flat();
+ const targetKey = relationMeta.keyTo as StringKeyOf;
+ const dedupedSourceIds = deduplicate(sourceIds);
+
+ const scope =
+ typeof inclusion === 'string' ? {} : (inclusion.scope as Filter);
+
+ const targetRepo = await getTargetRepo();
+ const targetsFound = await findByForeignKeys(
+ targetRepo,
+ targetKey,
+ dedupedSourceIds.filter(e => e),
+ scope,
+ options,
+ );
+
+ return sourceMap.map(chainIds => {
+ if (!chainIds) return [];
+ const targets = flattenTargetsOfOneToOneRelation(
+ chainIds,
+ targetsFound,
+ targetKey,
+ );
+ return targets.filter((v): v is Target & TargetRelations => v != null);
+ });
+ };
+}
diff --git a/packages/repository/src/relations/references-many/references-many.repository.ts b/packages/repository/src/relations/references-many/references-many.repository.ts
new file mode 100644
index 000000000000..8579268322cf
--- /dev/null
+++ b/packages/repository/src/relations/references-many/references-many.repository.ts
@@ -0,0 +1,55 @@
+// Copyright IBM Corp. 2018,2020. All Rights Reserved.
+// Node module: @loopback/repository
+// This file is licensed under the MIT License.
+// License text available at https://opensource.org/licenses/MIT
+
+import {Getter} from '@loopback/core';
+import {DataObject, Options} from '../../common-types';
+import {EntityNotFoundError} from '../../errors';
+import {Entity} from '../../model';
+import {constrainFilter, EntityCrudRepository} from '../../repositories';
+
+/**
+ * CRUD operations for a target repository of a ReferencesMany relation
+ */
+export interface ReferencesManyRepository {
+ /**
+ * Gets the target model instance
+ * @param options
+ * @returns A promise resolved with the target object or rejected
+ * with an EntityNotFoundError when target model instance was not found.
+ */
+ get(options?: Options): Promise;
+}
+
+export class DefaultReferencesManyRepository<
+ TargetEntity extends Entity,
+ TargetIds,
+ TargetRepository extends EntityCrudRepository,
+> implements ReferencesManyRepository
+{
+ /**
+ * Constructor of DefaultReferencesManyEntityCrudRepository
+ * @param getTargetRepository - the getter of the related target model repository instance
+ * @param constraint - the key value pair representing foreign key name to constrain
+ * the target repository instance
+ */
+ constructor(
+ public getTargetRepository: Getter,
+ public constraint: DataObject,
+ ) {}
+
+ async get(options?: Options): Promise {
+ const targetRepo = await this.getTargetRepository();
+ const result = await targetRepo.find(
+ constrainFilter(undefined, this.constraint),
+ options,
+ );
+ if (!result.length) {
+ // We don't have a direct access to the foreign key value here :(
+ const id = 'constraint ' + JSON.stringify(this.constraint);
+ throw new EntityNotFoundError(targetRepo.entityClass, id);
+ }
+ return result[0];
+ }
+}
diff --git a/packages/repository/src/relations/relation.decorator.ts b/packages/repository/src/relations/relation.decorator.ts
index 43229b9f207c..83f06eb079fd 100644
--- a/packages/repository/src/relations/relation.decorator.ts
+++ b/packages/repository/src/relations/relation.decorator.ts
@@ -76,15 +76,3 @@ export function referencesOne(definition?: Object) {
decoratorName: '@referencesOne',
});
}
-
-/**
- * Decorator for referencesMany
- * @param definition
- * @returns A property decorator
- */
-export function referencesMany(definition?: Object) {
- const rel = Object.assign({type: RelationType.referencesMany}, definition);
- return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel, {
- decoratorName: '@referencesMany',
- });
-}
diff --git a/packages/repository/src/relations/relation.types.ts b/packages/repository/src/relations/relation.types.ts
index ed17b026c193..6dc26a424d8c 100644
--- a/packages/repository/src/relations/relation.types.ts
+++ b/packages/repository/src/relations/relation.types.ts
@@ -143,6 +143,21 @@ export interface HasOneDefinition extends RelationDefinitionBase {
keyFrom?: string;
}
+export interface ReferencesManyDefinition extends RelationDefinitionBase {
+ type: RelationType.referencesMany;
+ targetsMany: true;
+
+ /**
+ * keyTo: The foreign key used by the target model for this relation.
+ * keyFrom: The source key used by the source model for this relation.
+ *
+ * TODO(bajtos) Add relation description.
+ *
+ */
+ keyTo?: string;
+ keyFrom?: string;
+}
+
/**
* A union type describing all possible Relation metadata objects.
*/
@@ -150,6 +165,7 @@ export type RelationMetadata =
| HasManyDefinition
| BelongsToDefinition
| HasOneDefinition
+ | ReferencesManyDefinition
// TODO(bajtos) add other relation types and remove RelationDefinitionBase once
// all relation types are covered.
| RelationDefinitionBase;
diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts
index 51988c28f75f..8988f7cd09b3 100644
--- a/packages/repository/src/repositories/legacy-juggler-bridge.ts
+++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts
@@ -36,6 +36,7 @@ import {
createHasManyRepositoryFactory,
createHasManyThroughRepositoryFactory,
createHasOneRepositoryFactory,
+ createReferencesManyAccessor,
HasManyDefinition,
HasManyRepositoryFactory,
HasManyThroughRepositoryFactory,
@@ -43,6 +44,8 @@ import {
HasOneRepositoryFactory,
includeRelatedModels,
InclusionResolver,
+ ReferencesManyAccessor,
+ ReferencesManyDefinition,
} from '../relations';
import {IsolationLevel, Transaction} from '../transaction';
import {isTypeResolver, resolveType} from '../type-resolver';
@@ -423,6 +426,40 @@ export class DefaultCrudRepository<
);
}
+ /**
+ * @deprecated
+ * Function to create a references many accessor
+ *
+ * Use `this.createReferencesManyAccessorFor()` instead
+ *
+ * @param relationName - Name of the relation defined on the source model
+ * @param targetRepo - Target repository instance
+ */
+ protected _createReferencesManyAccessorFor(
+ relationName: string,
+ targetRepoGetter: Getter>,
+ ): ReferencesManyAccessor {
+ return this.createReferencesManyAccessorFor(relationName, targetRepoGetter);
+ }
+
+ /**
+ * Function to create a references many accessor
+ *
+ * @param relationName - Name of the relation defined on the source model
+ * @param targetRepo - Target repository instance
+ */
+ protected createReferencesManyAccessorFor(
+ relationName: string,
+ targetRepoGetter: Getter>,
+ ): ReferencesManyAccessor {
+ const meta = this.entityClass.definition.relations[relationName];
+ return createReferencesManyAccessor(
+ meta as ReferencesManyDefinition,
+ targetRepoGetter,
+ this,
+ );
+ }
+
async create(entity: DataObject, options?: Options): Promise {
// perform persist hook
const data = await this.entityToData(entity, options);
diff --git a/tsconfig.json b/tsconfig.json
index 98bca0b9cd75..31bfe4afd0b5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -67,6 +67,9 @@
{
"path": "examples/passport-login/tsconfig.json"
},
+ {
+ "path": "examples/references-many/tsconfig.json"
+ },
{
"path": "examples/rest-crud/tsconfig.json"
},