diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9f38a3b7775e..55f8229fc5c3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,3 +19,5 @@ See also #23 - [ ] Documentation in [/docs/site](../tree/master/docs/site) was updated - [ ] Affected artifact templates in `packages/cli` were updated - [ ] Affected example projects in `examples/*` were updated + +👉 [Check out how to submit a PR](https://loopback.io/doc/en/lb4/submitting_a_pr.html) 👈 diff --git a/CODEOWNERS b/CODEOWNERS index fd5d65262d3c..023236e41a30 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -25,6 +25,7 @@ packages/service-proxy/* @raymondfeng packages/testlab/* @bajtos examples/todo/* @bajtos @hacksparrow examples/express-composition/* @nabdelgadir +examples/greeter-extension/* @raymondfeng examples/hello-world/* @b-admike examples/log-extension/* @hacksparrow examples/rpc-server/* @hacksparrow diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 000000000000..8c25312d7137 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,21 @@ +## Project Maintainers + +Below is the list of maintainers for this repo (in alphabetical order). For SMEs +on particular package, please see the [CODEOWNERS file](CODEOWNERS). + +Core maintainers: + +- Biniam Admikew ([@b-admike](https://github.com/b-admike)) +- Diana Lau ([@dhmlau](https://github.com/dhmlau)) +- Dominique Emond ([@emonddr](https://github.com/emonddr)) +- Janny Hou ([@jannyhou](https://github.com/jannyhou)) +- Miroslav Bajtos ([@bajtos](https://github.com/bajtos)) +- Nora Abdelgadir ([@nabdelgadir](https://github.com/nabdelgadir)) +- Raymond Feng ([@raymondfeng](https://github.com/raymondfeng)) +- Yaapa Hage ([@hacksparrow](https://github.com/hacksparrow)) + +Community maintainers: + +- Hugo Da Roit ([@Yaty](https://github.com/Yaty)) +- Mario Estrada ([@marioestradarosa](https://github.com/marioestradarosa)) +- TN ([@thinusn](https://github.com/thinusn)) diff --git a/benchmark/CHANGELOG.md b/benchmark/CHANGELOG.md index 47815c002027..33836abd5204 100644 --- a/benchmark/CHANGELOG.md +++ b/benchmark/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.16](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@1.1.15...@loopback/benchmark@1.1.16) (2019-04-05) + +**Note:** Version bump only for package @loopback/benchmark + + + + + ## [1.1.15](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@1.1.14...@loopback/benchmark@1.1.15) (2019-03-22) **Note:** Version bump only for package @loopback/benchmark diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json index 67f6234f806b..ac27c22cf9ea 100644 --- a/benchmark/package-lock.json +++ b/benchmark/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/benchmark", - "version": "1.1.15", + "version": "1.1.16", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,24 +10,17 @@ "integrity": "sha1-DmH8ucA+BH0hxEllVMcRYperYM0=", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/caseless": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", - "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, "@types/debug": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.2.tgz", - "integrity": "sha512-jkf6UiWUjcOqdQbatbvOm54/YbCdjt3JjiAzT/9KS2XtMmOkYHdKsI5u8fulhbuTUuiqNBfa6J5GSDiwjK+zLA==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.3.tgz", + "integrity": "sha512-PQverGatRgqIhRLracrC8k/j5A6QOASVLR0wuURx6ROQZx3OQ9PnTc/5Xrznny/xaO0TwdxSgGyG90NCzk37Rg==" }, "@types/form-data": { "version": "2.2.1", @@ -35,13 +28,6 @@ "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/mocha": { @@ -53,8 +39,7 @@ "@types/node": { "version": "10.12.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", - "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==", - "dev": true + "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==" }, "@types/request": { "version": "2.48.1", @@ -65,13 +50,6 @@ "@types/form-data": "*", "@types/node": "*", "@types/tough-cookie": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/request-promise-native": { @@ -181,11 +159,11 @@ "dev": true }, "autocannon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/autocannon/-/autocannon-3.2.0.tgz", - "integrity": "sha512-hpcaDJABDFcYG1Ra9pUTtgu3e6DjOCcKZRKgLhtazI1aIGeMNPT4uX39C93luEq8WcbfMyl6z8t81FaVNCw9Rg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/autocannon/-/autocannon-3.2.1.tgz", + "integrity": "sha512-Z4Q6rOlB66iGLKWHCWEoPqfsUV1nTOGuWFw+GU4GUTzoqAnOSi3ggAq9tTGDjZyhBf+5yc5sA+swmx9/gwsaVQ==", "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "cli-table3": "^0.5.1", "color-support": "^1.1.1", "cross-argv": "^1.0.0", @@ -193,14 +171,14 @@ "hdr-histogram-js": "^1.1.4", "hdr-histogram-percentiles-obj": "^2.0.0", "http-parser-js": "^0.5.0", - "hyperid": "^2.0.0", + "hyperid": "^2.0.2", "manage-path": "^2.0.0", "minimist": "^1.2.0", "on-net-listen": "^1.1.1", "pretty-bytes": "^5.1.0", - "progress": "^2.0.0", + "progress": "^2.0.3", "reinterval": "^1.1.0", - "retimer": "^1.0.1", + "retimer": "^2.0.0", "timestring": "^5.0.1" } }, @@ -1935,11 +1913,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -1992,6 +1965,13 @@ "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "request-promise-core": { @@ -2047,9 +2027,9 @@ "dev": true }, "retimer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-1.1.0.tgz", - "integrity": "sha512-+Tjoa47XqpO+cmNObvmK6UPFmUTzQPtr4MqMS7ZJKPKYAnryCxG2FXT8/SEgPsEghQQgXFPZEdILNxJkvXtjUw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-2.0.0.tgz", + "integrity": "sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==" }, "safe-buffer": { "version": "5.1.2", diff --git a/benchmark/package.json b/benchmark/package.json index b1954c82aa64..128e6b1d5a9e 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/benchmark", - "version": "1.1.15", + "version": "1.1.16", "private": true, "description": "Benchmarks measuring performance of our framework.", "keywords": [ @@ -35,9 +35,9 @@ "!*/__tests__" ], "dependencies": { - "@loopback/example-todo": "^1.5.3", - "@loopback/openapi-spec-builder": "^1.1.2", - "@loopback/rest": "^1.9.1", + "@loopback/example-todo": "^1.5.4", + "@loopback/openapi-spec-builder": "^1.1.3", + "@loopback/rest": "^1.10.0", "@types/byline": "^4.2.31", "@types/debug": "^4.1.0", "@types/request-promise-native": "^1.0.15", @@ -49,8 +49,8 @@ "request-promise-native": "^1.0.5" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", "@types/mocha": "^5.0.0", "@types/node": "^10.11.2", "mocha": "^6.0.0", diff --git a/bin/check-package-locks.js b/bin/check-package-locks.js new file mode 100755 index 000000000000..07ed66b7b9a5 --- /dev/null +++ b/bin/check-package-locks.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-next +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * This is an internal script to verify that local monorepo dependencies + * are excluded from package-lock files. + */ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const promisify = require('util').promisify; + +const readFile = promisify(fs.readFile); + +const Project = require('@lerna/project'); + +async function checkPackageLocks() { + const project = new Project(process.cwd()); + const packages = await project.getPackages(); + const rootPath = project.rootPath; + const lockFiles = packages.map(p => + path.relative(rootPath, path.join(p.location, 'package-lock.json')), + ); + + const checkResults = await Promise.all( + lockFiles.map(async lockFile => { + return {lockFile, violations: await checkLockFile(lockFile)}; + }), + ); + const badPackages = checkResults.filter(r => r.violations.length > 0); + if (!badPackages.length) return true; + + console.error('\nInvalid package-lock entries found!\n'); + for (const {lockFile, violations} of badPackages) { + console.error(' %s', lockFile); + for (const v of violations) { + console.error(' -> %s', v); + } + } + + console.error('\nRun the following command to fix the problems:'); + console.error('\n $ npm run update-package-locks\n'); + return false; +} + +if (require.main === module) { + checkPackageLocks().then( + ok => process.exit(ok ? 0 : 1), + err => { + console.error(err); + process.exit(2); + }, + ); +} + +async function checkLockFile(lockFile) { + const data = JSON.parse(await readFile(lockFile, 'utf-8')); + return Object.keys(data.dependencies || []).filter(dep => + dep.startsWith('@loopback/'), + ); +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9ef110aef7a8..541157e37163 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.12.0](https://github.com/strongloop/loopback-next/compare/@loopback/docs@1.11.1...@loopback/docs@1.12.0) (2019-04-05) + + +### Bug Fixes + +* **docs:** move binding scope diagram to the correct folder ([0151a3d](https://github.com/strongloop/loopback-next/commit/0151a3d)) + + +### Features + +* add greeter-extension example ([9b09298](https://github.com/strongloop/loopback-next/commit/9b09298)) +* **context:** add binding.toAlias() to resolve values from another binding ([15dcd16](https://github.com/strongloop/loopback-next/commit/15dcd16)) +* **rest:** add mountExpressRouter ([be21cde](https://github.com/strongloop/loopback-next/commit/be21cde)) + + + + + ## [1.11.1](https://github.com/strongloop/loopback-next/compare/@loopback/docs@1.11.0...@loopback/docs@1.11.1) (2019-03-22) **Note:** Version bump only for package @loopback/docs diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md new file mode 100644 index 000000000000..33e08b2446d5 --- /dev/null +++ b/docs/MAINTAINING.md @@ -0,0 +1,46 @@ +# Maintaining LoopBack + +Congratulations! Since you're reading this page, you are probably already a +maintainer of this repo or close to be one. Thank you for your contributions so +far! + +## Why do I want to be a maintainer? + +- Greater influence on LoopBack's future direction +- Commit (write) rights to `loopback-next` repo +- Ability to review and land pull requests, edit/categorize/close issues + +## What are a maintainer's responsibilities? + +We ask you to follow the existing processes, see +http://loopback.io/doc/en/contrib/Governance.html. This means mostly: + +- Be nice to others +- Try to reach consensus with other maintainers before making a decision +- Always use pull requests to make code changes +- Honour our current conventions, see + http://loopback.io/doc/en/contrib/triaging-pull-requests.html and + http://loopback.io/doc/en/contrib/style-guide.html (but feel free to propose + changes to these conventions!) +- To avoid possible confusion, we don't have any specific requirements about the + amount of time you should spend on the project - just keep working on things + that you find important to you, in a pace that suits you. We may ask you to + take a look at issues and pull requests related to code you contributed + yourself. + +## More questions? + +**Q: Now that I have rights to merge pull requests, how many approvals from +other maintainers before I can merge?** + +A: If the changes are straightforward and you're confident about the changes, +please go ahead to merge it. Otherwise you can always mention +[@strongloop/loopback-maintainers](https://github.com/orgs/strongloop/teams/loopback-maintainers) +in the GitHub issues/pull requests. + +**Q: If I have questions/suggestions on the contribution process, what should I +do?** + +A: We are always open for suggestions on how to make the process for +contributors or maintainers smooth. If you have any feedback, please open a +GitHub ticket for discussion. diff --git a/docs/package-lock.json b/docs/package-lock.json index dcc91f8fd249..d4c53f6087bc 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/docs", - "version": "1.11.1", + "version": "1.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/docs/package.json b/docs/package.json index eea6df9abc46..655ba032836d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/docs", - "version": "1.11.1", + "version": "1.12.0", "description": "Documentation for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/docs", "author": "IBM Corp.", @@ -20,7 +20,7 @@ "clean": "lb-clean loopback-docs*.tgz package api-docs site/readmes" }, "devDependencies": { - "@loopback/build": "^1.4.0" + "@loopback/build": "^1.4.1" }, "publishConfig": { "access": "public" diff --git a/docs/site/Binding.md b/docs/site/Binding.md index 5ba0cc0e1b0b..111c464b6f76 100644 --- a/docs/site/Binding.md +++ b/docs/site/Binding.md @@ -113,6 +113,21 @@ class MyValueProvider implements Provider { binding.toProvider(MyValueProvider); ``` +#### An alias + +An alias is the key with optional path to resolve the value from another +binding. For example, if we want to get options from RestServer for the API +explorer, we can configure the `apiExplorer.options` to be resolved from +`servers.RestServer.options#apiExplorer`. + +```ts +ctx.bind('servers.RestServer.options').to({apiExplorer: {path: '/explorer'}}); +ctx + .bind('apiExplorer.options') + .toAlias('servers.RestServer.options#apiExplorer'); +const apiExplorerOptions = await ctx.get('apiExplorer.options'); // => {path: '/explorer'} +``` + ### Configure the scope We allow a binding to be resolved within a context using one of the following diff --git a/docs/site/DEVELOPING.md b/docs/site/DEVELOPING.md index abda92072144..d40a2061449f 100644 --- a/docs/site/DEVELOPING.md +++ b/docs/site/DEVELOPING.md @@ -150,11 +150,11 @@ from package-lock files.** If you ever end up with corrupted or out-of-date package locks, run the following commands to fix the problem: -``` +```sh $ npm run update-package-locks ``` -### Adding or updating dependencies +### Adding dependencies Use the following command to add or update dependency `dep` in a package `name`: @@ -177,6 +177,14 @@ adding a dependency to a scope, see package locks manually, see [Updating package locks](#updating-package-locks) above. +### Updating dependencies + +To update dependencies to their latest compatible versions: + +```sh +npm run update-all-deps +``` + ## File naming convention For consistency, we follow diff --git a/docs/site/Dependency-injection.md b/docs/site/Dependency-injection.md index 0eba170af562..e164ca3b02dd 100644 --- a/docs/site/Dependency-injection.md +++ b/docs/site/Dependency-injection.md @@ -321,7 +321,7 @@ its dependencies are resolved. Let's take a look at the following example: -![binding-scopes](../img/binding-scopes.png) +![binding-scopes](./imgs/binding-scopes.png) The corresponding code is: diff --git a/docs/site/Examples.md b/docs/site/Examples.md index 54b6c788dc86..d9e4399d284c 100644 --- a/docs/site/Examples.md +++ b/docs/site/Examples.md @@ -14,6 +14,12 @@ LoopBack 4 comes with the following example projects: - **[rpc-server](https://github.com/strongloop/loopback-next/tree/master/examples/rpc-server)**: An example showing how to implement a made-up RPC protocol. +- **[greeter-extension](https://github.com/strongloop/loopback-next/tree/master/examples/greeter-extension)**: + An example showing how to implement the extension point/extension pattern. + +- **[loopback4-example-shopping](https://github.com/strongloop/loopback4-example-shopping)**: + An online e-commerce demo to validate/test the LoopBack 4 framework readiness. + You can download the example projects using our CLI tool `lb4`: ```sh @@ -26,6 +32,7 @@ $ lb4 example rpc-server: A basic RPC server using a made-up protocol. soap-calculator: An example on how to integrate SOAP web services. express-composition: A simple Express application that uses LoopBack 4 REST API. + greeter-extension: An example showing how to implement the extension point/extension pattern. ``` Please follow the instructions in diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index a83ce425da63..dd8a2b54140a 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -15,6 +15,7 @@ The [loopback-next](https://github.com/strongloop/loopback-next) repository uses | [core](https://github.com/strongloop/loopback-next/tree/master/packages/core) | @loopback/core | Define and implement core constructs such as Application and Component | | [docs](https://github.com/strongloop/loopback-next/tree/master/docs) | @loopback/docs | Documentation files rendered at [https://loopback.io](https://loopback.io) | | [example-express-composition](https://github.com/strongloop/loopback-next/tree/master/examples/express-composition) | @loopback/example-express-composition | A simple Express application that uses LoopBack 4 REST API | +| [example-greeter-extension](https://github.com/strongloop/loopback-next/tree/master/examples/greeter-extension) | @loopback/example-greeter-extension | An example showing how to implement the extension point/extension pattern using LoopBack 4 | | [example-hello-world](https://github.com/strongloop/loopback-next/tree/master/examples/hello-world) | @loopback/example-hello-world | A simple hello-world application using LoopBack 4 | | [example-log-extension](https://github.com/strongloop/loopback-next/tree/master/examples/log-extension) | @loopback/example-log-extension | An example showing how to write a complex log extension for LoopBack 4 | | [example-rpc-server](https://github.com/strongloop/loopback-next/tree/master/examples/rpc-server) | @loopback/example-rpc-server | An example RPC server and application to demonstrate the creation of your own custom server | diff --git a/docs/site/Routes.md b/docs/site/Routes.md index 0bb64bec2b24..df4120d04f96 100644 --- a/docs/site/Routes.md +++ b/docs/site/Routes.md @@ -226,3 +226,64 @@ export class MyApplication extends RestApplication { } } ``` + +## Mounting an Express Router + +If you have an existing [Express](https://expressjs.com/) application that you +want to use with LoopBack 4, you can mount the Express application on top of a +LoopBack 4 application. This way you can mix and match both frameworks, while +using LoopBack as the host. You can also do the opposite and use Express as the +host by mounting LoopBack 4 REST API on an Express application. See +[Creating an Express Application with LoopBack REST API](express-with-lb4-rest-tutorial.md) +for the tutorial. + +Mounting an Express router on a LoopBack 4 application can be done using the +`mountExpressRouter` function provided by both +[`RestApplication`](http://apidocs.loopback.io/@loopback%2fdocs/rest.html#RestApplication) +and +[`RestServer`](http://apidocs.loopback.io/@loopback%2fdocs/rest.html#RestServer). + +Example use: + +{% include note.html content=" +Make sure [express](https://www.npmjs.com/package/express) is installed. +" %} + +{% include code-caption.html content="src/express-app.ts" %} + +```ts +import {Request, Response} from 'express'; +import * as express from 'express'; + +const legacyApp = express(); + +// your existing Express routes +legacyApp.get('/pug', function(_req: Request, res: Response) { + res.send('Pug!'); +}); + +export {legacyApp}; +``` + +{% include code-caption.html content="src/application.ts" %} + +```ts +import {RestApplication} from '@loopback/rest'; + +const legacyApp = require('./express-app').legacyApp; + +const openApiSpecForLegacyApp: RouterSpec = { + // insert your spec here, your 'paths', 'components', and 'tags' will be used +}; + +class MyApplication extends RestApplication { + constructor(/* ... */) { + // ... + + this.mountExpressRouter('/dogs', legacyApp, openApiSpecForLegacyApp); + } +} +``` + +Any routes you define in your `legacyApp` will be mounted on top of the `/dogs` +base path, e.g. if you visit the `/dogs/pug` endpoint, you'll see `Pug!`. diff --git a/docs/site/Sequence.md b/docs/site/Sequence.md index 58111c80502a..f5b0d2c0a8db 100644 --- a/docs/site/Sequence.md +++ b/docs/site/Sequence.md @@ -336,3 +336,94 @@ An example error message when the debug mode is enabled: - Try and use existing actions - Implement your own version of built in actions - Publish reusable actions to npm + +## Working with Express middleware + +Under the hood, LoopBack leverages [Express](https://expressjs.com) framework +and its concept of middleware. To avoid common pitfalls, it is not possible to +mount Express middleware directly on a LoopBack application. Instead, LoopBack +provides and enforces a higher-level structure. + +In a typical Express application, there are four kinds of middleware invoked in +the following order: + +1. Request-preprocessing middleware like + [cors](https://www.npmjs.com/package/cors) or + [body-parser](https://www.npmjs.com/package/body-parser). +2. Route handlers handling requests and producing responses. +3. Middleware serving static assets (files). +4. Error handling middleware. + +In LoopBack, we handle the request in the following steps: + +1. The built-in request-preprocessing middleware is invoked. +2. The registered Sequence is started. The default implementation of `findRoute` + and `invoke` actions will try to match the incoming request against the + following resources: + 1. Native LoopBack routes (controller methods, route handlers). + 2. External Express routes (registered via `mountExpressRouter` API) + 3. Static assets +3. Errors are handled by the Sequence using `reject` action. + +Let's see how different kinds of Express middleware can be mapped to LoopBack +concepts: + +#### Request-preprocessing middleware + +At the moment, LoopBack does not provide API for mounting arbitrary middleware, +we are discussing this feature in issues +[#1293](https://github.com/strongloop/loopback-next/issues/1293) and +[#2035](https://github.com/strongloop/loopback-next/issues/2035). Please up-vote +them if you are interested in using Express middleware in LoopBack applications. + +All applications come with [cors](https://www.npmjs.com/package/cors) enabled, +this middleware can be configured via RestServer options - see +[Customize CORS](Server.md#customize-cors). + +While it is not possible to add additional middleware to a LoopBack application, +it is possible to mount the entire LoopBack application as component of a parent +top-level Express application where you can add arbitrary middleware as needed. +You can find more details about this approach in +[Creating an Express Application with LoopBack REST API](express-with-lb4-rest-tutorial.md) + +#### Route handlers + +In Express, a route handler is a middleware function that serves the response +and does not call `next()`. Handlers can be registered using APIs like +`app.get()`, `app.post()`, but also a more generic `app.use()`. + +In LoopBack, we typically use [Controllers](Controllers.md) and +[Route handlers](Routes.md) to implement request handling logic. + +To support interoperability with Express, it is also possible to take an Express +Router instance and add it to a LoopBack application as an external router - see +[Mounting an Express Router](Routes.md#mounting-an-express-router). This way it +is possible to implement server endpoints using Express APIs. + +#### Static files + +LoopBack provides native API for registering static assets as described in +[Serve static files](Application.html#serve-static-files). Under the hood, +static assets are served by +[serve-static](https://www.npmjs.com/package/serve-static) middleware from +Express. + +The main difference between LoopBack and vanilla Express applications: LoopBack +ensures that static-asset middleware is always invoked as the last one, only +when no other route handled the request. This is important for performance +reasons to avoid costly filesystem calls. + +#### Error handling middleware + +In Express, errors are handled by a special form of middleware, one that's +accepting four arguments: `err`, `request`, `response`, `next`. It's up to the +application developer to ensure that error handler is registered as the last +middleware in the chain, otherwise not all errors may be routed to it. + +In LoopBack, we use async functions instead of callbacks and thus can use simple +`try`/`catch` flow to receive both sync and async errors from individual +sequence actions. A typical Sequence implementation then passes these errors to +the Sequence action `reject`. + +You can learn more about error handling in +[Handling errors](Sequence.md#handling-errors). diff --git a/docs/site/express-with-lb4-rest-tutorial.md b/docs/site/express-with-lb4-rest-tutorial.md index 3fbe26dfb247..64636a21227a 100644 --- a/docs/site/express-with-lb4-rest-tutorial.md +++ b/docs/site/express-with-lb4-rest-tutorial.md @@ -14,6 +14,12 @@ REST API can be mounted to an Express application and be used as middleware. This way the user can mix and match features from both frameworks to suit their needs. +{% include note.html content=" +If you want to use LoopBack as the host instead and mount your Express +application on a LoopBack 4 application, see +[Mounting an Express Router](Routes.md#mounting-an-express-router). +" %} + This tutorial assumes familiarity with scaffolding a LoopBack 4 application, [`Models`](Model.md), [`DataSources`](DataSources.md), [`Repositories`](Repositories.md), and [`Controllers`](Controllers.md). To see diff --git a/docs/img/binding-scopes.png b/docs/site/imgs/binding-scopes.png similarity index 100% rename from docs/img/binding-scopes.png rename to docs/site/imgs/binding-scopes.png diff --git a/docs/site/todo-list-tutorial-controller.md b/docs/site/todo-list-tutorial-controller.md index 0b373a139785..a5d6bf02ebe2 100644 --- a/docs/site/todo-list-tutorial-controller.md +++ b/docs/site/todo-list-tutorial-controller.md @@ -98,125 +98,96 @@ Using our constraining factory as we did with the `POST` request, we'll define the controller methods for the rest of the HTTP verbs for the route. The completed controller should look as follows: -{% include code-caption.html content="src/controllers/todo-list.controller.ts" %} +{% include code-caption.html content="src/controllers/todo-list-todo.controller.ts" %} ```ts import { + Count, + CountSchema, Filter, repository, Where, - Count, - CountSchema, } from '@loopback/repository'; import { del, get, - getFilterSchemaFor, getWhereSchemaFor, param, patch, post, requestBody, } from '@loopback/rest'; -import {TodoList} from '../models'; +import {Todo} from '../models'; import {TodoListRepository} from '../repositories'; -export class TodoListController { +export class TodoListTodoController { constructor( - @repository(TodoListRepository) - public todoListRepository: TodoListRepository, + @repository(TodoListRepository) protected todoListRepo: TodoListRepository, ) {} - @post('/todo-lists', { - responses: { - '200': { - description: 'TodoList model instance', - content: {'application/json': {schema: {'x-ts-type': TodoList}}}, - }, - }, - }) - async create(@requestBody() obj: TodoList): Promise { - return await this.todoListRepository.create(obj); - } - - @get('/todo-lists/count', { + @post('/todo-lists/{id}/todos', { responses: { '200': { - description: 'TodoList model count', - content: {'application/json': {schema: CountSchema}}, + description: 'TodoList.Todo model instance', + content: {'application/json': {schema: {'x-ts-type': Todo}}}, }, }, }) - async count( - @param.query.object('where', getWhereSchemaFor(TodoList)) where?: Where, - ): Promise { - return await this.todoListRepository.count(where); + async create( + @param.path.number('id') id: number, + @requestBody() todo: Todo, + ): Promise { + return await this.todoListRepo.todos(id).create(todo); } - @get('/todo-lists', { + @get('/todo-lists/{id}/todos', { responses: { '200': { - description: 'Array of TodoList model instances', - content: {'application/json': {schema: {'x-ts-type': TodoList}}}, + description: "Array of Todo's belonging to TodoList", + content: { + 'application/json': { + schema: {type: 'array', items: {'x-ts-type': Todo}}, + }, + }, }, }, }) async find( - @param.query.object('filter', getFilterSchemaFor(TodoList)) filter?: Filter, - ): Promise { - return await this.todoListRepository.find(filter); + @param.path.number('id') id: number, + @param.query.object('filter') filter?: Filter, + ): Promise { + return await this.todoListRepo.todos(id).find(filter); } - @patch('/todo-lists', { + @patch('/todo-lists/{id}/todos', { responses: { '200': { - description: 'TodoList PATCH success count', + description: 'TodoList.Todo PATCH success count', content: {'application/json': {schema: CountSchema}}, }, }, }) - async updateAll( - @requestBody() obj: Partial, - @param.query.object('where', getWhereSchemaFor(TodoList)) where?: Where, + async patch( + @param.path.number('id') id: number, + @requestBody() todo: Partial, + @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, ): Promise { - return await this.todoListRepository.updateAll(obj, where); + return await this.todoListRepo.todos(id).patch(todo, where); } - @get('/todo-lists/{id}', { + @del('/todo-lists/{id}/todos', { responses: { '200': { - description: 'TodoList model instance', - content: {'application/json': {schema: {'x-ts-type': TodoList}}}, - }, - }, - }) - async findById(@param.path.number('id') id: number): Promise { - return await this.todoListRepository.findById(id); - } - - @patch('/todo-lists/{id}', { - responses: { - '204': { - description: 'TodoList PATCH success', + description: 'TodoList.Todo DELETE success count', + content: {'application/json': {schema: CountSchema}}, }, }, }) - async updateById( + async delete( @param.path.number('id') id: number, - @requestBody() obj: TodoList, - ): Promise { - await this.todoListRepository.updateById(id, obj); - } - - @del('/todo-lists/{id}', { - responses: { - '204': { - description: 'TodoList DELETE success', - }, - }, - }) - async deleteById(@param.path.number('id') id: number): Promise { - await this.todoListRepository.deleteById(id); + @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, + ): Promise { + return await this.todoListRepo.todos(id).delete(where); } } ``` diff --git a/examples/express-composition/CHANGELOG.md b/examples/express-composition/CHANGELOG.md index a3284010e1e0..484c8ed644d5 100644 --- a/examples/express-composition/CHANGELOG.md +++ b/examples/express-composition/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-express-composition@1.2.3...@loopback/example-express-composition@1.2.4) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-express-composition + + + + + ## [1.2.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-express-composition@1.2.2...@loopback/example-express-composition@1.2.3) (2019-03-22) **Note:** Version bump only for package @loopback/example-express-composition diff --git a/examples/express-composition/package-lock.json b/examples/express-composition/package-lock.json index 6163284a173e..a2665fb0d894 100644 --- a/examples/express-composition/package-lock.json +++ b/examples/express-composition/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-express-composition", - "version": "1.2.3", + "version": "1.2.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -154,6 +154,42 @@ "qs": "6.5.2", "raw-body": "2.3.3", "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "brace-expansion": { @@ -255,19 +291,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -352,6 +375,41 @@ "type-is": "~1.6.16", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } } }, "finalhandler": { @@ -366,6 +424,26 @@ "parseurl": "~1.3.2", "statuses": "~1.4.0", "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } } }, "forwarded": { @@ -413,17 +491,6 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -459,9 +526,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -525,11 +592,6 @@ "minimist": "0.0.8" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -553,9 +615,9 @@ } }, "p-event": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz", - "integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", "requires": { "p-timeout": "^2.0.1" } @@ -590,11 +652,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -604,11 +661,6 @@ "ipaddr.js": "1.8.0" } }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -623,6 +675,24 @@ "http-errors": "1.6.3", "iconv-lite": "0.4.23", "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + } } }, "resolve": { @@ -645,9 +715,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, "send": { @@ -668,6 +738,42 @@ "on-finished": "~2.3.0", "range-parser": "~1.2.0", "statuses": "~1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } } }, "serve-static": { @@ -693,9 +799,9 @@ "dev": true }, "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "strip-ansi": { "version": "3.0.1", @@ -719,9 +825,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -736,7 +842,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -758,9 +864,9 @@ } }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "unpipe": { diff --git a/examples/express-composition/package.json b/examples/express-composition/package.json index 7f6f48f07b67..2db26e7ed422 100644 --- a/examples/express-composition/package.json +++ b/examples/express-composition/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-express-composition", - "version": "1.2.3", + "version": "1.2.4", "description": "LoopBack 4 REST API on Express", "keywords": [ "loopback-application", @@ -43,24 +43,24 @@ "author": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/boot": "^1.1.2", - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/repository": "^1.2.1", - "@loopback/rest": "^1.9.1", - "@loopback/rest-explorer": "^1.1.13", - "@loopback/service-proxy": "^1.1.1", + "@loopback/boot": "^1.1.3", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/repository": "^1.3.0", + "@loopback/rest": "^1.10.0", + "@loopback/rest-explorer": "^1.1.14", + "@loopback/service-proxy": "^1.1.2", "express": "^4.16.4", - "p-event": "^4.0.0" + "p-event": "^4.1.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/express": "^4.16.1", "@types/node": "^10.11.2", - "tslint": "^5.12.0", - "typescript": "^3.2.2" + "tslint": "^5.14.0", + "typescript": "^3.4.1" } } diff --git a/examples/greeter-extension/.npmrc b/examples/greeter-extension/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/examples/greeter-extension/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/examples/greeter-extension/.prettierignore b/examples/greeter-extension/.prettierignore new file mode 100644 index 000000000000..c6911da9e1e8 --- /dev/null +++ b/examples/greeter-extension/.prettierignore @@ -0,0 +1,2 @@ +dist +*.json diff --git a/examples/greeter-extension/.prettierrc b/examples/greeter-extension/.prettierrc new file mode 100644 index 000000000000..f58b81dd7be2 --- /dev/null +++ b/examples/greeter-extension/.prettierrc @@ -0,0 +1,6 @@ +{ + "bracketSpacing": false, + "singleQuote": true, + "printWidth": 80, + "trailingComma": "all" +} diff --git a/examples/greeter-extension/.vscode/settings.json b/examples/greeter-extension/.vscode/settings.json new file mode 100644 index 000000000000..4a90df17452c --- /dev/null +++ b/examples/greeter-extension/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "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, + + "tslint.ignoreDefinitionFiles": true, + "typescript.tsdk": "./node_modules/typescript/lib" +} diff --git a/examples/greeter-extension/.vscode/tasks.json b/examples/greeter-extension/.vscode/tasks.json new file mode 100644 index 000000000000..c3003aa764e3 --- /dev/null +++ b/examples/greeter-extension/.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", "$tslint5"] + } + ] +} diff --git a/examples/greeter-extension/CHANGELOG.md b/examples/greeter-extension/CHANGELOG.md new file mode 100644 index 000000000000..e3b328ccec03 --- /dev/null +++ b/examples/greeter-extension/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# 1.0.0 (2019-04-05) + + +### Features + +* add greeter-extension example ([9b09298](https://github.com/strongloop/loopback-next/commit/9b09298)) diff --git a/examples/greeter-extension/LICENSE b/examples/greeter-extension/LICENSE new file mode 100644 index 000000000000..c880a3a5c143 --- /dev/null +++ b/examples/greeter-extension/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2019. All Rights Reserved. +Node module: @loopback/example-greeter-extension +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/greeter-extension/README.md b/examples/greeter-extension/README.md new file mode 100644 index 000000000000..8cc5703a770b --- /dev/null +++ b/examples/greeter-extension/README.md @@ -0,0 +1,357 @@ +# @loopback/example-greeter-extension + +This example project illustrates how to implement the +[Extension Point/extension pattern](https://wiki.eclipse.org/FAQ_What_are_extensions_and_extension_points%3F), +which promotes loose coupling and offers great extensibility. There are many use +cases in LoopBack 4 that fit into design pattern. For example: + +- `@loopback/boot` uses `BootStrapper` that delegates to `Booters` to handle + different types of artifacts +- `@loopback/rest` uses `RequestBodyParser` that finds the corresponding + `BodyParsers` to parse request body encoded in different media types + +## Overview + +We'll use the following scenario to walk through important steps to organize the +`greet` service that allows extensible languages - each of them being supported +by a `Greeter` extension. + +![greeters](greeters.png) + +Various constructs from LoopBack 4, such as `Context`, `@inject.*`, and +`Component` are used to build the service in an extensible fashion. + +## Define an extension point + +In our scenario, we want to allow other modules to extend or customize how +people are greeted in different languages. To achieve that, we declare the +`greeter` extension point, which declares a contract as TypeScript interfaces +that extensions must conform to. + +### Define interface for extensions + +An extension point interacts with unknown number of extensions. It needs to +define one or more interfaces as contracts that each extension must implement. + +```ts +/** + * Typically an extension point defines an interface as the contract for + * extensions to implement + */ +export interface Greeter { + language: string; + greet(name: string): string; +} +``` + +### Define class for the extension point + +Typically an extension point is defined as a TypeScript class and bound to a +context. In our case, we mark `GreetingService` as the extension point that +needs to access a list of greeters. + +```ts +/** + * An extension point for greeters that can greet in different languages + */ +export class GreetingService { + constructor( + /** + * Inject a getter function to fetch greeters (bindings tagged with + * `{extensionPoint: GREETER_EXTENSION_POINT_NAME}`) + */ + @inject.getter( + bindingTagFilter({extensionPoint: GREETER_EXTENSION_POINT_NAME}), + ) + private getGreeters: Getter, + ) {} + // ... +} +``` + +To customize metadata such as `id` for the extension point, we can use +`@extensionPoint` to decorate the class, such as: + +```ts +@extensionPoint(GREETER_EXTENSION_POINT_NAME) +export class GreetingService {} +``` + +#### Access extensions for a given extension point + +To simplify access to extensions for a given extension point, we use dependency +injection to receive a `getter` function that gives us a list of greeters. + +```ts +@extensionPoint(GREETER_EXTENSION_POINT_NAME) +export class GreetingService { + constructor( + /** + * Inject a getter function to fetch greeters (bindings tagged with + * `{extensionPoint: GREETER_EXTENSION_POINT_NAME}`) + */ + @extensions() // Sugar for @inject.getter(filterByTag({extensionPoint: GREETER_EXTENSION_POINT_NAME})) + private getGreeters: Getter, // ... + ) {} +} +``` + +Please note that it's possible to add/remove greeters after the extension point +is instantiated. If we use `@inject` to receive a list of registered greeters, +the `GreetingService` instance is injected with a snapshot of greeters but it +won't pick up any changes afterward. With `@inject.getter`, LoopBack injects a +getter function that will return the latest list of greeters, reflecting any +changes made since the last call. For example: + +```ts +// Get the latest list of greeters +const greeters = await this.getGreeters(); +``` + +#### Implement the delegation logic + +Typically, the extension point implementation will get a list of registered +extensions. For example, when a person needs to be greeted in a specific +language, the code iterates through all greeters to find an instance that +matches the language. In this module, `GreetingService` implements the `greet` +operation which uses `findGreeter` to find a greeter and produces a greeting for +the given language. + +```ts +export class GreetingService { + // ... + /** + * Find a greeter that can speak the given language + * @param language Language code for the greeting + */ + async findGreeter(language: string): Promise { + // Get the latest list of greeters + const greeters = await this.getGreeters(); + // Find a greeter that can speak the given language + return greeters.find(g => g.language === language); + } + + /** + * Greet in the given language + * @param language Language code + * @param name Name + */ + async greet(language: string, name: string): Promise { + let greeting: string = ''; + + const greeter = await this.findGreeter(language); + if (greeter) { + greeting = greeter.greet(name); + } else { + // Fall back to English + greeting = `Hello, ${name}`; + } + if (this.options && this.options.color) { + greeting = chalk.keyword(this.options.color)(greeting); + } + return greeting; + } +} +``` + +## Implement an extension + +Modules that want to connect to `greeter` extension point must implement +`Greeter` interface in their extension. The key attribute is that the +`GreetingService` being extended knows nothing about the module that is +connecting to it beyond the scope of that contract. This allows `greeters` built +by different individuals or companies to interact seamlessly, even without their +knowing much about one another. + +```ts +import {Greeter, asGreeter} from '../types'; +import {bind, inject} from '@loopback/context'; + +/** + * Options for the Chinese greeter + */ +export interface ChineseGreeterOptions { + // Name first, default to `true` + nameFirst: boolean; +} + +/** + * A greeter implementation for Chinese + */ +@bind(asGreeter) +export class ChineseGreeter implements Greeter { + language = 'zh'; + + constructor( + /** + * Inject the configuration for ChineseGreeter + */ + @inject('greeters.ChineseGreeter.options', {optional: true}) + private options: ChineseGreeterOptions = {nameFirst: true}, + ) {} + + greet(name: string) { + if (this.options && this.options.nameFirst === false) { + return `你好,${name}!`; + } + return `${name},你好!`; + } +} +``` + +Please note we use +[`@bind`](https://loopback.io/doc/en/lb4/Binding.html#configure-binding-attributes-for-a-class) +to customize how the class can be bound. In this case, `asGreeter` is a binding +template function, which is equivalent as configuring a binding with +`{extensionPoint: 'greeter'}` tag and in the `SINGLETON` scope. + +```ts +/** + * A binding template for greeter extensions + * @param binding + */ +export const asGreeter: BindingTemplate = binding => + binding.inScope(BindingScope.SINGLETON).tag({extensionPoint: 'greeter'}); +``` + +## Register an extension point + +To register an extension point, we simply bind the implementation class to a +`Context`. For example: + +```ts +app + .bind('services.GreetingService') + .toClass(GreetingService) + .inScope(BindingScope.SINGLETON); +``` + +**NOTE**: Your extension point may choose to use a different binding scope. + +The process can be automated with a component: + +```ts +import {createBindingFromClass} from '@loopback/context'; +import {Component} from '@loopback/core'; +import {GreetingService} from './greeting-service'; +import {GREETING_SERVICE} from './keys'; + +/** + * Define a component to register the greeter extension point and built-in + * extensions + */ +export class GreeterComponent implements Component { + bindings = [ + createBindingFromClass(GreetingService, { + key: GREETING_SERVICE, + }), + // ... + ]; +} +``` + +## Register extensions + +To connect an extension to an extension point, we just have to bind the +extension to the `Context` and tag the binding with +`{extensionPoint: 'greeters'}`. + +```ts +app + .bind('greeters.FrenchGreeter') + .toClass(FrenchGreeter) + .apply(asGreeter); +``` + +Or + +```ts +app.add(createBindingFromClass(FrenchGreeter)); +``` + +The registration can be done using a component too: + +```ts +export class GreeterComponent implements Component { + bindings = [ + // ... + createBindingFromClass(EnglishGreeter), + createBindingFromClass(ChineseGreeter), + ]; +} +``` + +## Configure an extension point + +Sometimes it's desirable to make the extension point configurable. Two steps are +involved to achieve that. + +1. Declare an injection for the configuration for your extension point class: + +```ts +export class GreetingService { + constructor( + // ... + private getGreeters: Getter, + /** + * An extension point should be able to receive its options via dependency + * injection. + */ + @configuration() // Sugar for @inject('services.GreetingService.options', {optional: true}) + public readonly options?: GreetingServiceOptions, + ) {} +} +``` + +2. Set configuration for the extension point + +```ts +// Configure the extension point +app.bind('services.GreetingService.options').to({color: 'blue'}); +``` + +## Configure an extension + +Some extensions also support customization. The approach is similar as how to +configure an extension point. + +1. Declare an injection for the configuration in the extension class + +```ts +export class ChineseGreeter implements Greeter { + language = 'zh'; + + constructor( + /** + * Inject the configuration for ChineseGreeter + */ + @inject('greeters.ChineseGreeter.options', {optional: true}) + private options: ChineseGreeterOptions = {nameFirst: true}, + ) {} +} +``` + +2. Set configuration for the extension + +```ts +// Configure the ChineseGreeter +app.bind('greeters.ChineseGreeter.options').to({nameFirst: false}); +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/examples/greeter-extension/greeters.png b/examples/greeter-extension/greeters.png new file mode 100644 index 000000000000..600633d7940b Binary files /dev/null and b/examples/greeter-extension/greeters.png differ diff --git a/examples/greeter-extension/index.d.ts b/examples/greeter-extension/index.d.ts new file mode 100644 index 000000000000..18078ab5428c --- /dev/null +++ b/examples/greeter-extension/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist'; diff --git a/examples/greeter-extension/index.js b/examples/greeter-extension/index.js new file mode 100644 index 000000000000..96b109ab9512 --- /dev/null +++ b/examples/greeter-extension/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('./dist'); diff --git a/examples/greeter-extension/index.ts b/examples/greeter-extension/index.ts new file mode 100644 index 000000000000..d1bad0d47fbc --- /dev/null +++ b/examples/greeter-extension/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './src'; diff --git a/examples/greeter-extension/package-lock.json b/examples/greeter-extension/package-lock.json new file mode 100644 index 000000000000..420045463137 --- /dev/null +++ b/examples/greeter-extension/package-lock.json @@ -0,0 +1,371 @@ +{ + "name": "@loopback/example-greeter-extension", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/debug": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz", + "integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==", + "dev": true + }, + "@types/node": { + "version": "10.12.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", + "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "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" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "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=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/examples/greeter-extension/package.json b/examples/greeter-extension/package.json new file mode 100644 index 000000000000..c9df7ab8711f --- /dev/null +++ b/examples/greeter-extension/package.json @@ -0,0 +1,60 @@ +{ + "name": "@loopback/example-greeter-extension", + "version": "1.0.0", + "description": "An example extension point/extensions for LoopBack 4", + "main": "index.js", + "engines": { + "node": ">=8.9" + }, + "scripts": { + "build:apidocs": "lb-apidocs", + "build": "lb-tsc es2017 --outDir dist", + "build:watch": "lb-tsc es2017 --outDir dist --watch", + "clean": "lb-clean *example-greeter-extension-*.tgz dist package api-docs", + "lint": "npm run prettier:check && npm run tslint", + "lint:fix": "npm run tslint:fix && npm run prettier:fix", + "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"", + "prettier:check": "npm run prettier:cli -- -l", + "prettier:fix": "npm run prettier:cli -- --write", + "tslint": "lb-tslint", + "tslint:fix": "npm run tslint -- --fix", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "posttest": "npm run lint", + "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", + "verify": "npm pack && tar xf *example-greeter-extension*.tgz && tree package && npm run clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/strongloop/loopback-next.git" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "loopback", + "loopback-extension" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/strongloop/loopback-next/issues" + }, + "homepage": "https://github.com/strongloop/loopback-next/tree/master/examples/greeter-extension", + "devDependencies": { + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", + "@types/debug": "0.0.30", + "@types/node": "^10.11.2", + "tslint": "^5.14.0", + "typescript": "^3.4.1" + }, + "dependencies": { + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/rest": "^1.10.0", + "chalk": "^2.4.2", + "debug": "^4.0.1" + } +} diff --git a/examples/greeter-extension/src/__tests__/greeter-extension.acceptance.ts b/examples/greeter-extension/src/__tests__/greeter-extension.acceptance.ts new file mode 100644 index 000000000000..8f1ba19296cc --- /dev/null +++ b/examples/greeter-extension/src/__tests__/greeter-extension.acceptance.ts @@ -0,0 +1,81 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application, bind, createBindingFromClass} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import chalk from 'chalk'; +import { + asGreeter, + Greeter, + GreeterComponent, + GreetingService, + GREETING_SERVICE, +} from '..'; + +describe('greeter-extension-pont', () => { + let app: Application; + let greetingService: GreetingService; + + beforeEach(givenAppWithGreeterComponent); + beforeEach(findGreetingService); + + it('greets by language', async () => { + let msg = await greetingService.greet('en', 'Raymond'); + expect(msg).to.eql('Hello, Raymond'); + msg = await greetingService.greet('zh', 'Raymond'); + expect(msg).to.eql('Raymond,你好!'); + }); + + it('supports options for the extension point', async () => { + // Configure the extension point + app.bind('services.GreetingService.options').to({color: 'blue'}); + greetingService = await app.get(GREETING_SERVICE); + expect(greetingService.options).to.eql({color: 'blue'}); + const msg = await greetingService.greet('en', 'Raymond'); + expect(msg).to.eql(chalk.keyword('blue')('Hello, Raymond')); + }); + + it('supports options for extensions', async () => { + // Configure the ChineseGreeter + app.bind('greeters.ChineseGreeter.options').to({nameFirst: false}); + const msg = await greetingService.greet('zh', 'Raymond'); + expect(msg).to.eql('你好,Raymond!'); + }); + + it('honors a newly added/removed greeter binding', async () => { + /** + * A greeter implementation for French + */ + @bind(asGreeter) + class FrenchGreeter implements Greeter { + language = 'fr'; + + greet(name: string) { + return `Bonjour, ${name}`; + } + } + // Add a new greeter for French + const binding = createBindingFromClass(FrenchGreeter); + app.add(binding); + + let msg = await greetingService.greet('fr', 'Raymond'); + expect(msg).to.eql('Bonjour, Raymond'); + + // Remove the French greeter + app.unbind(binding.key); + msg = await greetingService.greet('fr', 'Raymond'); + // It falls back to English + expect(msg).to.eql('Hello, Raymond'); + }); + + function givenAppWithGreeterComponent() { + app = new Application(); + app.component(GreeterComponent); + } + + async function findGreetingService() { + greetingService = await app.get(GREETING_SERVICE); + } +}); diff --git a/examples/greeter-extension/src/component.ts b/examples/greeter-extension/src/component.ts new file mode 100644 index 000000000000..b5f1381ef5f7 --- /dev/null +++ b/examples/greeter-extension/src/component.ts @@ -0,0 +1,25 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {createBindingFromClass} from '@loopback/context'; +import {Component} from '@loopback/core'; +import {GreetingService} from './greeting-service'; +import {ChineseGreeter} from './greeters/greeter-cn'; +import {EnglishGreeter} from './greeters/greeter-en'; +import {GREETING_SERVICE} from './keys'; + +/** + * Define a component to register the greeter extension point and built-in + * extensions + */ +export class GreeterComponent implements Component { + bindings = [ + createBindingFromClass(GreetingService, { + key: GREETING_SERVICE, + }), + createBindingFromClass(EnglishGreeter), + createBindingFromClass(ChineseGreeter), + ]; +} diff --git a/examples/greeter-extension/src/decorators.ts b/examples/greeter-extension/src/decorators.ts new file mode 100644 index 000000000000..1f52d4e6287d --- /dev/null +++ b/examples/greeter-extension/src/decorators.ts @@ -0,0 +1,85 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + ClassDecoratorFactory, + createViewGetter, + filterByTag, + inject, + MetadataInspector, +} from '@loopback/context'; +import {EXTENSION_POINT_NAME} from './keys'; + +/** + * Decorate a class as a named extension point. If the decoration is not + * present, the name of the class will be used. + * + * TODO: to be promoted to `@loopback/core` module. + * @param name Name of the extension point + */ +export function extensionPoint(name: string) { + return ClassDecoratorFactory.createDecorator(EXTENSION_POINT_NAME, {name}); +} + +/** + * Shortcut to inject extensions for the given extension point. + * + * TODO: to be promoted to `@loopback/core` module possibly as + * `@inject.extensions`. + * + * @param extensionPoint Name of the extension point. If not supplied, we use + * the name from `@extensionPoint` or the class name of the extension point. + */ +export function extensions(extensionPointName?: string) { + return inject('', {decorator: '@extensions'}, (ctx, injection, session?) => { + if (!session) return undefined; + // Find the key of the target binding + if (!session.currentBinding) return undefined; + + if (!extensionPointName) { + let target: Function; + if (typeof injection.target === 'function') { + // Constructor injection + target = injection.target; + } else { + // Injection on the prototype + target = injection.target.constructor; + } + const meta: + | {name: string} + | undefined = MetadataInspector.getClassMetadata( + EXTENSION_POINT_NAME, + target, + ); + extensionPointName = (meta && meta.name) || target.name; + } + + const bindingFilter = filterByTag({extensionPoint: extensionPointName}); + return createViewGetter(ctx, bindingFilter, session); + }); +} + +/** + * Shortcut to inject configuration for the target binding. To be promoted + * as `@inject.config` in `@loopback/context` module. + * + * See https://github.com/strongloop/loopback-next/pull/2259 + */ +export function configuration() { + return inject( + '', + {decorator: '@inject.config', optional: true}, + (ctx, injection, session?) => { + if (!session) return undefined; + // Find the key of the target binding + if (!session.currentBinding) return undefined; + const key = session.currentBinding!.key; + return ctx.get(`${key}.options`, { + session, + optional: injection.metadata && injection.metadata.optional, + }); + }, + ); +} diff --git a/examples/greeter-extension/src/greeters/greeter-cn.ts b/examples/greeter-extension/src/greeters/greeter-cn.ts new file mode 100644 index 000000000000..74d4960ae713 --- /dev/null +++ b/examples/greeter-extension/src/greeters/greeter-cn.ts @@ -0,0 +1,38 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {bind, inject} from '@loopback/context'; +import {asGreeter, Greeter} from '../types'; + +/** + * Options for the Chinese greeter + */ +export interface ChineseGreeterOptions { + // Name first, default to `true` + nameFirst: boolean; +} + +/** + * A greeter implementation for Chinese. + */ +@bind(asGreeter) +export class ChineseGreeter implements Greeter { + language = 'zh'; + + constructor( + /** + * Inject the configuration for ChineseGreeter + */ + @inject('greeters.ChineseGreeter.options', {optional: true}) + private options: ChineseGreeterOptions = {nameFirst: true}, + ) {} + + greet(name: string) { + if (this.options && this.options.nameFirst === false) { + return `你好,${name}!`; + } + return `${name},你好!`; + } +} diff --git a/examples/greeter-extension/src/greeters/greeter-en.ts b/examples/greeter-extension/src/greeters/greeter-en.ts new file mode 100644 index 000000000000..47e404bbace8 --- /dev/null +++ b/examples/greeter-extension/src/greeters/greeter-en.ts @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {bind} from '@loopback/context'; +import {asGreeter, Greeter} from '../types'; + +/** + * A greeter implementation for English + */ +@bind(asGreeter) +export class EnglishGreeter implements Greeter { + language = 'en'; + + greet(name: string) { + return `Hello, ${name}`; + } +} diff --git a/examples/greeter-extension/src/greeting-service.ts b/examples/greeter-extension/src/greeting-service.ts new file mode 100644 index 000000000000..ee50fd70778a --- /dev/null +++ b/examples/greeter-extension/src/greeting-service.ts @@ -0,0 +1,69 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Getter} from '@loopback/context'; +import chalk from 'chalk'; +import {configuration, extensionPoint, extensions} from './decorators'; +import {Greeter, GREETER_EXTENSION_POINT_NAME} from './types'; + +/** + * Options for the greeter extension point + */ +export interface GreetingServiceOptions { + color: string; +} + +/** + * An extension point for greeters that can greet in different languages + */ +@extensionPoint(GREETER_EXTENSION_POINT_NAME) +export class GreetingService { + constructor( + /** + * Inject a getter function to fetch greeters (bindings tagged with + * `{extensionPoint: GREETER_EXTENSION_POINT_NAME}`) + */ + @extensions() // Sugar for @inject.getter(filterByTag({extensionPoint: GREETER_EXTENSION_POINT_NAME})) + private getGreeters: Getter, + /** + * An extension point should be able to receive its options via dependency + * injection. + */ + @configuration() // Sugar for @inject('services.GreetingService.options', {optional: true}) + public readonly options?: GreetingServiceOptions, + ) {} + + /** + * Find a greeter that can speak the given language + * @param language Language code for the greeting + */ + async findGreeter(language: string): Promise { + // Get the latest list of greeters + const greeters = await this.getGreeters(); + // Find a greeter that can speak the given language + return greeters.find(g => g.language === language); + } + + /** + * Greet in the given language + * @param language Language code + * @param name Name + */ + async greet(language: string, name: string): Promise { + let greeting: string = ''; + + const greeter = await this.findGreeter(language); + if (greeter) { + greeting = greeter.greet(name); + } else { + // Fall back to English + greeting = `Hello, ${name}`; + } + if (this.options && this.options.color) { + greeting = chalk.keyword(this.options.color)(greeting); + } + return greeting; + } +} diff --git a/examples/greeter-extension/src/index.ts b/examples/greeter-extension/src/index.ts new file mode 100644 index 000000000000..d28f8be973be --- /dev/null +++ b/examples/greeter-extension/src/index.ts @@ -0,0 +1,9 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './component'; +export * from './greeting-service'; +export * from './keys'; +export * from './types'; diff --git a/examples/greeter-extension/src/keys.ts b/examples/greeter-extension/src/keys.ts new file mode 100644 index 000000000000..0c2c5dd61145 --- /dev/null +++ b/examples/greeter-extension/src/keys.ts @@ -0,0 +1,19 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey, MetadataAccessor} from '@loopback/context'; +import {GreetingService} from './greeting-service'; + +/** + * Strongly-typed binding key for GreetingService + */ +export const GREETING_SERVICE = BindingKey.create( + 'services.GreetingService', +); + +export const EXTENSION_POINT_NAME = MetadataAccessor.create< + {name: string}, + ClassDecorator +>('extensionPoint.name'); diff --git a/examples/greeter-extension/src/types.ts b/examples/greeter-extension/src/types.ts new file mode 100644 index 000000000000..1044d4baf7fc --- /dev/null +++ b/examples/greeter-extension/src/types.ts @@ -0,0 +1,38 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/example-greeter-extension +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingScope, BindingTemplate} from '@loopback/context'; + +/** + * Typically an extension point defines an interface as the contract for + * extensions to implement + */ +export interface Greeter { + language: string; + greet(name: string): string; +} + +/** + * A factory function to create binding template for extensions of the given + * extension point + * @param extensionPoint Name/id of the extension point + */ +export function extensionFor(extensionPoint: string): BindingTemplate { + return binding => + binding.inScope(BindingScope.SINGLETON).tag({extensionPoint}); +} + +/** + * Name/id of the greeter extension point + */ +export const GREETER_EXTENSION_POINT_NAME = 'greeters'; + +/** + * A binding template for greeter extensions + */ +export const asGreeter: BindingTemplate = binding => { + extensionFor(GREETER_EXTENSION_POINT_NAME)(binding); + binding.tag({namespace: 'greeters'}); +}; diff --git a/examples/greeter-extension/tsconfig.build.json b/examples/greeter-extension/tsconfig.build.json new file mode 100644 index 000000000000..6e15e4be4f6f --- /dev/null +++ b/examples/greeter-extension/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/examples/greeter-extension/tslint.build.json b/examples/greeter-extension/tslint.build.json new file mode 100644 index 000000000000..121b8adb21a3 --- /dev/null +++ b/examples/greeter-extension/tslint.build.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/tslint", + "extends": ["@loopback/tslint-config/tslint.build.json"] +} diff --git a/examples/greeter-extension/tslint.json b/examples/greeter-extension/tslint.json new file mode 100644 index 000000000000..2bb931e66a64 --- /dev/null +++ b/examples/greeter-extension/tslint.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/tslint", + "extends": ["@loopback/tslint-config/tslint.common.json"] +} diff --git a/examples/hello-world/CHANGELOG.md b/examples/hello-world/CHANGELOG.md index e3b06c6a6fde..f2dc4c58eb3f 100644 --- a/examples/hello-world/CHANGELOG.md +++ b/examples/hello-world/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.9](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@1.1.8...@loopback/example-hello-world@1.1.9) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-hello-world + + + + + ## [1.1.8](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@1.1.7...@loopback/example-hello-world@1.1.8) (2019-03-22) **Note:** Version bump only for package @loopback/example-hello-world diff --git a/examples/hello-world/package-lock.json b/examples/hello-world/package-lock.json index ef2194e11fd4..91089463a9e6 100644 --- a/examples/hello-world/package-lock.json +++ b/examples/hello-world/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-hello-world", - "version": "1.1.8", + "version": "1.1.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -219,9 +219,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -283,9 +283,9 @@ } }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, "sprintf-js": { @@ -316,9 +316,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -333,7 +333,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -346,9 +346,9 @@ } }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "wrappy": { diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index 5d23abfb28ec..ebdae12b68a7 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-hello-world", - "version": "1.1.8", + "version": "1.1.9", "description": "A simple hello-world Application using LoopBack 4", "main": "index.js", "engines": { @@ -37,16 +37,16 @@ }, "license": "MIT", "dependencies": { - "@loopback/core": "^1.2.1", - "@loopback/rest": "^1.9.1" + "@loopback/core": "^1.3.0", + "@loopback/rest": "^1.10.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "keywords": [ "loopback", diff --git a/examples/log-extension/CHANGELOG.md b/examples/log-extension/CHANGELOG.md index 64740c2bd4af..f92176e42403 100644 --- a/examples/log-extension/CHANGELOG.md +++ b/examples/log-extension/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.9](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@1.1.8...@loopback/example-log-extension@1.1.9) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-log-extension + + + + + ## [1.1.8](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@1.1.7...@loopback/example-log-extension@1.1.8) (2019-03-22) **Note:** Version bump only for package @loopback/example-log-extension diff --git a/examples/log-extension/package-lock.json b/examples/log-extension/package-lock.json index 028b283366e4..10566cb7f834 100644 --- a/examples/log-extension/package-lock.json +++ b/examples/log-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-log-extension", - "version": "1.1.8", + "version": "1.1.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -222,9 +222,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -291,9 +291,9 @@ } }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, "sprintf-js": { @@ -326,9 +326,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -343,7 +343,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -356,9 +356,9 @@ } }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "wrappy": { diff --git a/examples/log-extension/package.json b/examples/log-extension/package.json index af21b566bf2d..57316083d671 100644 --- a/examples/log-extension/package.json +++ b/examples/log-extension/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-log-extension", - "version": "1.1.8", + "version": "1.1.9", "description": "An example extension project for LoopBack 4", "main": "index.js", "engines": { @@ -42,19 +42,19 @@ }, "homepage": "https://github.com/strongloop/loopback-next/tree/master/examples/log-extension", "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/debug": "^4.1.0", "@types/node": "^10.11.2", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/rest": "^1.9.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/rest": "^1.10.0", "chalk": "^2.3.2", "debug": "^4.0.1" } diff --git a/examples/rpc-server/CHANGELOG.md b/examples/rpc-server/CHANGELOG.md index b81f75d730c6..453c2619439e 100644 --- a/examples/rpc-server/CHANGELOG.md +++ b/examples/rpc-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.8](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@1.1.7...@loopback/example-rpc-server@1.1.8) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-rpc-server + + + + + ## [1.1.7](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@1.1.6...@loopback/example-rpc-server@1.1.7) (2019-03-22) **Note:** Version bump only for package @loopback/example-rpc-server diff --git a/examples/rpc-server/package-lock.json b/examples/rpc-server/package-lock.json index ac59885f3e8d..d7407c1d9d1b 100644 --- a/examples/rpc-server/package-lock.json +++ b/examples/rpc-server/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-rpc-server", - "version": "1.1.7", + "version": "1.1.8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -459,9 +459,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -553,9 +553,9 @@ } }, "p-event": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz", - "integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", "requires": { "p-timeout": "^2.0.1" } @@ -645,9 +645,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, "send": { @@ -719,9 +719,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -736,7 +736,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -758,9 +758,9 @@ } }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "unpipe": { diff --git a/examples/rpc-server/package.json b/examples/rpc-server/package.json index 449f9615ebbc..39baab30ab57 100644 --- a/examples/rpc-server/package.json +++ b/examples/rpc-server/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-rpc-server", - "version": "1.1.7", + "version": "1.1.8", "description": "A basic RPC server using a made-up protocol.", "keywords": [ "loopback-application", @@ -38,18 +38,18 @@ "author": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", "express": "^4.16.3", - "p-event": "^4.0.0" + "p-event": "^4.1.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/express": "^4.11.1", "@types/node": "^10.11.2", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" } } diff --git a/examples/soap-calculator/CHANGELOG.md b/examples/soap-calculator/CHANGELOG.md index b380761ac50f..cd7d07ba4928 100644 --- a/examples/soap-calculator/CHANGELOG.md +++ b/examples/soap-calculator/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.4.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@1.4.3...@loopback/example-soap-calculator@1.4.4) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-soap-calculator + + + + + ## [1.4.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@1.4.2...@loopback/example-soap-calculator@1.4.3) (2019-03-22) **Note:** Version bump only for package @loopback/example-soap-calculator diff --git a/examples/soap-calculator/package-lock.json b/examples/soap-calculator/package-lock.json index 327d6b46d3e0..62c05388b4e2 100644 --- a/examples/soap-calculator/package-lock.json +++ b/examples/soap-calculator/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-soap-calculator", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1651,9 +1651,9 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", "optional": true }, "nanomatch": { @@ -1925,11 +1925,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -1977,6 +1972,13 @@ "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "require-directory": { @@ -2376,27 +2378,27 @@ "dev": true }, "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", "requires": { "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", + "debug": "^4.1.1", + "globalize": "^1.4.2", "lodash": "^4.17.4", "md5": "^2.2.1", "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", + "os-locale": "^3.1.0", "yamljs": "^0.3.0" } }, "strong-soap": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/strong-soap/-/strong-soap-1.17.0.tgz", - "integrity": "sha512-lFKBMFh3upFlm1LL5hSOzdQVklXIQIn+al3Xi7Rxt2K+N8myE6gdwDG8TqLP6Qy0zEVHi6Go8tdQHLKq5m18Gw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/strong-soap/-/strong-soap-1.18.0.tgz", + "integrity": "sha512-xmqm2YqjdAhv2CF26sLjH9pPIGiEhU75m0pDcsybkjTKrx9Ok1Vjp62GOVjUjlKRo2IVe77jlsDdIMDcEA5IWA==", "requires": { "compress": "^0.99.0", - "debug": "^3.1.0", + "debug": "^4.1.1", "httpntlm": "^1.7.6", "lodash": "^4.13.1", "optional": "^0.1.3", @@ -2404,21 +2406,11 @@ "request": "^2.72.0", "sax": "^1.2", "selectn": "^1.0.20", - "strong-globalize": "^4.1.1", + "strong-globalize": "^4.1.3", "strong-ursa": "^0.10.1", "uuid": "^3.2.1", - "xml-crypto": "^0.10.1", - "xmlbuilder": "^9.0.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - } + "xml-crypto": "^1.3.0", + "xmlbuilder": "^10.1.1" } }, "strong-ursa": { @@ -2505,9 +2497,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -2522,7 +2514,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -2548,9 +2540,9 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "underscore": { @@ -2752,28 +2744,28 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xml-crypto": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-0.10.1.tgz", - "integrity": "sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-1.3.0.tgz", + "integrity": "sha512-Kx/owhke7oy89NAB8HTkaENc1BaCixQDHD6Wg61VTIOdjBlIRLNs2Ts76MhJz78EPyOMoqUoY4ytShCqbv1XBA==", "requires": { - "xmldom": "=0.1.19", - "xpath.js": ">=0.0.3" + "xmldom": "0.1.27", + "xpath": "0.0.27" } }, "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==" }, "xmldom": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.19.tgz", - "integrity": "sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=" - }, - "xpath.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", - "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==" + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + }, + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" }, "y18n": { "version": "4.0.0", diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index 782f39cf7df1..6425dd487326 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-soap-calculator", - "version": "1.4.3", + "version": "1.4.4", "description": "Integrate a SOAP webservice with LoopBack 4", "keywords": [ "loopback", @@ -41,25 +41,25 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^1.1.2", - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/repository": "^1.2.1", - "@loopback/rest": "^1.9.1", - "@loopback/rest-explorer": "^1.1.13", - "@loopback/service-proxy": "^1.1.1", + "@loopback/boot": "^1.1.3", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/repository": "^1.3.0", + "@loopback/rest": "^1.10.0", + "@loopback/rest-explorer": "^1.1.14", + "@loopback/service-proxy": "^1.1.2", "loopback-connector-soap": "^5.0.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/mocha": "^5.0.0", "@types/node": "^10.11.2", "mocha": "^6.0.0", "source-map-support": "^0.5.5", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" } } diff --git a/examples/todo-list/CHANGELOG.md b/examples/todo-list/CHANGELOG.md index fbbc465c51fd..50b3845576e7 100644 --- a/examples/todo-list/CHANGELOG.md +++ b/examples/todo-list/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.5.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@1.5.3...@loopback/example-todo-list@1.5.4) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-todo-list + + + + + ## [1.5.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@1.5.2...@loopback/example-todo-list@1.5.3) (2019-03-22) **Note:** Version bump only for package @loopback/example-todo-list diff --git a/examples/todo-list/package-lock.json b/examples/todo-list/package-lock.json index 17f86cb10a01..fe8bcda0f4c7 100644 --- a/examples/todo-list/package-lock.json +++ b/examples/todo-list/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo-list", - "version": "1.5.3", + "version": "1.5.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -488,9 +488,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -560,6 +560,13 @@ "request": "^2.53.0", "strong-globalize": "^4.1.1", "traverse": "^0.6.6" + }, + "dependencies": { + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" + } } }, "map-age-cleaner": { @@ -581,12 +588,12 @@ } }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, @@ -595,11 +602,6 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, - "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" - }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", @@ -614,9 +616,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "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", @@ -736,9 +738,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "request": { "version": "2.88.0", @@ -857,17 +859,17 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", "requires": { "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", + "debug": "^4.1.1", + "globalize": "^1.4.2", "lodash": "^4.17.4", "md5": "^2.2.1", "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", + "os-locale": "^3.1.0", "yamljs": "^0.3.0" } }, @@ -905,9 +907,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -922,7 +924,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -948,9 +950,9 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "uri-js": { diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index f02d0e8ba48c..72ba9a85130a 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo-list", - "version": "1.5.3", + "version": "1.5.4", "description": "Continuation of the todo example using relations in LoopBack 4.", "main": "index.js", "engines": { @@ -36,27 +36,27 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^1.1.2", - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/openapi-v3-types": "^1.0.10", - "@loopback/repository": "^1.2.1", - "@loopback/rest": "^1.9.1", - "@loopback/rest-explorer": "^1.1.13", - "@loopback/service-proxy": "^1.1.1", + "@loopback/boot": "^1.1.3", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/openapi-v3-types": "^1.0.11", + "@loopback/repository": "^1.3.0", + "@loopback/rest": "^1.10.0", + "@loopback/rest-explorer": "^1.1.14", + "@loopback/service-proxy": "^1.1.2", "loopback-connector-rest": "^3.1.1" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/http-caching-proxy": "^1.0.11", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/http-caching-proxy": "^1.0.12", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/lodash": "^4.14.109", "@types/node": "^10.11.2", "lodash": "^4.17.10", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "keywords": [ "loopback", diff --git a/examples/todo/CHANGELOG.md b/examples/todo/CHANGELOG.md index 44527a19b22b..ee888bc10578 100644 --- a/examples/todo/CHANGELOG.md +++ b/examples/todo/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.5.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@1.5.3...@loopback/example-todo@1.5.4) (2019-04-05) + +**Note:** Version bump only for package @loopback/example-todo + + + + + ## [1.5.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@1.5.2...@loopback/example-todo@1.5.3) (2019-03-22) **Note:** Version bump only for package @loopback/example-todo diff --git a/examples/todo/package-lock.json b/examples/todo/package-lock.json index 21287d44a613..744451d07ecd 100644 --- a/examples/todo/package-lock.json +++ b/examples/todo/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo", - "version": "1.5.3", + "version": "1.5.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -488,9 +488,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -560,6 +560,13 @@ "request": "^2.53.0", "strong-globalize": "^4.1.1", "traverse": "^0.6.6" + }, + "dependencies": { + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" + } } }, "map-age-cleaner": { @@ -581,12 +588,12 @@ } }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, @@ -595,11 +602,6 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, - "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" - }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", @@ -614,9 +616,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "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", @@ -736,9 +738,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "request": { "version": "2.88.0", @@ -857,17 +859,17 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", "requires": { "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", + "debug": "^4.1.1", + "globalize": "^1.4.2", "lodash": "^4.17.4", "md5": "^2.2.1", "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", + "os-locale": "^3.1.0", "yamljs": "^0.3.0" } }, @@ -905,9 +907,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -922,7 +924,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -948,9 +950,9 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "uri-js": { diff --git a/examples/todo/package.json b/examples/todo/package.json index bfa964f64768..6456a246c897 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo", - "version": "1.5.3", + "version": "1.5.4", "description": "Tutorial example on how to build an application with LoopBack 4.", "main": "index.js", "engines": { @@ -36,27 +36,27 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^1.1.2", - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/openapi-v3-types": "^1.0.10", - "@loopback/repository": "^1.2.1", - "@loopback/rest": "^1.9.1", - "@loopback/rest-explorer": "^1.1.13", - "@loopback/service-proxy": "^1.1.1", + "@loopback/boot": "^1.1.3", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/openapi-v3-types": "^1.0.11", + "@loopback/repository": "^1.3.0", + "@loopback/rest": "^1.10.0", + "@loopback/rest-explorer": "^1.1.14", + "@loopback/service-proxy": "^1.1.2", "loopback-connector-rest": "^3.1.1" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/http-caching-proxy": "^1.0.11", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/http-caching-proxy": "^1.0.12", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/lodash": "^4.14.109", "@types/node": "^10.11.2", "lodash": "^4.17.10", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "keywords": [ "loopback", diff --git a/greenkeeper.json b/greenkeeper.json index 158cf7ba1118..90726f094cf9 100644 --- a/greenkeeper.json +++ b/greenkeeper.json @@ -6,6 +6,7 @@ "benchmark/package.json", "docs/package.json", "examples/express-composition/package.json", + "examples/greeter-extension/package.json", "examples/hello-world/package.json", "examples/log-extension/package.json", "examples/rpc-server/package.json", diff --git a/package-lock.json b/package-lock.json index d80107ca79d5..975814a31764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4175,9 +4175,9 @@ "dev": true }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -6532,9 +6532,9 @@ "dev": true }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -6549,7 +6549,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" } }, "tsutils": { @@ -6583,9 +6583,9 @@ "dev": true }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 1b710b66980e..c15c87bacfd5 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "cz-conventional-changelog": "^2.1.0", "husky": "^1.3.1", "lerna": "^3.13.0", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "scripts": { "postinstall": "lerna bootstrap", @@ -30,6 +30,7 @@ "update-package-locks": "lerna clean && lerna bootstrap --no-ci", "update-template-deps": "node bin/update-template-deps -f", "update-greenkeeper-json": "node bin/update-greenkeeper-json -f", + "update-all-deps": "npm update && lerna exec -- npm update && npm run update-package-locks", "sync-dev-deps": "node bin/sync-dev-deps", "version": "npm run update-packages && git add greenkeeper.json \"**/package-lock.json\" && npm run update-template-deps && npm run apidocs", "outdated": "npm outdated --depth 0 && lerna exec --no-bail \"npm outdated --depth 0\"", @@ -37,7 +38,7 @@ "coverage:ci": "node packages/build/bin/run-nyc report --reporter=text-lcov | coveralls", "precoverage": "npm test", "coverage": "open coverage/index.html", - "lint": "npm run prettier:check && npm run tslint", + "lint": "npm run prettier:check && npm run tslint && node bin/check-package-locks", "lint:fix": "npm run tslint:fix && npm run prettier:fix", "tslint": "node packages/build/bin/run-tslint --project tsconfig.json", "tslint:fix": "npm run tslint -- --fix", diff --git a/packages/authentication/CHANGELOG.md b/packages/authentication/CHANGELOG.md index 4986ff2ea312..3eb136779eb4 100644 --- a/packages/authentication/CHANGELOG.md +++ b/packages/authentication/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.1.0](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@1.0.17...@loopback/authentication@1.1.0) (2019-04-05) + + +### Features + +* design auth system with user scenario ([124c078](https://github.com/strongloop/loopback-next/commit/124c078)) + + + + + ## [1.0.17](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@1.0.16...@loopback/authentication@1.0.17) (2019-03-22) **Note:** Version bump only for package @loopback/authentication diff --git a/packages/authentication/README.md b/packages/authentication/README.md index 9eb0eb988dfd..74c598e1df11 100644 --- a/packages/authentication/README.md +++ b/packages/authentication/README.md @@ -10,6 +10,10 @@ component, it is not production ready.** The component demonstrates how to leverage Passport module and extension points provided by LoopBack 4 to implement an authentication layer. +To handle multiple authentication strategies without using the Passport module, +please read +[Multiple Authentication strategies](./packages/authentication/docs/authentication-system.md). + ## Installation ```shell diff --git a/packages/authentication/docs/authentication-action.md b/packages/authentication/docs/authentication-action.md new file mode 100644 index 000000000000..f2e78c982348 --- /dev/null +++ b/packages/authentication/docs/authentication-action.md @@ -0,0 +1,26 @@ +### Auth action + +```ts +import * as HttpErrors from 'http-errors'; + +async action(request: Request): Promise { + const authStrategy = await this.getAuthStrategy(); + if (!authStrategy) { + // The invoked operation does not require authentication. + return undefined; + } + + try { + const userProfile: UserProfile = await authStrategy.authenticate(request); + this.setCurrentUser(userProfile); + // a convenient return for the next request handlers + return userProfile; + } catch (err) { + // interpret the raw error code/msg here and throw the corresponding HTTP error + // convert it to http error + if (err.code == '401') { + throw new HttpErrors.Unauthorized(err.message); + } + } + } +``` diff --git a/packages/authentication/docs/authentication-strategy.md b/packages/authentication/docs/authentication-strategy.md new file mode 100644 index 000000000000..11a11397b413 --- /dev/null +++ b/packages/authentication/docs/authentication-strategy.md @@ -0,0 +1,20 @@ +### Auth strategy interface + +```ts +import {Request} from '@loopback/rest'; + +interface AuthenticationStrategy { + // The resolver will read the `options` object from metadata, then invoke the + // `authenticate` with `options` if it exists. + authenticate( + request: Request, + options: object, + ): Promise; + + // This is a private function that extracts credential fields from a request, + // it is called in function `authenticate`. You could organize the extraction + // logic in this function or write them in `authenticate` directly without defining + // this extra utility. + private extractCredentials?(request: Request): Promise; +} +``` diff --git a/packages/authentication/docs/authentication-system.md b/packages/authentication/docs/authentication-system.md new file mode 100644 index 000000000000..d9d1fd4eeeb8 --- /dev/null +++ b/packages/authentication/docs/authentication-system.md @@ -0,0 +1,165 @@ +## Multiple Authentication strategies + +An authentication system in a LoopBack 4 application could potentially support +multiple popular strategies, including basic auth, oauth2, saml, openid-connect, +etc...And also allow programmers to use either a token based or a session based +approach to track the logged-in user. + +The diagram below illustrates the high level abstraction of such an extensible +authentication system. + + + +Assume the app has a static login page with a list of available choices for +users to login: + +- local: basic auth with email/username + password +- facebook account: oauth2 +- google account: oauth2 +- ibm intranet account: saml +- openid account: openid-connect +- ... + +For the local login, we retrieve the user from a local database. + +For the third-party service login, e.g. facebook account login, we retrieve the +user info from the facebook authorization server using oauth2, then find or +create the user in the local database. + +By clicking any one of the links, you login with a particular account and your +status will be tracked in a session(with session-based auth), or your profile +will be encoded into a JWT token(with token-based auth). + +A common flow for all the login strategies would be: the authentication action +verifies the credentials and returns the raw information of that logged-in user. + +Here the raw information refers to the data returned from a third-party service +or a persistent database. Therefore you need another step to convert it to a +user profile instance which describes your application's user model. Finally the +user profile is either tracked by a generated token OR a session + cookie. + +The next diagram illustrates the flow of verifying the client requests sent +after the user has logged in. + + + +The request goes through the authentication action which invokes the +authentication strategy to decode/deserialize the user profile from the +token/session, binds it to the request context so that actions after +'authenticate' could inject it using DI. + +Next let's walk through the typical API flow of user login and user +verification. + +## API Flows (using BasicAuth + JWT as example) + +Other than the LoopBack core and its authentication module, there are different +parts included and integrated together to perform the authentication. + +The next diagram, using the BasicAuth + JWT authentication strategy as an +example, draws two API flows: + +- Login: user login with email+password +- Verify: verify the logged-in user + +along with the responsibilities divided among different parts: + +- LoopBack core: resolve a strategy based on the endpoint's corresponding + authentication metadata, execute the authentication action which invokes the + strategy's `authenticate` method. + +- Authentication strategy: + + - (login flow) verify user credentials and return a user profile(it's up to + the programmer to create the JWT access token inside the controller + function). + - (verify flow) verify the token and decode user profile from it. + +- Authentication services: some utility services that can be injected in the + strategy class. (Each service's functionalities will be covered in the next + section) + +_Note: FixIt! the step 6 in the following diagram should be moved to LoopBack +side_ + + + +_Note: Another section for session based auth TBD_ + +## Authentication framework architecture + +The following diagram describes the architecture of the entire authentication +framework and the detailed responsibility of each part. + +You can check the pseudo code in folder `docs` for: + +- [authentication-action](./authentication-action.md) +- [authentication-strategy](./authentication-strategy.md) +- [basic auth strategy](./strategies/basic-auth.md) +- [jwt strategy](./strategies/jwt.md) +- [oauth2 strategy](./strategies/oauth2.md) +- [endpoints defined in controller](./controller-functions.md) + +And the abstractions for: + +- [user service](../src/services/user.service.ts) +- [token service](../src/services/token.service.ts) + + + +### Token based authentication + +- Login flow + + - authentication action: + - resolve metadata to get the strategy + - invoke strategy.authenticate() + - set the current user as the return of strategy.authenticate() + - strategy: + - extract credentials from + - transport layer(request) + - or local configuration file + - verify credentials and return the user profile (call user service) + - controller function: + - generate token (call token service) + - return token or serialize it into the response + +- Verify flow + - authentication action: + - resolve metadata to get the strategy + - invoke strategy.authenticate() + - set the current user as the return of strategy.authenticate() + - strategy: + - extract access token from transport layer(request) + - verify access token(call token service) + - decode user from access token(call token service) + - return user + - controller: + - process the injected user + +### Session based authentication + +- Login flow + + - authentication action: + - resolve metadata to get the strategy + - invoke strategy.authenticate() + - strategy: + - extract credentials from + - transport layer (request) + - or local configuration file + - verify credentials (call user service) and return the user profile + - controller: + - serialize user info into the session + +- Verify flow + - authentication action: + - resolve metadata to get the strategy + - invoke strategy.authenticate() + - set the current user as the return of strategy.authenticate() + - strategy: + - extract session info from cookie(call session service) + - deserialize user info from session(call session service) + - return user + - controller function: + - process the injected user diff --git a/packages/authentication/docs/controller-functions.md b/packages/authentication/docs/controller-functions.md new file mode 100644 index 000000000000..6d42d816ea04 --- /dev/null +++ b/packages/authentication/docs/controller-functions.md @@ -0,0 +1,112 @@ +## Endpoint definitions + +The following decorated controller functions demos the endpoints described at +the beginning of markdown file +[authentication-system](./authentication-system.md). + +Please note how they are decorated with `@authenticate()`, the syntax is: +`@authenticate(strategy_name, options)` + +- /login + +```ts +class LoginController { + @post('/login', APISpec) + login() { + // static route + } +} +``` + +- /loginWithLocal + +```ts + +const RESPONSE_SPEC_FOR_JWT_LOGIN = { + responses: { + '200': { + description: 'Token', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + token: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, +}; + +class LoginController{ + constructor( + @inject(AuthenticationBindings.CURRENT_USER) userProfile: UserProfile, + @inject(AuthenticationBindings.SERVICES.JWT_TOKEN) JWTtokenService: TokenService, + ) {} + + // I was about to create a local login example, while if the credentials are + // provided in the request body, all the authenticate logic will happen in the + // controller, the auth action isn't even involved. + // See the login endpoint in shopping example + // https://github.com/strongloop/loopback4-example-shopping/blob/master/src/controllers/user.controller.ts#L137 + + // Describe the response using OpenAPI spec + @post('/loginOAI/basicAuth', RESPONSE_SPEC_FOR_JWT_LOGIN) + @authenticate('basicAuth') + basicAuthLoginReturningJWTToken() { + await token = JWTtokenService.generateToken(this.userProfile); + // Action `send` will serialize token into response according to the OpenAPI spec. + return token; + } + + // OR + // Serialize the token into response in the controller directly without describing it + // with OpenAPI spec + @post('/loginWithoutOAI/basicAuth') + @authenticate('basicAuth') + basicAuthLoginReturningJWTToken() { + await token = JWTtokenService.generateToken(this.userProfile); + // It's on users to serialize the token into the response. + await writeTokenToResponse(); + } +} +``` + +```ts +class UserOrdersController { + @get('Users/me/orders', ...APISpec) + @authenticate('jwt') + getOrders() { + // The `userProfile` is set in the authentication action + // and get injected in the controller constructor + const id = this.userProfile.id; + await this.userRepo(id).orders(); + } +} +``` + +Other auth strategies like oauth2 will be determined in another story. + +- /loginWithFB + +```ts +class UserController { + @post('/loginWithFB', APISpec) + @authenticate('oath2.fb', {session: false}) + loginWithFB() {} +} +``` + +- /loginWithGoogle + +```ts +class UserController { + @post('/loginWithGoogle', APISpec) + @authenticate('oath2.google', {session: true}) + loginWithGoogle() {} +} +``` diff --git a/packages/authentication/docs/imgs/API-flow-(JWT).png b/packages/authentication/docs/imgs/API-flow-(JWT).png new file mode 100644 index 000000000000..3778e3860638 Binary files /dev/null and b/packages/authentication/docs/imgs/API-flow-(JWT).png differ diff --git a/packages/authentication/docs/imgs/auth-framework-architecture.png b/packages/authentication/docs/imgs/auth-framework-architecture.png new file mode 100644 index 000000000000..c440ffc3d792 Binary files /dev/null and b/packages/authentication/docs/imgs/auth-framework-architecture.png differ diff --git a/packages/authentication/docs/imgs/multiple-auth-strategies-login.png b/packages/authentication/docs/imgs/multiple-auth-strategies-login.png new file mode 100644 index 000000000000..863d0ee3e0f1 Binary files /dev/null and b/packages/authentication/docs/imgs/multiple-auth-strategies-login.png differ diff --git a/packages/authentication/docs/imgs/multiple-auth-strategies-verify.png b/packages/authentication/docs/imgs/multiple-auth-strategies-verify.png new file mode 100644 index 000000000000..42a14363eef6 Binary files /dev/null and b/packages/authentication/docs/imgs/multiple-auth-strategies-verify.png differ diff --git a/packages/authentication/docs/strategies/basic-auth.md b/packages/authentication/docs/strategies/basic-auth.md new file mode 100644 index 000000000000..19af3cd7f984 --- /dev/null +++ b/packages/authentication/docs/strategies/basic-auth.md @@ -0,0 +1,33 @@ +You could find the `AuthenticationStrategy` interface in file +[authentication-strategy.md](./docs/authentication-strategy.md) + +```ts +import {Request} from '@loopback/rest'; + +interface BasicAuthOptions = { + // Define it as anyobject in the pseudo code + [property: string]: any; +}; + +class BasicAuthenticationStrategy implements AuthenticationStrategy { + options: object; + constructor( + @inject(AUTHENTICATION_BINDINGS.USER_SERVICE) userService: UserService, + @inject(AUTHENTICATION_BINDINGS.BASIC_AUTH_OPTIONS) options?: BasicAuthOptions, + ) {} + + authenticate(request: Request, options: BasicAuthOptions): Promise { + // override the global set options with the one passed from the caller + options = options || this.options; + // extract the username and password from request + const credentials = await this.extractCredentials(request); + // `verifyCredentials` throws error accordingly: user doesn't exist OR invalid credentials + const user = await userService.verifyCredentials(credentials); + return await userService.convertToUserProfile(user); + } + + extractCredentials(request): Promise { + // code to extract username and password from request header + } +} +``` diff --git a/packages/authentication/docs/strategies/jwt.md b/packages/authentication/docs/strategies/jwt.md new file mode 100644 index 000000000000..67278a7f6a14 --- /dev/null +++ b/packages/authentication/docs/strategies/jwt.md @@ -0,0 +1,34 @@ +You could find the `AuthenticationStrategy` interface in file +[authentication-strategy.md](./docs/authentication-strategy.md) + +```ts +import {Request} from '@loopback/rest'; + +interface JWTAuthOptions = { + // Define it as anyobject in the pseudo code + [property: string]: any; +}; +class JWTAuthenticationStrategy implements AuthenticationStrategy { + constructor( + @inject(AUTHENTICATION_BINDINGS.USER_SERVICE) tokenService: TokenService, + @inject(AUTHENTICATION_BINDINGS.JWT_AUTH_OPTIONS) options?: JWTAuthOptions, + ) {} + + authenticate( + request: Request, + options: JWTAuthOptions, + ): Promise { + // override the global set options with the one passed from the caller + options = options || this.options; + // extract the username and password from request + const token = await this.extractCredentials(request); + // `verifyToken` should decode the payload from the token and convert the token payload to + // userProfile object. + return await tokenService.verifyToken(token); + } + + extractCredentials(request): Promise { + // code to extract json web token from request header/cookie/query + } +} +``` diff --git a/packages/authentication/docs/strategies/oauth2.md b/packages/authentication/docs/strategies/oauth2.md new file mode 100644 index 000000000000..0fef78611b05 --- /dev/null +++ b/packages/authentication/docs/strategies/oauth2.md @@ -0,0 +1,3 @@ +### OAuth2 Strategy + +TBD in a forthcoming PR. diff --git a/packages/authentication/docs/strategies/openid.md b/packages/authentication/docs/strategies/openid.md new file mode 100644 index 000000000000..a0990367ef8b --- /dev/null +++ b/packages/authentication/docs/strategies/openid.md @@ -0,0 +1 @@ +TBD diff --git a/packages/authentication/docs/strategies/saml.md b/packages/authentication/docs/strategies/saml.md new file mode 100644 index 000000000000..a0990367ef8b --- /dev/null +++ b/packages/authentication/docs/strategies/saml.md @@ -0,0 +1 @@ +TBD diff --git a/packages/authentication/docs/track-user-profile/session-based-auth.md b/packages/authentication/docs/track-user-profile/session-based-auth.md new file mode 100644 index 000000000000..a0990367ef8b --- /dev/null +++ b/packages/authentication/docs/track-user-profile/session-based-auth.md @@ -0,0 +1 @@ +TBD diff --git a/packages/authentication/docs/track-user-profile/token-based-auth.md b/packages/authentication/docs/track-user-profile/token-based-auth.md new file mode 100644 index 000000000000..a0990367ef8b --- /dev/null +++ b/packages/authentication/docs/track-user-profile/token-based-auth.md @@ -0,0 +1 @@ +TBD diff --git a/packages/authentication/docs/transport-layer-based-extraction/extract-current-user.md b/packages/authentication/docs/transport-layer-based-extraction/extract-current-user.md new file mode 100644 index 000000000000..f735ad4820c3 --- /dev/null +++ b/packages/authentication/docs/transport-layer-based-extraction/extract-current-user.md @@ -0,0 +1,9 @@ +## Extracting current user + +- option1: session + - cookie +- option2: token + - header + - query + - request body + - ... diff --git a/packages/authentication/package-lock.json b/packages/authentication/package-lock.json index c808aa4aac71..95948e290ae3 100644 --- a/packages/authentication/package-lock.json +++ b/packages/authentication/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/authentication", - "version": "1.0.17", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/authentication/package.json b/packages/authentication/package.json index f28c7884bca4..ec2b29c373e9 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/authentication", - "version": "1.0.17", + "version": "1.1.0", "description": "A LoopBack component for authentication support.", "engines": { "node": ">=8.9" @@ -20,19 +20,19 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/metadata": "^1.0.10", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/rest": "^1.9.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/metadata": "^1.0.11", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/rest": "^1.10.0", "passport": "^0.4.0", "passport-strategy": "^1.0.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/openapi-spec-builder": "^1.1.2", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/openapi-spec-builder": "^1.1.3", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2", "@types/passport": "^1.0.0", "@types/passport-http": "^0.3.6", diff --git a/packages/authentication/src/services/session.service.ts b/packages/authentication/src/services/session.service.ts new file mode 100644 index 000000000000..b070838cdba0 --- /dev/null +++ b/packages/authentication/src/services/session.service.ts @@ -0,0 +1,2 @@ +// TBD: create a new story to design session service, it will be about tracking the user using +// cookies + sessions. diff --git a/packages/authentication/src/services/token.service.ts b/packages/authentication/src/services/token.service.ts new file mode 100644 index 000000000000..f25f77437052 --- /dev/null +++ b/packages/authentication/src/services/token.service.ts @@ -0,0 +1,6 @@ +import {UserProfile} from '../types'; + +export interface TokenService { + verifyToken(token: string): Promise; + generateToken(userProfile: UserProfile): Promise; +} diff --git a/packages/authentication/src/services/user.service.ts b/packages/authentication/src/services/user.service.ts new file mode 100644 index 000000000000..a4e02dee3290 --- /dev/null +++ b/packages/authentication/src/services/user.service.ts @@ -0,0 +1,92 @@ +import {UserProfile} from '../types'; + +/** + * A service for performing the login action in an authentication strategy. + * + * Usually a client user uses basic credentials to login, or is redirected to a + * third-party application that grants limited access. + * + * + * Note: The creation of user is handled in the user controller by calling user repository APIs. + * For Basic auth, the user has to register first using some endpoint like `/register`. + * For 3rd-party auth, the user will be created if login is successful + * and the user doesn't exist in database yet. + * + * Type `C` stands for the type of your credential object. + * + * - For local strategy: + * + * A typical credential would be: + * { + * username: username, + * password: password + * } + * + * - For oauth strategy: + * + * A typical credential would be: + * { + * clientId: string; + * clientSecret: string; + * callbackURL: string; + * } + * + * It could be read from a local configuration file in the app + * + * - For saml strategy: + * + * A typical credential would be: + * + * { + * path: string; + * issuer: string; + * entryPoint: string; + * } + * + * It could be read from a local configuration file in the app. + */ +export interface UserService { + /** + * Verify the identity of a user, construct a corresponding user profile using + * the user information and return the user profile. + * + * A pseudo code for basic authentication: + * ```ts + * verifyCredentials(credentials: C): Promise { + * // the id field shouldn't be hardcoded + * user = await UserRepo.find(credentials.id); + * matched = await passwordService.compare(user.password, credentials.password); + * if (matched) return user; + * // throw a JS error, agnostic of the client type + * throw new Error('authentication failed'); + * }; + * ``` + * + * A pseudo code for 3rd party authentication: + * ```ts + * type UserInfo = { + * accessToken: string; + * refreshToken: string; + * userProfile: string; + * }; + * verifyCredentials(credentials: C): Promise { + * try { + * userInfo: UserInfo = await getUserInfoFromFB(credentials); + * } catch (e) { + * // throw a JS error, agnostic of the client type + * throw e; + * } + * }; + * ``` + * @param credentials Credentials for basic auth or configurations for 3rd party. + * Example see the + */ + verifyCredentials(credentials: C): Promise; + + /** + * Convert the user returned by `verifyCredentials()` to a common + * user profile that describes a user in your application + * @param user The user returned from `verifyCredentials()` + */ + convertToUserProfile(user: U): UserProfile; +} diff --git a/packages/boot/CHANGELOG.md b/packages/boot/CHANGELOG.md index d5d904e8a2b4..112da8f2c37c 100644 --- a/packages/boot/CHANGELOG.md +++ b/packages/boot/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/strongloop/loopback-next/compare/@loopback/boot@1.1.2...@loopback/boot@1.1.3) (2019-04-05) + +**Note:** Version bump only for package @loopback/boot + + + + + ## [1.1.2](https://github.com/strongloop/loopback-next/compare/@loopback/boot@1.1.1...@loopback/boot@1.1.2) (2019-03-22) **Note:** Version bump only for package @loopback/boot diff --git a/packages/boot/package-lock.json b/packages/boot/package-lock.json index 51f2b2bdf4c9..7ebcadbd362a 100644 --- a/packages/boot/package-lock.json +++ b/packages/boot/package-lock.json @@ -1,13 +1,13 @@ { "name": "@loopback/boot", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { "@types/debug": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.2.tgz", - "integrity": "sha512-jkf6UiWUjcOqdQbatbvOm54/YbCdjt3JjiAzT/9KS2XtMmOkYHdKsI5u8fulhbuTUuiqNBfa6J5GSDiwjK+zLA==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.3.tgz", + "integrity": "sha512-PQverGatRgqIhRLracrC8k/j5A6QOASVLR0wuURx6ROQZx3OQ9PnTc/5Xrznny/xaO0TwdxSgGyG90NCzk37Rg==" }, "@types/events": { "version": "3.0.0", @@ -22,13 +22,6 @@ "@types/events": "*", "@types/minimatch": "*", "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/minimatch": { @@ -39,8 +32,7 @@ "@types/node": { "version": "10.12.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", - "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==", - "dev": true + "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==" }, "balanced-match": { "version": "1.0.0", diff --git a/packages/boot/package.json b/packages/boot/package.json index 80825cae9238..3b62d60cfcda 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/boot", - "version": "1.1.2", + "version": "1.1.3", "description": "A collection of Booters for LoopBack 4 Applications", "engines": { "node": ">=8.9" @@ -23,21 +23,21 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/repository": "^1.2.1", - "@loopback/service-proxy": "^1.1.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/repository": "^1.3.0", + "@loopback/service-proxy": "^1.1.2", "@types/debug": "^4.1.0", "@types/glob": "^7.1.1", "debug": "^4.0.1", "glob": "^7.1.2" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/rest": "^1.9.1", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/rest": "^1.10.0", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "files": [ diff --git a/packages/build/CHANGELOG.md b/packages/build/CHANGELOG.md index 2d4fa3241dbb..a2ae5959c152 100644 --- a/packages/build/CHANGELOG.md +++ b/packages/build/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.4.1](https://github.com/strongloop/loopback-next/compare/@loopback/build@1.4.0...@loopback/build@1.4.1) (2019-04-05) + +**Note:** Version bump only for package @loopback/build + + + + + # [1.4.0](https://github.com/strongloop/loopback-next/compare/@loopback/build@1.3.2...@loopback/build@1.4.0) (2019-03-22) diff --git a/packages/build/package-lock.json b/packages/build/package-lock.json index a0660984cc83..8e2fae860f71 100644 --- a/packages/build/package-lock.json +++ b/packages/build/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/build", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13,11 +13,11 @@ } }, "@babel/generator": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", - "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", "requires": { - "@babel/types": "^7.3.4", + "@babel/types": "^7.4.0", "jsesc": "^2.5.1", "lodash": "^4.17.11", "source-map": "^0.5.0", @@ -43,11 +43,11 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.4.0" } }, "@babel/highlight": { @@ -61,40 +61,40 @@ } }, "@babel/parser": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", - "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz", + "integrity": "sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==" }, "@babel/template": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", - "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.2.2", - "@babel/types": "^7.2.2" + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" } }, "@babel/traverse": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", - "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz", + "integrity": "sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", + "@babel/generator": "^7.4.0", "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.3.4", - "@babel/types": "^7.3.4", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.11" } }, "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.11", @@ -138,9 +138,9 @@ "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==" }, "@types/lodash": { - "version": "4.14.122", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.122.tgz", - "integrity": "sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew==" + "version": "4.14.123", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", + "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==" }, "@types/marked": { "version": "0.4.2", @@ -158,9 +158,9 @@ "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==" }, "@types/node": { - "version": "10.12.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", - "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==" + "version": "10.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz", + "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg==" }, "@types/shelljs": { "version": "0.8.3", @@ -241,14 +241,6 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" - } - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -466,9 +458,9 @@ } }, "camelcase": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", - "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.0.tgz", + "integrity": "sha512-Y05ICatFYPAfykDIB7VdwSJ0LUl1yq/BwO2OpyGGLjiRe1fgzTwVypPiWnzkGFOVFHXrCXUNBl86bpjBhZWSJg==" }, "chalk": { "version": "2.4.2", @@ -1096,11 +1088,6 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -1170,11 +1157,11 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" }, "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", + "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", "requires": { - "async": "^2.5.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" @@ -1639,12 +1626,12 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, @@ -1697,9 +1684,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "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", @@ -1809,6 +1796,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -1876,44 +1868,37 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "bundled": true }, "append-transform": { "version": "1.0.0", - "resolved": false, - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "bundled": true, "requires": { "default-require-extensions": "^2.0.0" } }, "archy": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + "bundled": true }, "arrify": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "bundled": true }, "async": { "version": "2.6.2", - "resolved": false, - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "bundled": true, "requires": { "lodash": "^4.17.11" } }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "bundled": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1921,8 +1906,7 @@ }, "caching-transform": { "version": "3.0.1", - "resolved": false, - "integrity": "sha512-Y1KTLNwSPd4ljsDrFOtyXVmm7Gnk42yQitNq43AhE+cwUR/e4T+rmOHs1IPtzBg8066GBJfTOj1rQYFSWSsH2g==", + "bundled": true, "requires": { "hasha": "^3.0.0", "make-dir": "^1.3.0", @@ -1932,13 +1916,11 @@ }, "camelcase": { "version": "5.0.0", - "resolved": false, - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + "bundled": true }, "cliui": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "bundled": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -1947,37 +1929,31 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "bundled": true }, "commander": { "version": "2.17.1", - "resolved": false, - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "bundled": true, "optional": true }, "commondir": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "bundled": true }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "bundled": true }, "convert-source-map": { "version": "1.6.0", - "resolved": false, - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "bundled": true, "requires": { "safe-buffer": "~5.1.1" } }, "cross-spawn": { "version": "4.0.2", - "resolved": false, - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "bundled": true, "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" @@ -1985,50 +1961,43 @@ }, "debug": { "version": "4.1.1", - "resolved": false, - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "bundled": true, "requires": { "ms": "^2.1.1" } }, "decamelize": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "bundled": true }, "default-require-extensions": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "bundled": true, "requires": { "strip-bom": "^3.0.0" } }, "end-of-stream": { "version": "1.4.1", - "resolved": false, - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "bundled": true, "requires": { "once": "^1.4.0" } }, "error-ex": { "version": "1.3.2", - "resolved": false, - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "bundled": true, "requires": { "is-arrayish": "^0.2.1" } }, "es6-error": { "version": "4.1.1", - "resolved": false, - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + "bundled": true }, "execa": { "version": "1.0.0", - "resolved": false, - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "bundled": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -2041,8 +2010,7 @@ "dependencies": { "cross-spawn": { "version": "6.0.5", - "resolved": false, - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "bundled": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -2055,8 +2023,7 @@ }, "find-cache-dir": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "bundled": true, "requires": { "commondir": "^1.0.1", "make-dir": "^1.0.0", @@ -2065,16 +2032,14 @@ }, "find-up": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "bundled": true, "requires": { "locate-path": "^3.0.0" } }, "foreground-child": { "version": "1.5.6", - "resolved": false, - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "bundled": true, "requires": { "cross-spawn": "^4", "signal-exit": "^3.0.0" @@ -2082,26 +2047,22 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "bundled": true }, "get-caller-file": { "version": "1.0.3", - "resolved": false, - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "bundled": true }, "get-stream": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "bundled": true, "requires": { "pump": "^3.0.0" } }, "glob": { "version": "7.1.3", - "resolved": false, - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "bundled": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2113,13 +2074,11 @@ }, "graceful-fs": { "version": "4.1.15", - "resolved": false, - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + "bundled": true }, "handlebars": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "bundled": true, "requires": { "async": "^2.5.0", "optimist": "^0.6.1", @@ -2129,38 +2088,32 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": false, - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "bundled": true } } }, "has-flag": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "bundled": true }, "hasha": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "bundled": true, "requires": { "is-stream": "^1.0.1" } }, "hosted-git-info": { "version": "2.7.1", - "resolved": false, - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "bundled": true }, "imurmurhash": { "version": "0.1.4", - "resolved": false, - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "bundled": true }, "inflight": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2168,51 +2121,42 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true }, "invert-kv": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + "bundled": true }, "is-arrayish": { "version": "0.2.1", - "resolved": false, - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "bundled": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "bundled": true }, "is-stream": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "bundled": true }, "isexe": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "bundled": true }, "istanbul-lib-coverage": { "version": "2.0.3", - "resolved": false, - "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==" + "bundled": true }, "istanbul-lib-hook": { "version": "2.0.3", - "resolved": false, - "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", + "bundled": true, "requires": { "append-transform": "^1.0.0" } }, "istanbul-lib-report": { "version": "2.0.4", - "resolved": false, - "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", + "bundled": true, "requires": { "istanbul-lib-coverage": "^2.0.3", "make-dir": "^1.3.0", @@ -2221,8 +2165,7 @@ "dependencies": { "supports-color": { "version": "6.1.0", - "resolved": false, - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "bundled": true, "requires": { "has-flag": "^3.0.0" } @@ -2231,8 +2174,7 @@ }, "istanbul-lib-source-maps": { "version": "3.0.2", - "resolved": false, - "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", + "bundled": true, "requires": { "debug": "^4.1.1", "istanbul-lib-coverage": "^2.0.3", @@ -2243,36 +2185,31 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": false, - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "bundled": true } } }, "istanbul-reports": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", + "bundled": true, "requires": { "handlebars": "^4.1.0" } }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "bundled": true }, "lcid": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "bundled": true, "requires": { "invert-kv": "^2.0.0" } }, "load-json-file": { "version": "4.0.0", - "resolved": false, - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "bundled": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -2282,8 +2219,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "bundled": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -2291,18 +2227,15 @@ }, "lodash": { "version": "4.17.11", - "resolved": false, - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "bundled": true }, "lodash.flattendeep": { "version": "4.4.0", - "resolved": false, - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + "bundled": true }, "lru-cache": { "version": "4.1.5", - "resolved": false, - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "bundled": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -2310,24 +2243,21 @@ }, "make-dir": { "version": "1.3.0", - "resolved": false, - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "bundled": true, "requires": { "pify": "^3.0.0" } }, "map-age-cleaner": { "version": "0.1.3", - "resolved": false, - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "bundled": true, "requires": { "p-defer": "^1.0.0" } }, "mem": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "bundled": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^1.0.0", @@ -2336,66 +2266,56 @@ }, "merge-source-map": { "version": "1.1.0", - "resolved": false, - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "bundled": true, "requires": { "source-map": "^0.6.1" }, "dependencies": { "source-map": { "version": "0.6.1", - "resolved": false, - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "bundled": true } } }, "mimic-fn": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "bundled": true }, "minimatch": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.10", - "resolved": false, - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "bundled": true }, "mkdirp": { "version": "0.5.1", - "resolved": false, - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "requires": { "minimist": "0.0.8" }, "dependencies": { "minimist": { "version": "0.0.8", - "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "bundled": true } } }, "ms": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "bundled": true }, "nice-try": { "version": "1.0.5", - "resolved": false, - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "bundled": true }, "normalize-package-data": { "version": "2.5.0", - "resolved": false, - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "bundled": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -2405,29 +2325,25 @@ }, "npm-run-path": { "version": "2.0.2", - "resolved": false, - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "bundled": true, "requires": { "path-key": "^2.0.0" } }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "bundled": true }, "once": { "version": "1.4.0", - "resolved": false, - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "requires": { "wrappy": "1" } }, "optimist": { "version": "0.6.1", - "resolved": false, - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "bundled": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -2435,13 +2351,11 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "bundled": true }, "os-locale": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "bundled": true, "requires": { "execa": "^1.0.0", "lcid": "^2.0.0", @@ -2450,44 +2364,37 @@ }, "p-defer": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + "bundled": true }, "p-finally": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "bundled": true }, "p-is-promise": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==" + "bundled": true }, "p-limit": { "version": "2.1.0", - "resolved": false, - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "bundled": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "bundled": true, "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + "bundled": true }, "package-hash": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "bundled": true, "requires": { "graceful-fs": "^4.1.15", "hasha": "^3.0.0", @@ -2497,8 +2404,7 @@ }, "parse-json": { "version": "4.0.0", - "resolved": false, - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "bundled": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -2506,54 +2412,45 @@ }, "path-exists": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "bundled": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "bundled": true }, "path-key": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "bundled": true }, "path-parse": { "version": "1.0.6", - "resolved": false, - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "bundled": true }, "path-type": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "bundled": true, "requires": { "pify": "^3.0.0" } }, "pify": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "bundled": true }, "pkg-dir": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "bundled": true, "requires": { "find-up": "^3.0.0" } }, "pseudomap": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "bundled": true }, "pump": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "bundled": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -2561,8 +2458,7 @@ }, "read-pkg": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "bundled": true, "requires": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", @@ -2571,8 +2467,7 @@ }, "read-pkg-up": { "version": "4.0.0", - "resolved": false, - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "bundled": true, "requires": { "find-up": "^3.0.0", "read-pkg": "^3.0.0" @@ -2580,80 +2475,67 @@ }, "release-zalgo": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "bundled": true, "requires": { "es6-error": "^4.0.1" } }, "require-directory": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "bundled": true }, "require-main-filename": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "bundled": true }, "resolve": { "version": "1.10.0", - "resolved": false, - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "bundled": true, "requires": { "path-parse": "^1.0.6" } }, "resolve-from": { "version": "4.0.0", - "resolved": false, - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "bundled": true }, "rimraf": { "version": "2.6.3", - "resolved": false, - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "bundled": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "bundled": true }, "semver": { "version": "5.6.0", - "resolved": false, - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "bundled": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "bundled": true }, "shebang-command": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "bundled": true, "requires": { "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "bundled": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "bundled": true }, "spawn-wrap": { "version": "1.4.2", - "resolved": false, - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "bundled": true, "requires": { "foreground-child": "^1.5.6", "mkdirp": "^0.5.0", @@ -2665,8 +2547,7 @@ }, "spdx-correct": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "bundled": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -2674,13 +2555,11 @@ }, "spdx-exceptions": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "bundled": true }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "bundled": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -2688,13 +2567,11 @@ }, "spdx-license-ids": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==" + "bundled": true }, "string-width": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "bundled": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2702,26 +2579,22 @@ }, "strip-ansi": { "version": "4.0.0", - "resolved": false, - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "bundled": true, "requires": { "ansi-regex": "^3.0.0" } }, "strip-bom": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "bundled": true }, "strip-eof": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "bundled": true }, "test-exclude": { "version": "5.1.0", - "resolved": false, - "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "bundled": true, "requires": { "arrify": "^1.0.1", "minimatch": "^3.0.4", @@ -2731,8 +2604,7 @@ }, "uglify-js": { "version": "3.4.9", - "resolved": false, - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "bundled": true, "optional": true, "requires": { "commander": "~2.17.1", @@ -2741,21 +2613,18 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": false, - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "bundled": true, "optional": true } } }, "uuid": { "version": "3.3.2", - "resolved": false, - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "bundled": true }, "validate-npm-package-license": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "bundled": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -2763,26 +2632,22 @@ }, "which": { "version": "1.3.1", - "resolved": false, - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "bundled": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "bundled": true }, "wordwrap": { "version": "0.0.3", - "resolved": false, - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "bundled": true }, "wrap-ansi": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "bundled": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -2790,21 +2655,18 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "bundled": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "requires": { "number-is-nan": "^1.0.0" } }, "string-width": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2813,8 +2675,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2823,13 +2684,11 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "bundled": true }, "write-file-atomic": { "version": "2.4.2", - "resolved": false, - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "bundled": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -2838,18 +2697,15 @@ }, "y18n": { "version": "4.0.0", - "resolved": false, - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "bundled": true }, "yallist": { "version": "2.1.2", - "resolved": false, - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "bundled": true }, "yargs": { "version": "12.0.5", - "resolved": false, - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "bundled": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.2.0", @@ -2867,8 +2723,7 @@ }, "yargs-parser": { "version": "11.1.1", - "resolved": false, - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "bundled": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -3012,9 +2867,9 @@ } }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "parse-passwd": { "version": "1.0.0", @@ -3201,9 +3056,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, "send": { "version": "0.16.2", @@ -3438,9 +3293,9 @@ } }, "source-map-support": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", - "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.11.tgz", + "integrity": "sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3622,9 +3477,9 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tslint": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.13.1.tgz", - "integrity": "sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "requires": { "babel-code-frame": "^6.22.0", "builtin-modules": "^1.1.1", @@ -3638,7 +3493,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" }, "dependencies": { "commander": { @@ -3707,9 +3562,9 @@ "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=" }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==" }, "uc.micro": { "version": "1.0.6", @@ -3717,19 +3572,19 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.2.tgz", + "integrity": "sha512-imog1WIsi9Yb56yRt5TfYVxGmnWs3WSGU73ieSOlMVFwhJCA9W8fqFFMMj4kgDqiS/80LGdsYnWL7O9UcjEBlg==", "optional": true, "requires": { - "commander": "~2.17.1", + "commander": "~2.19.0", "source-map": "~0.6.1" }, "dependencies": { "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "optional": true }, "source-map": { @@ -3947,6 +3802,13 @@ "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", "yargs-parser": "^11.1.1" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + } } }, "yargs-parser": { diff --git a/packages/build/package.json b/packages/build/package.json index 11437f32efd2..b1bc9e95090b 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "https://github.com/strongloop/loopback-next.git" }, - "version": "1.4.0", + "version": "1.4.1", "engines": { "node": ">=8.9" }, @@ -14,7 +14,7 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/tslint-config": "^2.0.3", + "@loopback/tslint-config": "^2.0.4", "@types/mocha": "^5.0.0", "@types/node": "^10.11.2", "cross-spawn": "^6.0.5", @@ -25,10 +25,10 @@ "nyc": "^13.3.0", "prettier": "^1.16.1", "rimraf": "^2.6.2", - "source-map-support": "^0.5.5", + "source-map-support": "^0.5.11", "strong-docs": "^4.1.0", - "tslint": "^5.12.0", - "typescript": "^3.3.1" + "tslint": "^5.14.0", + "typescript": "^3.4.1" }, "bin": { "lb-tsc": "./bin/compile-package.js", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index d655a211dab8..c8548c6b1498 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/strongloop/loopback-next/compare/@loopback/cli@1.8.4...@loopback/cli@1.9.0) (2019-04-05) + + +### Features + +* add greeter-extension example ([9b09298](https://github.com/strongloop/loopback-next/commit/9b09298)) + + + + + ## [1.8.4](https://github.com/strongloop/loopback-next/compare/@loopback/cli@1.8.3...@loopback/cli@1.8.4) (2019-03-22) **Note:** Version bump only for package @loopback/cli diff --git a/packages/cli/generators/example/index.js b/packages/cli/generators/example/index.js index d5217d38e784..a988117afec9 100644 --- a/packages/cli/generators/example/index.js +++ b/packages/cli/generators/example/index.js @@ -9,7 +9,6 @@ const BaseGenerator = require('../../lib/base-generator'); const chalk = require('chalk'); const downloadAndExtractExample = require('./downloader'); const path = require('path'); -const utils = require('../../lib/utils'); const fs = require('fs-extra'); const EXAMPLES = { @@ -19,9 +18,11 @@ const EXAMPLES = { 'hello-world': 'A simple hello-world Application using LoopBack 4.', 'log-extension': 'An example extension project for LoopBack 4.', 'rpc-server': 'A basic RPC server using a made-up protocol.', - 'soap-calculator': 'An example on how to integrate SOAP web services', + 'soap-calculator': 'An example on how to integrate SOAP web services.', 'express-composition': 'A simple Express application that uses LoopBack 4 REST API.', + 'greeter-extension': + 'An example showing how to implement the extension point/extension pattern.', }; Object.freeze(EXAMPLES); diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index 03a3d31d0572..31ad4cfc5b95 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/cli", - "version": "1.8.4", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -809,6 +809,13 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "crypto-random-string": { @@ -982,6 +989,13 @@ "requires": { "errlop": "^1.1.1", "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "ejs": { @@ -2733,6 +2747,13 @@ "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "npm-bundled": { @@ -2749,6 +2770,13 @@ "osenv": "^0.1.5", "semver": "^5.5.0", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "npm-packlist": { @@ -2768,6 +2796,13 @@ "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.0.0", "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "npm-registry-fetch": { @@ -2829,56 +2864,11 @@ "yaml": "^1.3.1" } }, - "oas-resolver": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.2.2.tgz", - "integrity": "sha512-+k7Pn/4WDK8ZtMiu09LjXRGbVmcr8h5g76YsQhH9og2zM8fc9MZLdNXuQTWFJG7R6OlHHEru7CFQLmz+tjBQhw==", - "requires": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.7", - "reftools": "^1.0.7", - "yaml": "^1.3.1", - "yargs": "^12.0.5" - } - }, "oas-schema-walker": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.2.tgz", "integrity": "sha512-Q9xqeUtc17ccP/dpUfARci4kwFFszyJAgR/wbDhrRR/73GqsY5uSmKaIK+RmBqO8J4jVYrrDPjQKvt1IcpQdGw==" }, - "oas-validator": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.2.2.tgz", - "integrity": "sha512-LQUmTq96aLqF/tNQkkHJWssODL5hbKSFeIsxeXbAwlM+9lGmhgK3dM0KOYc5QZv9ZTQhf0FYjhOmm1t7klqoJA==", - "requires": { - "ajv": "^5.5.2", - "better-ajv-errors": "^0.5.2", - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.7", - "oas-linter": "^3.0.1", - "oas-resolver": "^2.2.2", - "oas-schema-walker": "^1.1.2", - "reftools": "^1.0.7", - "should": "^13.2.1", - "yaml": "^1.3.1" - }, - "dependencies": { - "better-ajv-errors": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.5.7.tgz", - "integrity": "sha512-O7tpXektKWVwYCH5g6Vs3lKD+sJs7JHh5guapmGJd+RTwxhFZEf4FwvbHBURUnoXsTeFaMvGuhTTmEGiHpNi6w==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/runtime": "^7.0.0", - "chalk": "^2.4.1", - "core-js": "^2.5.7", - "json-to-ast": "^2.0.3", - "jsonpointer": "^4.0.1", - "leven": "^2.1.0" - } - } - } - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -3050,6 +3040,13 @@ "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "pacote": { @@ -3084,6 +3081,13 @@ "tar": "^4.4.8", "unique-filename": "^1.1.1", "which": "^1.3.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "parallel-transform": { @@ -3331,12 +3335,12 @@ } }, "read-chunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.1.0.tgz", - "integrity": "sha512-ZdiZJXXoZYE08SzZvTipHhI+ZW0FpzxmFtLI3vIeMuRN9ySbIZ+SZawKogqJ7dxW9fJ/W73BNtxu4Zu/bZp+Ng==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", + "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", "requires": { "pify": "^4.0.1", - "with-open-file": "^0.1.5" + "with-open-file": "^0.1.6" }, "dependencies": { "pify": { @@ -3412,9 +3416,9 @@ } }, "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -3605,9 +3609,9 @@ "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" }, "semver-diff": { "version": "2.1.0", @@ -3615,6 +3619,13 @@ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "sentence-case": { @@ -3729,18 +3740,31 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.7.tgz", - "integrity": "sha512-rlrre9F80pIQr3M36gOdoCEWzFAMDgHYD8+tocqOw+Zw9OZ8F84a80Ds69eZfcjnzDqqG88ulFld0oin/6rG/g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", + "integrity": "sha512-eQKMaeWovtOtYe2xThEvaHmmxf870Di+bim10c3ZPrL5bZhLGtu8cz+rOBTFz0CwBV4Q/7dYwZiqZbGVLZ+vjQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.1", + "@sinonjs/commons": "^1.4.0", "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.2.0", + "@sinonjs/samsam": "^3.3.1", "diff": "^3.5.0", "lolex": "^3.1.0", "nise": "^1.4.10", "supports-color": "^5.5.0" + }, + "dependencies": { + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + } } }, "slash": { @@ -3872,21 +3896,21 @@ } }, "socks": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", - "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", + "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", "requires": { "ip": "^1.1.5", "smart-buffer": "4.0.2" } }, "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "~4.2.1", + "socks": "~2.3.2" } }, "source-map": { @@ -4113,21 +4137,68 @@ "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=" }, "swagger2openapi": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.2.3.tgz", - "integrity": "sha512-N+Pjf82oHuz0KLQOmaTHkIzscSiCgtQNCOhuWDPmuAboF7oE/p5WxTfP6JOd0bZ5Ri8JGipCgXmJA/8zEr72rA==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.2.5.tgz", + "integrity": "sha512-AZl3kJsIsqQgY2yPIaCUwd62e722CG7qUsCES3/gCPzlfpkXikEdttkH4V2T4ANNSIbZdRI8jI0Yzbp0gi7shA==", "requires": { "better-ajv-errors": "^0.6.1", "call-me-maybe": "^1.0.1", "node-fetch-h2": "^2.3.0", "node-readfiles": "^0.2.0", "oas-kit-common": "^1.0.7", - "oas-resolver": "^2.2.2", + "oas-resolver": "^2.2.3", "oas-schema-walker": "^1.1.2", - "oas-validator": "^3.2.2", + "oas-validator": "^3.2.4", "reftools": "^1.0.7", "yaml": "^1.3.1", "yargs": "^12.0.5" + }, + "dependencies": { + "oas-resolver": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.2.3.tgz", + "integrity": "sha512-C3tQ9gmgVtu4sninufsqvqYreQgDzz6hQmXe5t1TqhnihbANQyW7Wm0+lO/LaguCVtJLyRRdDFxKRKX7xUO0OQ==", + "requires": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.7", + "reftools": "^1.0.7", + "yaml": "^1.3.1", + "yargs": "^12.0.5" + } + }, + "oas-validator": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.2.4.tgz", + "integrity": "sha512-RO4r4Ni4YXS8q3cLCtBxsRS9H3/6LdQM3ncUNuOeAgkRvWXwYH2wl8h0vkIUHXks9af5nPZfzIoUztu+d9QHHg==", + "requires": { + "ajv": "^5.5.2", + "better-ajv-errors": "^0.5.2", + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.7", + "oas-linter": "^3.0.1", + "oas-resolver": "^2.2.3", + "oas-schema-walker": "^1.1.2", + "reftools": "^1.0.7", + "should": "^13.2.1", + "yaml": "^1.3.1" + }, + "dependencies": { + "better-ajv-errors": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.5.7.tgz", + "integrity": "sha512-O7tpXektKWVwYCH5g6Vs3lKD+sJs7JHh5guapmGJd+RTwxhFZEf4FwvbHBURUnoXsTeFaMvGuhTTmEGiHpNi6w==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/runtime": "^7.0.0", + "chalk": "^2.4.1", + "core-js": "^2.5.7", + "json-to-ast": "^2.0.3", + "jsonpointer": "^4.0.1", + "leven": "^2.1.0" + } + } + } + } } }, "swap-case": { @@ -4346,9 +4417,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", + "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==" }, "unicode-10.0.0": { "version": "0.7.5", @@ -4669,13 +4740,25 @@ } }, "with-open-file": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.5.tgz", - "integrity": "sha512-zY51cJCXG6qBilVuMceKNwU3rzjB/Ygt96BuSQ4+tXo3ewlxvj9ET99lpUHNzWfkYmsyfqKZEB9NJORmGZ1Ltw==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.6.tgz", + "integrity": "sha512-SQS05JekbtwQSgCYlBsZn/+m2gpn4zWsqpCYIrCHva0+ojXcnmUEPsBN6Ipoz3vmY/81k5PvYEWSxER2g4BTqA==", "requires": { "p-finally": "^1.0.0", - "p-try": "^2.0.0", - "pify": "^3.0.0" + "p-try": "^2.1.0", + "pify": "^4.0.1" + }, + "dependencies": { + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } } }, "wrap-ansi": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 2689e293fe7c..5d3070d85f0a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/cli", - "version": "1.8.4", + "version": "1.9.0", "description": "Yeoman generator for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/packages/cli", "author": "IBM Corp.", @@ -22,9 +22,9 @@ "yeoman-generator" ], "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/ejs": "^2.6.0", "@types/node": "^10.11.2", "glob": "^7.1.2", @@ -34,7 +34,7 @@ "request": "^2.87.0", "request-promise-native": "^1.0.5", "rimraf": "^2.6.2", - "sinon": "^7.2.3", + "sinon": "^7.3.1", "yeoman-assert": "^3.1.1", "yeoman-environment": "^2.0.6", "yeoman-test": "^1.7.0" @@ -52,7 +52,7 @@ "pacote": "^9.1.0", "pluralize": "^7.0.0", "regenerate": "^1.3.3", - "semver": "^5.5.0", + "semver": "^6.0.0", "swagger-parser": "^6.0.5", "swagger2openapi": "^5.1.0", "typescript": "^3.1.1", @@ -82,40 +82,41 @@ "nyc": "^13.3.0", "prettier": "^1.16.1", "rimraf": "^2.6.2", - "source-map-support": "^0.5.5", + "source-map-support": "^0.5.11", "strong-docs": "^4.1.0", - "tslint": "^5.12.0", - "typescript": "^3.3.1", - "@loopback/authentication": "^1.0.17", - "@loopback/boot": "^1.1.2", - "@loopback/build": "^1.4.0", - "@loopback/cli": "^1.8.4", - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/metadata": "^1.0.10", - "@loopback/openapi-spec-builder": "^1.1.2", - "@loopback/openapi-v3-types": "^1.0.10", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/repository-json-schema": "^1.3.6", - "@loopback/repository": "^1.2.1", - "@loopback/rest": "^1.9.1", - "@loopback/testlab": "^1.2.1", - "@loopback/docs": "^1.11.1", + "tslint": "^5.14.0", + "typescript": "^3.4.1", + "@loopback/authentication": "^1.1.0", + "@loopback/boot": "^1.1.3", + "@loopback/build": "^1.4.1", + "@loopback/cli": "^1.9.0", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/metadata": "^1.0.11", + "@loopback/openapi-spec-builder": "^1.1.3", + "@loopback/openapi-v3-types": "^1.0.11", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/repository-json-schema": "^1.3.7", + "@loopback/repository": "^1.3.0", + "@loopback/rest": "^1.10.0", + "@loopback/testlab": "^1.2.2", + "@loopback/docs": "^1.12.0", "glob": "^7.1.2", - "@loopback/example-hello-world": "^1.1.8", - "@loopback/example-log-extension": "^1.1.8", - "@loopback/example-rpc-server": "^1.1.7", - "@loopback/example-todo": "^1.5.3", - "@loopback/example-soap-calculator": "^1.4.3", - "@loopback/service-proxy": "^1.1.1", - "@loopback/http-caching-proxy": "^1.0.11", - "@loopback/http-server": "^1.1.10", - "@loopback/example-todo-list": "^1.5.3", + "@loopback/example-hello-world": "^1.1.9", + "@loopback/example-log-extension": "^1.1.9", + "@loopback/example-rpc-server": "^1.1.8", + "@loopback/example-todo": "^1.5.4", + "@loopback/example-soap-calculator": "^1.4.4", + "@loopback/service-proxy": "^1.1.2", + "@loopback/http-caching-proxy": "^1.0.12", + "@loopback/http-server": "^1.1.11", + "@loopback/example-todo-list": "^1.5.4", "@loopback/dist-util": "^0.4.0", - "@loopback/rest-explorer": "^1.1.13", - "@loopback/tslint-config": "^2.0.3", + "@loopback/rest-explorer": "^1.1.14", + "@loopback/tslint-config": "^2.0.4", "express-composition": "^1.1.0", - "@loopback/example-express-composition": "^1.2.3" + "@loopback/example-express-composition": "^1.2.4", + "@loopback/example-greeter-extension": "^1.0.0" } }, "copyright.owner": "IBM Corp.", diff --git a/packages/context/CHANGELOG.md b/packages/context/CHANGELOG.md index aa26646023ec..72621e3c4938 100644 --- a/packages/context/CHANGELOG.md +++ b/packages/context/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/strongloop/loopback-next/compare/@loopback/context@1.8.1...@loopback/context@1.9.0) (2019-04-05) + + +### Bug Fixes + +* **context:** clear binding cache upon scope or value getter changes ([122fe7b](https://github.com/strongloop/loopback-next/commit/122fe7b)) + + +### Features + +* **context:** add a helper function to create a getter from binding filter ([41248f3](https://github.com/strongloop/loopback-next/commit/41248f3)) +* **context:** add binding.toAlias() to resolve values from another binding ([15dcd16](https://github.com/strongloop/loopback-next/commit/15dcd16)) +* **context:** pass resolution options into binding.getValue() ([705dcd5](https://github.com/strongloop/loopback-next/commit/705dcd5)) + + + + + ## [1.8.1](https://github.com/strongloop/loopback-next/compare/@loopback/context@1.8.0...@loopback/context@1.8.1) (2019-03-22) **Note:** Version bump only for package @loopback/context diff --git a/packages/context/package-lock.json b/packages/context/package-lock.json index a05d7a0b4363..ead3f9a2e2f4 100644 --- a/packages/context/package-lock.json +++ b/packages/context/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/context", - "version": "1.8.1", + "version": "1.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -32,9 +32,9 @@ } }, "bluebird": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", "dev": true }, "debug": { @@ -51,9 +51,9 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "p-event": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz", - "integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", "requires": { "p-timeout": "^2.0.1" } diff --git a/packages/context/package.json b/packages/context/package.json index 07c1825af05f..cc7dd9c7b6b8 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/context", - "version": "1.8.1", + "version": "1.9.0", "description": "LoopBack's container for Inversion of Control", "engines": { "node": ">=8.9" @@ -19,20 +19,20 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/metadata": "^1.0.10", + "@loopback/metadata": "^1.0.11", "debug": "^4.0.1", - "p-event": "^4.0.0", + "p-event": "^4.1.0", "uuid": "^3.2.1" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/bluebird": "^3.5.20", "@types/debug": "^4.1.0", "@types/node": "^10.11.2", "@types/uuid": "^3.4.3", - "bluebird": "^3.5.1" + "bluebird": "^3.5.4" }, "keywords": [ "LoopBack", diff --git a/packages/context/src/__tests__/unit/binding.unit.ts b/packages/context/src/__tests__/unit/binding.unit.ts index 28cb6837a4d0..ff32055720b0 100644 --- a/packages/context/src/__tests__/unit/binding.unit.ts +++ b/packages/context/src/__tests__/unit/binding.unit.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {expect} from '@loopback/testlab'; +import {expect, sinon, SinonSpy} from '@loopback/testlab'; import { Binding, BindingScope, @@ -191,6 +191,64 @@ describe('Binding', () => { }); }); + describe('toAlias(bindingKeyWithPath)', async () => { + it('binds to another binding with sync value', () => { + ctx.bind('parent.options').to({child: {disabled: true}}); + ctx.bind('child.options').toAlias('parent.options#child'); + const childOptions = ctx.getSync('child.options'); + expect(childOptions).to.eql({disabled: true}); + }); + + it('creates the alias even when the target is not bound yet', () => { + ctx.bind('child.options').toAlias('parent.options#child'); + ctx.bind('parent.options').to({child: {disabled: true}}); + const childOptions = ctx.getSync('child.options'); + expect(childOptions).to.eql({disabled: true}); + }); + + it('binds to another binding with async value', async () => { + ctx + .bind('parent.options') + .toDynamicValue(() => Promise.resolve({child: {disabled: true}})); + ctx.bind('child.options').toAlias('parent.options#child'); + const childOptions = await ctx.get('child.options'); + expect(childOptions).to.eql({disabled: true}); + }); + + it('reports error if alias binding cannot be resolved', () => { + ctx.bind('child.options').toAlias('parent.options#child'); + expect(() => ctx.getSync('child.options')).to.throw( + /The key 'parent.options' is not bound to any value in context/, + ); + }); + + it('reports error if alias binding cannot be resolved - async', async () => { + ctx.bind('child.options').toAlias('parent.options#child'); + return expect(ctx.get('child.options')).to.be.rejectedWith( + /The key 'parent.options' is not bound to any value in context/, + ); + }); + + it('allows optional if alias binding cannot be resolved', () => { + ctx.bind('child.options').toAlias('parent.options#child'); + const childOptions = ctx.getSync('child.options', {optional: true}); + expect(childOptions).to.be.undefined(); + }); + + it('allows optional if alias binding cannot be resolved - async', async () => { + ctx.bind('child.options').toAlias('parent.options#child'); + const childOptions = await ctx.get('child.options', {optional: true}); + expect(childOptions).to.be.undefined(); + }); + + it('sets type to ALIAS', () => { + const childBinding = ctx + .bind('child.options') + .toAlias('parent.options#child'); + expect(childBinding.type).to.equal(BindingType.ALIAS); + }); + }); + describe('apply(templateFn)', () => { it('applies a template function', async () => { binding.apply(b => { @@ -214,6 +272,68 @@ describe('Binding', () => { }); }); + describe('cache', () => { + let spy: SinonSpy; + beforeEach(() => { + spy = sinon.spy(); + }); + + it('clears cache if scope changes', () => { + const indexBinding = ctx + .bind('index') + .toDynamicValue(spy) + .inScope(BindingScope.SINGLETON); + + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + spy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + spy.resetHistory(); + + indexBinding.inScope(BindingScope.CONTEXT); + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + }); + + it('clears cache if _getValue changes', () => { + const providerSpy = sinon.spy(); + class IndexProvider implements Provider { + value() { + return providerSpy(); + } + } + const indexBinding = ctx + .bind('index') + .toDynamicValue(spy) + .inScope(BindingScope.SINGLETON); + + ctx.getSync(indexBinding.key); + sinon.assert.calledOnce(spy); + spy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + spy.resetHistory(); + + // Now change the value getter + indexBinding.toProvider(IndexProvider); + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + sinon.assert.calledOnce(providerSpy); + spy.resetHistory(); + providerSpy.resetHistory(); + + // Singleton + ctx.getSync(indexBinding.key); + sinon.assert.notCalled(spy); + sinon.assert.notCalled(providerSpy); + }); + }); + describe('toJSON()', () => { it('converts a keyed binding to plain JSON object', () => { const json = binding.toJSON(); diff --git a/packages/context/src/__tests__/unit/context-view.unit.ts b/packages/context/src/__tests__/unit/context-view.unit.ts index 4f2647576574..0ae0cf458c9b 100644 --- a/packages/context/src/__tests__/unit/context-view.unit.ts +++ b/packages/context/src/__tests__/unit/context-view.unit.ts @@ -4,7 +4,14 @@ // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; -import {Binding, BindingScope, filterByTag, Context, ContextView} from '../..'; +import { + Binding, + BindingScope, + Context, + ContextView, + createViewGetter, + filterByTag, +} from '../..'; describe('ContextView', () => { let app: Context; @@ -133,6 +140,22 @@ describe('ContextView', () => { } }); + describe('createViewGetter', () => { + it('creates a getter function for the binding filter', async () => { + const getter = createViewGetter(server, filterByTag('foo')); + expect(await getter()).to.eql(['BAR', 'FOO']); + server + .bind('abc') + .to('ABC') + .tag('abc'); + server + .bind('xyz') + .to('XYZ') + .tag('foo'); + expect(await getter()).to.eql(['BAR', 'XYZ', 'FOO']); + }); + }); + function givenContextView() { bindings = []; givenContext(); diff --git a/packages/context/src/binding.ts b/packages/context/src/binding.ts index 3045bdee3f7c..cdef21f06de2 100644 --- a/packages/context/src/binding.ts +++ b/packages/context/src/binding.ts @@ -3,11 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import * as debugModule from 'debug'; +import * as debugFactory from 'debug'; import {BindingAddress, BindingKey} from './binding-key'; import {Context} from './context'; import {Provider} from './provider'; -import {ResolutionSession} from './resolution-session'; +import { + asResolutionOptions, + ResolutionOptions, + ResolutionOptionsOrSession, + ResolutionSession, +} from './resolution-session'; import {instantiateClass} from './resolver'; import { BoundValue, @@ -18,7 +23,7 @@ import { ValueOrPromise, } from './value-promise'; -const debug = debugModule('loopback:context:binding'); +const debug = debugFactory('loopback:context:binding'); /** * Scope for binding values @@ -113,6 +118,10 @@ export enum BindingType { * A provider class with `value()` function to get the value */ PROVIDER = 'Provider', + /** + * A alias to another binding key with optional path + */ + ALIAS = 'Alias', } // tslint:disable-next-line:no-any @@ -128,6 +137,11 @@ export type BindingTag = TagMap | string; */ export type BindingTemplate = (binding: Binding) => void; +type ValueGetter = ( + ctx: Context, + options: ResolutionOptions, +) => ValueOrPromise; + /** * Binding represents an entry in the `Context`. Each binding has a key and a * corresponding value getter. @@ -161,10 +175,7 @@ export class Binding { } private _cache: WeakMap; - private _getValue: ( - ctx?: Context, - session?: ResolutionSession, - ) => ValueOrPromise; + private _getValue: ValueGetter; private _valueConstructor?: Constructor; /** @@ -204,6 +215,15 @@ export class Binding { }); } + /** + * Clear the cache + */ + private _clearCache() { + if (!this._cache) return; + // WeakMap does not have a `clear` method + this._cache = new WeakMap(); + } + /** * This is an internal function optimized for performance. * Users should use `@inject(key)` or `ctx.get(key)` instead. @@ -228,7 +248,25 @@ export class Binding { * @param ctx Context for the resolution * @param session Optional session for binding and dependency resolution */ - getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise { + getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise; + + /** + * Returns a value or promise for this binding in the given context. The + * resolved value can be `undefined` if `optional` is set to `true` in + * `options`. + * @param ctx Context for the resolution + * @param options Optional options for binding and dependency resolution + */ + getValue( + ctx: Context, + options?: ResolutionOptions, + ): ValueOrPromise; + + // Implementation + getValue( + ctx: Context, + optionsOrSession?: ResolutionOptionsOrSession, + ): ValueOrPromise { /* istanbul ignore if */ if (debug.enabled) { debug('Get value for binding %s', this.key); @@ -246,11 +284,15 @@ export class Binding { } } } + const options = asResolutionOptions(optionsOrSession); if (this._getValue) { let result = ResolutionSession.runWithBinding( - s => this._getValue(ctx, s), + s => { + const optionsWithSession = Object.assign({}, options, {session: s}); + return this._getValue(ctx, optionsWithSession); + }, this, - session, + options.session, ); return this._cacheValue(ctx, result); } @@ -317,6 +359,7 @@ export class Binding { * @param scope Binding scope */ inScope(scope: BindingScope): this { + if (this._scope !== scope) this._clearCache(); this._scope = scope; return this; } @@ -328,11 +371,21 @@ export class Binding { */ applyDefaultScope(scope: BindingScope): this { if (!this._scope) { - this._scope = scope; + this.inScope(scope); } return this; } + /** + * Set the `_getValue` function + * @param getValue getValue function + */ + private _setValueGetter(getValue: ValueGetter) { + // Clear the cache + this._clearCache(); + this._getValue = getValue; + } + /** * Bind the key to a constant value. The value must be already available * at binding time, it is not allowed to pass a Promise instance. @@ -372,7 +425,7 @@ export class Binding { debug('Bind %s to constant:', this.key, value); } this._type = BindingType.CONSTANT; - this._getValue = () => value; + this._setValueGetter(() => value); return this; } @@ -400,7 +453,7 @@ export class Binding { debug('Bind %s to dynamic value:', this.key, factoryFn); } this._type = BindingType.DYNAMIC_VALUE; - this._getValue = ctx => factoryFn(); + this._setValueGetter(ctx => factoryFn()); return this; } @@ -426,14 +479,14 @@ export class Binding { debug('Bind %s to provider %s', this.key, providerClass.name); } this._type = BindingType.PROVIDER; - this._getValue = (ctx, session) => { + this._setValueGetter((ctx, options) => { const providerOrPromise = instantiateClass>( providerClass, - ctx!, - session, + ctx, + options.session, ); return transformValueOrPromise(providerOrPromise, p => p.value()); - }; + }); return this; } @@ -450,11 +503,34 @@ export class Binding { debug('Bind %s to class %s', this.key, ctor.name); } this._type = BindingType.CLASS; - this._getValue = (ctx, session) => instantiateClass(ctor, ctx!, session); + this._setValueGetter((ctx, options) => + instantiateClass(ctor, ctx, options.session), + ); this._valueConstructor = ctor; return this; } + /** + * Bind the key to an alias of another binding + * @param keyWithPath Target binding key with optional path, + * such as `servers.RestServer.options#apiExplorer` + */ + toAlias(keyWithPath: BindingAddress) { + /* istanbul ignore if */ + if (debug.enabled) { + debug('Bind %s to alias %s', this.key, keyWithPath); + } + this._type = BindingType.ALIAS; + this._setValueGetter((ctx, optionsOrSession) => { + const options = asResolutionOptions(optionsOrSession); + return ctx.getValueOrPromise(keyWithPath, options); + }); + return this; + } + + /** + * Unlock the binding + */ unlock(): this { this.isLocked = false; return this; diff --git a/packages/context/src/context-view.ts b/packages/context/src/context-view.ts index b4673116e1d3..ce5b9cf48f0f 100644 --- a/packages/context/src/context-view.ts +++ b/packages/context/src/context-view.ts @@ -42,7 +42,7 @@ export class ContextView extends EventEmitter constructor( protected readonly context: Context, - public readonly filter: BindingFilter, + public readonly filter: BindingFilter, ) { super(); } @@ -152,3 +152,19 @@ export class ContextView extends EventEmitter return () => this.values(session); } } + +/** + * Create a context view as a getter + * @param ctx Context object + * @param bindingFilter A function to match bindings + * @param session Resolution session + */ +export function createViewGetter( + ctx: Context, + bindingFilter: BindingFilter, + session?: ResolutionSession, +): Getter { + const view = new ContextView(ctx, bindingFilter); + view.open(); + return view.asGetter(session); +} diff --git a/packages/context/src/context.ts b/packages/context/src/context.ts index 3d32c221e321..9b74d65dfd2a 100644 --- a/packages/context/src/context.ts +++ b/packages/context/src/context.ts @@ -3,14 +3,12 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import * as debugModule from 'debug'; +import * as debugFactory from 'debug'; import {EventEmitter} from 'events'; import {v1 as uuidv1} from 'uuid'; -import {ValueOrPromise} from '.'; import {Binding, BindingTag} from './binding'; import {BindingFilter, filterByKey, filterByTag} from './binding-filter'; import {BindingAddress, BindingKey} from './binding-key'; -import {ContextView} from './context-view'; import { ContextEventObserver, ContextEventType, @@ -18,8 +16,19 @@ import { Notification, Subscription, } from './context-observer'; -import {ResolutionOptions, ResolutionSession} from './resolution-session'; -import {BoundValue, getDeepProperty, isPromiseLike} from './value-promise'; +import {ContextView} from './context-view'; +import { + asResolutionOptions, + ResolutionOptions, + ResolutionOptionsOrSession, + ResolutionSession, +} from './resolution-session'; +import { + BoundValue, + getDeepProperty, + isPromiseLike, + ValueOrPromise, +} from './value-promise'; /** * Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x. @@ -32,7 +41,7 @@ if (!Symbol.asyncIterator) { // This import must happen after the polyfill import {iterator, multiple} from 'p-event'; -const debug = debugModule('loopback:context'); +const debug = debugFactory('loopback:context'); /** * Context provides an implementation of Inversion of Control (IoC) container @@ -574,9 +583,14 @@ export class Context extends EventEmitter { * * @param keyWithPath The binding key, optionally suffixed with a path to the * (deeply) nested property to retrieve. + * @param session Optional session for resolution (accepted for backward + * compatibility) * @returns A promise of the bound value. */ - get(keyWithPath: BindingAddress): Promise; + get( + keyWithPath: BindingAddress, + session?: ResolutionSession, + ): Promise; /** * Get the value bound to the given key, optionally return a (deep) property @@ -594,20 +608,19 @@ export class Context extends EventEmitter { * * @param keyWithPath The binding key, optionally suffixed with a path to the * (deeply) nested property to retrieve. - * @param optionsOrSession Options or session for resolution. An instance of - * `ResolutionSession` is accepted for backward compatibility. + * @param options Options for resolution. * @returns A promise of the bound value, or a promise of undefined when * the optional binding is not found. */ get( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + options: ResolutionOptions, ): Promise; // Implementation async get( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + optionsOrSession?: ResolutionOptionsOrSession, ): Promise { this._debug('Resolving binding: %s', keyWithPath); return await this.getValueOrPromise( @@ -636,11 +649,13 @@ export class Context extends EventEmitter { * * @param keyWithPath The binding key, optionally suffixed with a path to the * (deeply) nested property to retrieve. - * * @param optionsOrSession Options or session for resolution. An instance of - * `ResolutionSession` is accepted for backward compatibility. + * @param session Session for resolution (accepted for backward compatibility) * @returns A promise of the bound value. */ - getSync(keyWithPath: BindingAddress): ValueType; + getSync( + keyWithPath: BindingAddress, + session?: ResolutionSession, + ): ValueType; /** * Get the synchronous value bound to the given key, optionally @@ -662,19 +677,18 @@ export class Context extends EventEmitter { * * @param keyWithPath The binding key, optionally suffixed with a path to the * (deeply) nested property to retrieve. - * * @param optionsOrSession Options or session for resolution. An instance of - * `ResolutionSession` is accepted for backward compatibility. + * @param options Options for resolution. * @returns The bound value, or undefined when an optional binding is not found. */ getSync( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + options?: ResolutionOptions, ): ValueType | undefined; // Implementation getSync( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + optionsOrSession?: ResolutionOptionsOrSession, ): ValueType | undefined { this._debug('Resolving binding synchronously: %s', keyWithPath); @@ -766,22 +780,16 @@ export class Context extends EventEmitter { */ getValueOrPromise( keyWithPath: BindingAddress, - optionsOrSession?: ResolutionOptions | ResolutionSession, + optionsOrSession?: ResolutionOptionsOrSession, ): ValueOrPromise { const {key, propertyPath} = BindingKey.parseKeyWithPath(keyWithPath); - // backwards compatibility - if (optionsOrSession instanceof ResolutionSession) { - optionsOrSession = {session: optionsOrSession}; - } + optionsOrSession = asResolutionOptions(optionsOrSession); const binding = this.getBinding(key, optionsOrSession); if (binding == null) return undefined; - const boundValue = binding.getValue( - this, - optionsOrSession && optionsOrSession.session, - ); + const boundValue = binding.getValue(this, optionsOrSession); if (propertyPath === undefined || propertyPath === '') { return boundValue; } diff --git a/packages/context/src/inject.ts b/packages/context/src/inject.ts index 14b209a5205f..15638e33eff5 100644 --- a/packages/context/src/inject.ts +++ b/packages/context/src/inject.ts @@ -21,7 +21,7 @@ import { } from './binding-filter'; import {BindingAddress} from './binding-key'; import {Context} from './context'; -import {ContextView} from './context-view'; +import {ContextView, createViewGetter} from './context-view'; import {ResolutionSession} from './resolution-session'; import {BoundValue, ValueOrPromise} from './value-promise'; @@ -461,9 +461,7 @@ function resolveAsGetterByFilter( ) { assertTargetIsGetter(injection); const bindingFilter = injection.bindingSelector as BindingFilter; - const view = new ContextView(ctx, bindingFilter); - view.open(); - return view.asGetter(session); + return createViewGetter(ctx, bindingFilter, session); } /** diff --git a/packages/context/src/resolution-session.ts b/packages/context/src/resolution-session.ts index 4bfc53cfa19b..39ebeaf74cc9 100644 --- a/packages/context/src/resolution-session.ts +++ b/packages/context/src/resolution-session.ts @@ -3,19 +3,15 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {DecoratorFactory} from '@loopback/metadata'; +import * as debugModule from 'debug'; import {Binding} from './binding'; import {Injection} from './inject'; -import {ValueOrPromise, BoundValue, tryWithFinally} from './value-promise'; -import * as debugModule from 'debug'; -import {DecoratorFactory} from '@loopback/metadata'; +import {BoundValue, tryWithFinally, ValueOrPromise} from './value-promise'; const debugSession = debugModule('loopback:context:resolver:session'); const getTargetName = DecoratorFactory.getTargetName; -// NOTE(bajtos) The following import is required to satisfy TypeScript compiler -// tslint:disable-next-line:no-unused -import {BindingKey} from './binding-key'; - /** * A function to be executed with the resolution session */ @@ -169,7 +165,7 @@ export class ResolutionSession { ); return { targetName: name, - bindingKey: injection.bindingSelector, + bindingSelector: injection.bindingSelector, // Cast to Object so that we don't have to expose InjectionMetadata metadata: injection.metadata as Object, }; @@ -343,3 +339,22 @@ export interface ResolutionOptions { */ optional?: boolean; } + +/** + * Resolution options or session + */ +export type ResolutionOptionsOrSession = ResolutionOptions | ResolutionSession; + +/** + * Normalize ResolutionOptionsOrSession to ResolutionOptions + * @param optionsOrSession resolution options or session + */ +export function asResolutionOptions( + optionsOrSession?: ResolutionOptionsOrSession, +): ResolutionOptions { + // backwards compatibility + if (optionsOrSession instanceof ResolutionSession) { + return {session: optionsOrSession}; + } + return optionsOrSession || {}; +} diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index ca2aa21eb8b3..ecf5a47a702c 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/strongloop/loopback-next/compare/@loopback/core@1.2.1...@loopback/core@1.3.0) (2019-04-05) + + +### Features + +* **core:** add constants for namespaces and types ([a4778f7](https://github.com/strongloop/loopback-next/commit/a4778f7)) +* **core:** create bindings from classes for components ([e615657](https://github.com/strongloop/loopback-next/commit/e615657)) + + + + + ## [1.2.1](https://github.com/strongloop/loopback-next/compare/@loopback/core@1.2.0...@loopback/core@1.2.1) (2019-03-22) **Note:** Version bump only for package @loopback/core diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index f7eee379a5cc..0d3371b2fc1d 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/core", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index 456b21825b4d..8986f6f10487 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/core", - "version": "1.2.1", + "version": "1.3.0", "description": "", "engines": { "node": ">=8.9" @@ -20,12 +20,12 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1" + "@loopback/context": "^1.9.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "files": [ diff --git a/packages/core/src/__tests__/unit/application.unit.ts b/packages/core/src/__tests__/unit/application.unit.ts index 6d6a6ec498d2..4cc86c6b430d 100644 --- a/packages/core/src/__tests__/unit/application.unit.ts +++ b/packages/core/src/__tests__/unit/application.unit.ts @@ -135,6 +135,37 @@ describe('Application', () => { expect(app.getSync('my-provider')).to.be.eql('my-str'); }); + it('binds classes with @bind from a component', () => { + @bind({scope: BindingScope.SINGLETON, tags: ['foo']}) + class MyClass {} + + class MyComponentWithClasses implements Component { + classes = {'my-class': MyClass}; + } + + app.component(MyComponentWithClasses); + const binding = app.getBinding('my-class'); + expect(binding.scope).to.eql(BindingScope.SINGLETON); + expect(binding.tagNames).to.containEql('foo'); + }); + + it('binds providers from a component', () => { + @bind({tags: ['foo']}) + class MyProvider implements Provider { + value() { + return 'my-str'; + } + } + + class MyComponentWithProviders implements Component { + providers = {'my-provider': MyProvider}; + } + + app.component(MyComponentWithProviders); + const binding = app.getBinding('my-provider'); + expect(binding.tagNames).to.containEql('foo'); + }); + it('binds from a component constructor', () => { class MyComponentWithDI implements Component { constructor(@inject(CoreBindings.APPLICATION_INSTANCE) ctx: Context) { diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index 15064c24ae7a..501c659d1b86 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -11,7 +11,7 @@ import { createBindingFromClass, } from '@loopback/context'; import {Component, mountComponent} from './component'; -import {CoreBindings} from './keys'; +import {CoreBindings, CoreTags} from './keys'; import {Server} from './server'; /** @@ -21,7 +21,7 @@ import {Server} from './server'; */ export class Application extends Context { constructor(public options: ApplicationConfig = {}) { - super(); + super('application'); // Bind to self to allow injection of application context in other modules. this.bind(CoreBindings.APPLICATION_INSTANCE).to(this); @@ -48,8 +48,8 @@ export class Application extends Context { controller(controllerCtor: ControllerClass, name?: string): Binding { const binding = createBindingFromClass(controllerCtor, { name, - namespace: 'controllers', - type: 'controller', + namespace: CoreBindings.CONTROLLERS, + type: CoreTags.CONTROLLER, defaultScope: BindingScope.TRANSIENT, }); this.add(binding); @@ -80,7 +80,7 @@ export class Application extends Context { const binding = createBindingFromClass(ctor, { name, namespace: CoreBindings.SERVERS, - type: 'server', + type: CoreTags.SERVER, defaultScope: BindingScope.SINGLETON, }); this.add(binding); @@ -122,7 +122,7 @@ export class Application extends Context { * @memberof Application */ public async getServer( - target: Constructor | String, + target: Constructor | string, ): Promise { let key: string; // instanceof check not reliable for string. @@ -196,8 +196,8 @@ export class Application extends Context { public component(componentCtor: Constructor, name?: string) { const binding = createBindingFromClass(componentCtor, { name, - namespace: 'components', - type: 'component', + namespace: CoreBindings.COMPONENTS, + type: CoreTags.COMPONENT, defaultScope: BindingScope.SINGLETON, }); this.add(binding); diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 319430bbdfec..7e84a09efed2 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -3,9 +3,15 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Constructor, Provider, BoundValue, Binding} from '@loopback/context'; -import {Server} from './server'; +import { + Binding, + BoundValue, + Constructor, + createBindingFromClass, + Provider, +} from '@loopback/context'; import {Application, ControllerClass} from './application'; +import {Server} from './server'; /** * A map of provider classes to be bound to a context @@ -87,13 +93,19 @@ export interface Component { export function mountComponent(app: Application, component: Component) { if (component.classes) { for (const classKey in component.classes) { - app.bind(classKey).toClass(component.classes[classKey]); + const binding = createBindingFromClass(component.classes[classKey], { + key: classKey, + }); + app.add(binding); } } if (component.providers) { for (const providerKey in component.providers) { - app.bind(providerKey).toProvider(component.providers[providerKey]); + const binding = createBindingFromClass(component.providers[providerKey], { + key: providerKey, + }); + app.add(binding); } } diff --git a/packages/core/src/keys.ts b/packages/core/src/keys.ts index 847388fc339c..a7a8e0563fa0 100644 --- a/packages/core/src/keys.ts +++ b/packages/core/src/keys.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {BindingKey} from '@loopback/context'; -import {Application, ControllerClass, ApplicationMetadata} from './application'; +import {Application, ApplicationMetadata, ControllerClass} from './application'; /** * Namespace for core binding keys @@ -38,7 +38,15 @@ export namespace CoreBindings { */ export const SERVERS = 'servers'; + // component + /** + * Binding key for components + */ + export const COMPONENTS = 'components'; + // controller + export const CONTROLLERS = 'controllers'; + /** * Binding key for the controller class resolved in the current request * context @@ -67,3 +75,20 @@ export namespace CoreBindings { */ export const CONTROLLER_CURRENT = BindingKey.create('controller.current'); } + +export namespace CoreTags { + /** + * Binding tag for components + */ + export const COMPONENT = 'component'; + + /** + * Binding tag for servers + */ + export const SERVER = 'server'; + + /** + * Binding tag for controllers + */ + export const CONTROLLER = 'controller'; +} diff --git a/packages/http-caching-proxy/CHANGELOG.md b/packages/http-caching-proxy/CHANGELOG.md index 5b27a450517d..e6ab3e509282 100644 --- a/packages/http-caching-proxy/CHANGELOG.md +++ b/packages/http-caching-proxy/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.12](https://github.com/strongloop/loopback-next/compare/@loopback/http-caching-proxy@1.0.11...@loopback/http-caching-proxy@1.0.12) (2019-04-05) + +**Note:** Version bump only for package @loopback/http-caching-proxy + + + + + ## [1.0.11](https://github.com/strongloop/loopback-next/compare/@loopback/http-caching-proxy@1.0.10...@loopback/http-caching-proxy@1.0.11) (2019-03-22) **Note:** Version bump only for package @loopback/http-caching-proxy diff --git a/packages/http-caching-proxy/package-lock.json b/packages/http-caching-proxy/package-lock.json index ff02c3077945..1eb89a2b7467 100644 --- a/packages/http-caching-proxy/package-lock.json +++ b/packages/http-caching-proxy/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/http-caching-proxy", - "version": "1.0.11", + "version": "1.0.12", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -590,9 +590,9 @@ } }, "p-event": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz", - "integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", "requires": { "p-timeout": "^2.0.1" } diff --git a/packages/http-caching-proxy/package.json b/packages/http-caching-proxy/package.json index 7446427d7378..00ddab31d23b 100644 --- a/packages/http-caching-proxy/package.json +++ b/packages/http-caching-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/http-caching-proxy", - "version": "1.0.11", + "version": "1.0.12", "description": "A caching HTTP proxy for integration tests. NOT SUITABLE FOR PRODUCTION USE!", "engines": { "node": ">=8.9" @@ -19,15 +19,15 @@ "dependencies": { "cacache": "^11.0.2", "debug": "^4.0.1", - "p-event": "^4.0.0", + "p-event": "^4.1.0", "request": "^2.87.0", "request-promise-native": "^1.0.5", "rimraf": "^2.6.2" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/debug": "^4.1.0", "@types/node": "^10.11.2", "@types/request-promise-native": "^1.0.14", diff --git a/packages/http-server/CHANGELOG.md b/packages/http-server/CHANGELOG.md index f23e3b2a26e6..597abc1db3c7 100644 --- a/packages/http-server/CHANGELOG.md +++ b/packages/http-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.11](https://github.com/strongloop/loopback-next/compare/@loopback/http-server@1.1.10...@loopback/http-server@1.1.11) (2019-04-05) + +**Note:** Version bump only for package @loopback/http-server + + + + + ## [1.1.10](https://github.com/strongloop/loopback-next/compare/@loopback/http-server@1.1.9...@loopback/http-server@1.1.10) (2019-03-22) **Note:** Version bump only for package @loopback/http-server diff --git a/packages/http-server/package-lock.json b/packages/http-server/package-lock.json index c1f57c9bda48..901872aaf700 100644 --- a/packages/http-server/package-lock.json +++ b/packages/http-server/package-lock.json @@ -1,67 +1,19 @@ { "name": "@loopback/http-server", - "version": "1.1.10", + "version": "1.1.11", "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/caseless": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", - "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", - "dev": true - }, - "@types/form-data": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/node": { "version": "10.12.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==", "dev": true }, - "@types/request": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", - "dev": true, - "requires": { - "@types/caseless": "*", - "@types/form-data": "*", - "@types/node": "*", - "@types/tough-cookie": "*" - } - }, - "@types/request-promise-native": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@types/request-promise-native/-/request-promise-native-1.0.15.tgz", - "integrity": "sha512-uYPjTChD9TpjlvbBjNpZfNc64TBejBS52u7pbxhQLnlxw+5Em7wLb6DU2wdJVhJ2Mou7v50N0qgL4Gia5mmRYg==", - "dev": true, - "requires": { - "@types/request": "*" - } - }, - "@types/tough-cookie": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", - "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "p-event": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.0.0.tgz", - "integrity": "sha512-5VKoUdeAfIlAXmASmre9vGlKjnwGwbr5Zwd3GZU2KmkaRgxEOeUUS5Oyh0qYMx8Y+0VTvvNjNIlqIvMpI/DKSw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", "requires": { "p-timeout": "^2.0.1" } @@ -78,54 +30,6 @@ "requires": { "p-finally": "^1.0.0" } - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "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==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } } } } diff --git a/packages/http-server/package.json b/packages/http-server/package.json index 56852bdb80d0..f6d6eaf08bd8 100644 --- a/packages/http-server/package.json +++ b/packages/http-server/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/http-server", - "version": "1.1.10", + "version": "1.1.11", "description": "A wrapper for creating HTTP/HTTPS servers", "engines": { "node": ">=8.9" @@ -17,16 +17,14 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "p-event": "^4.0.0" + "p-event": "^4.1.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/core": "^1.2.1", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", - "@types/node": "^10.11.2", - "@types/request-promise-native": "^1.0.15", - "request-promise-native": "^1.0.5" + "@loopback/build": "^1.4.1", + "@loopback/core": "^1.3.0", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", + "@types/node": "^10.11.2" }, "files": [ "README.md", diff --git a/packages/http-server/src/__tests__/integration/http-server.integration.ts b/packages/http-server/src/__tests__/integration/http-server.integration.ts index b6571d273ab6..758da15cb9b3 100644 --- a/packages/http-server/src/__tests__/integration/http-server.integration.ts +++ b/packages/http-server/src/__tests__/integration/http-server.integration.ts @@ -3,19 +3,18 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {HttpServer, HttpOptions, HttpServerOptions} from '../../'; import { - supertest, expect, - itSkippedOnTravis, + givenHttpServerConfig, httpGetAsync, httpsGetAsync, - givenHttpServerConfig, + itSkippedOnTravis, + supertest, } from '@loopback/testlab'; -import * as makeRequest from 'request-promise-native'; -import {IncomingMessage, ServerResponse, Server} from 'http'; -import * as path from 'path'; import * as fs from 'fs'; +import {IncomingMessage, Server, ServerResponse} from 'http'; +import * as path from 'path'; +import {HttpOptions, HttpServer, HttpServerOptions} from '../../'; describe('HttpServer (integration)', () => { let server: HttpServer | undefined; @@ -46,11 +45,7 @@ describe('HttpServer (integration)', () => { server = new HttpServer(dummyRequestHandler, serverOptions); await server.start(); await server.stop(); - await expect( - makeRequest({ - uri: server.url, - }), - ).to.be.rejectedWith(/ECONNREFUSED/); + await expect(httpGetAsync(server.url)).to.be.rejectedWith(/ECONNREFUSED/); }); it('exports original port', async () => { diff --git a/packages/metadata/CHANGELOG.md b/packages/metadata/CHANGELOG.md index 75f46b2e99a6..c68a292f9736 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.11](https://github.com/strongloop/loopback-next/compare/@loopback/metadata@1.0.10...@loopback/metadata@1.0.11) (2019-04-05) + +**Note:** Version bump only for package @loopback/metadata + + + + + ## [1.0.10](https://github.com/strongloop/loopback-next/compare/@loopback/metadata@1.0.9...@loopback/metadata@1.0.10) (2019-03-22) **Note:** Version bump only for package @loopback/metadata diff --git a/packages/metadata/package-lock.json b/packages/metadata/package-lock.json index 2b92d0cbba1e..9ad48ccc6ee6 100644 --- a/packages/metadata/package-lock.json +++ b/packages/metadata/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/metadata", - "version": "1.0.10", + "version": "1.0.11", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/metadata/package.json b/packages/metadata/package.json index c7d44ce22231..18c69356ae84 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/metadata", - "version": "1.0.10", + "version": "1.0.11", "description": "LoopBack's metadata utilities for reflection and decoration", "engines": { "node": ">=8.9" @@ -24,9 +24,9 @@ "reflect-metadata": "^0.1.10" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/debug": "^4.1.0", "@types/lodash": "^4.14.106", "@types/node": "^10.11.2" diff --git a/packages/openapi-spec-builder/CHANGELOG.md b/packages/openapi-spec-builder/CHANGELOG.md index 842ca408014a..0362d0161b38 100644 --- a/packages/openapi-spec-builder/CHANGELOG.md +++ b/packages/openapi-spec-builder/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.3](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-spec-builder@1.1.2...@loopback/openapi-spec-builder@1.1.3) (2019-04-05) + +**Note:** Version bump only for package @loopback/openapi-spec-builder + + + + + ## [1.1.2](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-spec-builder@1.1.1...@loopback/openapi-spec-builder@1.1.2) (2019-03-22) **Note:** Version bump only for package @loopback/openapi-spec-builder diff --git a/packages/openapi-spec-builder/package-lock.json b/packages/openapi-spec-builder/package-lock.json index 79aa9023f6f9..b6ccdd1e0a03 100644 --- a/packages/openapi-spec-builder/package-lock.json +++ b/packages/openapi-spec-builder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-spec-builder", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/openapi-spec-builder/package.json b/packages/openapi-spec-builder/package.json index 586c8d35ca90..8697910109b6 100644 --- a/packages/openapi-spec-builder/package.json +++ b/packages/openapi-spec-builder/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-spec-builder", - "version": "1.1.2", + "version": "1.1.3", "description": "Make it easy to create OpenAPI (Swagger) specification documents in your tests using the builder pattern.", "engines": { "node": ">=8.9" @@ -24,12 +24,12 @@ "Testing" ], "dependencies": { - "@loopback/openapi-v3-types": "^1.0.10" + "@loopback/openapi-v3-types": "^1.0.11" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "files": [ diff --git a/packages/openapi-v3-types/CHANGELOG.md b/packages/openapi-v3-types/CHANGELOG.md index 985213214379..79e758d7c778 100644 --- a/packages/openapi-v3-types/CHANGELOG.md +++ b/packages/openapi-v3-types/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.11](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3-types@1.0.10...@loopback/openapi-v3-types@1.0.11) (2019-04-05) + +**Note:** Version bump only for package @loopback/openapi-v3-types + + + + + ## [1.0.10](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3-types@1.0.9...@loopback/openapi-v3-types@1.0.10) (2019-03-22) **Note:** Version bump only for package @loopback/openapi-v3-types diff --git a/packages/openapi-v3-types/package-lock.json b/packages/openapi-v3-types/package-lock.json index 70e1fe2cfacd..c0f8ecddebac 100644 --- a/packages/openapi-v3-types/package-lock.json +++ b/packages/openapi-v3-types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-v3-types", - "version": "1.0.10", + "version": "1.0.11", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/openapi-v3-types/package.json b/packages/openapi-v3-types/package.json index 9fae271b2797..4519e883a55d 100644 --- a/packages/openapi-v3-types/package.json +++ b/packages/openapi-v3-types/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-v3-types", - "version": "1.0.10", + "version": "1.0.11", "description": "TypeScript type definitions for OpenAPI Specifications.", "engines": { "node": ">=8.9" @@ -9,9 +9,9 @@ "openapi3-ts": "^1.0.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "scripts": { diff --git a/packages/openapi-v3/CHANGELOG.md b/packages/openapi-v3/CHANGELOG.md index 3c3bc8685e97..0b793438ac07 100644 --- a/packages/openapi-v3/CHANGELOG.md +++ b/packages/openapi-v3/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.3](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@1.3.2...@loopback/openapi-v3@1.3.3) (2019-04-05) + +**Note:** Version bump only for package @loopback/openapi-v3 + + + + + ## [1.3.2](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@1.3.1...@loopback/openapi-v3@1.3.2) (2019-03-22) **Note:** Version bump only for package @loopback/openapi-v3 diff --git a/packages/openapi-v3/package-lock.json b/packages/openapi-v3/package-lock.json index 4e42e5eb81a8..926fb0c9372a 100644 --- a/packages/openapi-v3/package-lock.json +++ b/packages/openapi-v3/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-v3", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/openapi-v3/package.json b/packages/openapi-v3/package.json index 5f9edb2a202e..22a83e798dee 100644 --- a/packages/openapi-v3/package.json +++ b/packages/openapi-v3/package.json @@ -1,16 +1,16 @@ { "name": "@loopback/openapi-v3", - "version": "1.3.2", + "version": "1.3.3", "description": "Processes openapi v3 related metadata", "engines": { "node": ">=8.9" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/openapi-spec-builder": "^1.1.2", - "@loopback/repository": "^1.2.1", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/openapi-spec-builder": "^1.1.3", + "@loopback/repository": "^1.3.0", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/debug": "^4.1.0", "@types/lodash": "^4.14.106", "@types/node": "^10.11.2" @@ -49,9 +49,9 @@ "url": "https://github.com/strongloop/loopback-next.git" }, "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/openapi-v3-types": "^1.0.10", - "@loopback/repository-json-schema": "^1.3.6", + "@loopback/context": "^1.9.0", + "@loopback/openapi-v3-types": "^1.0.11", + "@loopback/repository-json-schema": "^1.3.7", "debug": "^4.0.1", "lodash": "^4.17.11" } diff --git a/packages/repository-json-schema/CHANGELOG.md b/packages/repository-json-schema/CHANGELOG.md index dd3fac3f66c8..604e5b3283da 100644 --- a/packages/repository-json-schema/CHANGELOG.md +++ b/packages/repository-json-schema/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.3.7](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@1.3.6...@loopback/repository-json-schema@1.3.7) (2019-04-05) + +**Note:** Version bump only for package @loopback/repository-json-schema + + + + + ## [1.3.6](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@1.3.5...@loopback/repository-json-schema@1.3.6) (2019-03-22) **Note:** Version bump only for package @loopback/repository-json-schema diff --git a/packages/repository-json-schema/package-lock.json b/packages/repository-json-schema/package-lock.json index 898abaa7352c..92c730244654 100644 --- a/packages/repository-json-schema/package-lock.json +++ b/packages/repository-json-schema/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository-json-schema", - "version": "1.3.6", + "version": "1.3.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index 5bea7cd84ef3..40c8740d3dec 100644 --- a/packages/repository-json-schema/package.json +++ b/packages/repository-json-schema/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository-json-schema", - "version": "1.3.6", + "version": "1.3.7", "description": "Converts TS classes into JSON Schemas using TypeScript's reflection API", "engines": { "node": ">=8.9" @@ -24,15 +24,15 @@ "access": "public" }, "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/metadata": "^1.0.10", - "@loopback/repository": "^1.2.1", + "@loopback/context": "^1.9.0", + "@loopback/metadata": "^1.0.11", + "@loopback/repository": "^1.3.0", "@types/json-schema": "^7.0.1" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2", "ajv": "^6.5.0" }, diff --git a/packages/repository/CHANGELOG.md b/packages/repository/CHANGELOG.md index d1b70c4dc160..2d798f49b531 100644 --- a/packages/repository/CHANGELOG.md +++ b/packages/repository/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.3.0](https://github.com/strongloop/loopback-next/compare/@loopback/repository@1.2.1...@loopback/repository@1.3.0) (2019-04-05) + + +### Features + +* **repository:** add execute implementation ([3bd6165](https://github.com/strongloop/loopback-next/commit/3bd6165)), closes [#2165](https://github.com/strongloop/loopback-next/issues/2165) + + + + + ## [1.2.1](https://github.com/strongloop/loopback-next/compare/@loopback/repository@1.2.0...@loopback/repository@1.2.1) (2019-03-22) **Note:** Version bump only for package @loopback/repository diff --git a/packages/repository/package-lock.json b/packages/repository/package-lock.json index 9d265a594572..17182acd2cc9 100644 --- a/packages/repository/package-lock.json +++ b/packages/repository/package-lock.json @@ -1,13 +1,13 @@ { "name": "@loopback/repository", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@types/debug": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.2.tgz", - "integrity": "sha512-jkf6UiWUjcOqdQbatbvOm54/YbCdjt3JjiAzT/9KS2XtMmOkYHdKsI5u8fulhbuTUuiqNBfa6J5GSDiwjK+zLA==" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.3.tgz", + "integrity": "sha512-PQverGatRgqIhRLracrC8k/j5A6QOASVLR0wuURx6ROQZx3OQ9PnTc/5Xrznny/xaO0TwdxSgGyG90NCzk37Rg==" }, "@types/lodash": { "version": "4.14.122", @@ -243,9 +243,9 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "loopback-connector": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.6.1.tgz", - "integrity": "sha512-2bVA3sMokZBoijxYhLJshNK5ADgdo4XA/j5sIItKyDvXvlkBvkpwP55G9qXw98PP3K5DC8tE+x597lFGY1MmFg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.6.2.tgz", + "integrity": "sha512-EJEactR/t8sdnpF+UqNQTo39ALWr3FB6b6rkEb6NdTEFOnXI+ZnW47sufqJmmh7U9JvIvm9WC61PNLvoP5y0cg==", "requires": { "async": "^2.1.5", "bluebird": "^3.4.6", @@ -508,17 +508,17 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", "requires": { "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", + "debug": "^4.1.1", + "globalize": "^1.4.2", "lodash": "^4.17.4", "md5": "^2.2.1", "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", + "os-locale": "^3.1.0", "yamljs": "^0.3.0" } }, diff --git a/packages/repository/package.json b/packages/repository/package.json index 76b9ce1a1f35..e7d0d61aa88e 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository", - "version": "1.2.1", + "version": "1.3.0", "description": "Repository based persistence for LoopBack 4", "engines": { "node": ">=8.9" @@ -19,15 +19,15 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/lodash": "^4.14.108", "@types/node": "^10.11.2" }, "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", "@types/debug": "^4.1.0", "debug": "^4.0.1", "lodash": "^4.17.11", diff --git a/packages/repository/src/__tests__/unit/repositories/legacy-juggler-bridge.unit.ts b/packages/repository/src/__tests__/unit/repositories/legacy-juggler-bridge.unit.ts index c9b4b9217336..7f32809ab6f7 100644 --- a/packages/repository/src/__tests__/unit/repositories/legacy-juggler-bridge.unit.ts +++ b/packages/repository/src/__tests__/unit/repositories/legacy-juggler-bridge.unit.ts @@ -394,4 +394,25 @@ describe('DefaultCrudRepository', () => { const ok = await repo.exists(note1.id); expect(ok).to.be.true(); }); + + it('implements Repository.execute()', async () => { + // Dummy implementation for execute() in datasource + ds.execute = (command, parameters, options) => { + return Promise.resolve({command, parameters, options}); + }; + const repo = new DefaultCrudRepository(Note, ds); + const result = await repo.execute('query', ['arg']); + expect(result).to.eql({ + command: 'query', + parameters: ['arg'], + options: undefined, + }); + }); + + it(`throws error when execute() not implemented by ds connector`, async () => { + const repo = new DefaultCrudRepository(Note, ds); + await expect(repo.execute('query', [])).to.be.rejectedWith( + 'execute() must be implemented by the connector', + ); + }); }); diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts index bd522d8e5392..5e3b028292a3 100644 --- a/packages/repository/src/repositories/legacy-juggler-bridge.ts +++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts @@ -433,12 +433,10 @@ export class DefaultCrudRepository async execute( command: Command, - // tslint:disable:no-any parameters: NamedParameters | PositionalParameters, options?: Options, ): Promise { - /* istanbul ignore next */ - throw new Error('Not implemented'); + return ensurePromise(this.dataSource.execute(command, parameters, options)); } protected toEntity(model: juggler.PersistedModel): T { diff --git a/packages/rest-explorer/CHANGELOG.md b/packages/rest-explorer/CHANGELOG.md index be934c923a0e..6c9970aeb7b5 100644 --- a/packages/rest-explorer/CHANGELOG.md +++ b/packages/rest-explorer/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.14](https://github.com/strongloop/loopback-next/compare/@loopback/rest-explorer@1.1.13...@loopback/rest-explorer@1.1.14) (2019-04-05) + +**Note:** Version bump only for package @loopback/rest-explorer + + + + + ## [1.1.13](https://github.com/strongloop/loopback-next/compare/@loopback/rest-explorer@1.1.12...@loopback/rest-explorer@1.1.13) (2019-03-22) **Note:** Version bump only for package @loopback/rest-explorer diff --git a/packages/rest-explorer/package-lock.json b/packages/rest-explorer/package-lock.json index 094776d354ab..98319f40ae3f 100644 --- a/packages/rest-explorer/package-lock.json +++ b/packages/rest-explorer/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest-explorer", - "version": "1.1.13", + "version": "1.1.14", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -454,9 +454,9 @@ "dev": true }, "swagger-ui-dist": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.21.0.tgz", - "integrity": "sha512-bAhzzpujhSIXdzpSI9b9RpvahC556lxcOIXxt1OOtTIasYodpy94gDlanQl4j7xavmi4nhaGiZ9iBcoFO+wHlA==" + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.22.0.tgz", + "integrity": "sha512-ZFcQoi4XT2t/NGKByVwmb4iERLtGmQQEFqHNC1PmdauWVZ2y80akkzctghgAftTlc8xpUp+I62nm4Km2q82ajQ==" }, "type-is": { "version": "1.6.16", diff --git a/packages/rest-explorer/package.json b/packages/rest-explorer/package.json index 4d5508ad85f0..10533ca4981e 100644 --- a/packages/rest-explorer/package.json +++ b/packages/rest-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest-explorer", - "version": "1.1.13", + "version": "1.1.14", "description": "LoopBack's API Explorer", "engines": { "node": ">=8.9" @@ -17,16 +17,16 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/rest": "^1.9.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/rest": "^1.10.0", "ejs": "^2.6.1", "swagger-ui-dist": "^3.20.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/ejs": "^2.6.0", "@types/express": "^4.16.1", "@types/node": "^10.1.1", diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index 6aefdf52fa9f..10c8f672bf88 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.10.0](https://github.com/strongloop/loopback-next/compare/@loopback/rest@1.9.1...@loopback/rest@1.10.0) (2019-04-05) + + +### Bug Fixes + +* **rest:** make sure basePath is included in RestServer.url ([705bce4](https://github.com/strongloop/loopback-next/commit/705bce4)) + + +### Features + +* **context:** pass resolution options into binding.getValue() ([705dcd5](https://github.com/strongloop/loopback-next/commit/705dcd5)) +* **rest:** add mountExpressRouter ([be21cde](https://github.com/strongloop/loopback-next/commit/be21cde)) + + + + + ## [1.9.1](https://github.com/strongloop/loopback-next/compare/@loopback/rest@1.9.0...@loopback/rest@1.9.1) (2019-03-22) **Note:** Version bump only for package @loopback/rest diff --git a/packages/rest/package-lock.json b/packages/rest/package-lock.json index 66be279aeddb..036d6fdb9e03 100644 --- a/packages/rest/package-lock.json +++ b/packages/rest/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest", - "version": "1.9.1", + "version": "1.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11,13 +11,6 @@ "requires": { "@types/connect": "*", "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/connect": { @@ -26,13 +19,6 @@ "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/cors": { @@ -60,19 +46,12 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", - "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz", + "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==", "requires": { "@types/node": "*", "@types/range-parser": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/http-errors": { @@ -81,9 +60,9 @@ "integrity": "sha512-s+RHKSGc3r0m3YEE2UXomJYrpQaY9cDmNDLU2XvG1/LAZsQ7y8emYkTLfcw/ByDtcsTyRQKwr76Bj4PkN2hfWg==" }, "@types/js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-UGEe/6RsNAxgWdknhzFZbCxuYc5I7b/YEKlfKbo+76SM8CJzGs7XKCj7zyugXViRbKYpXhSXhCYVQZL5tmDbpQ==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==", "dev": true }, "@types/lodash": { @@ -109,8 +88,7 @@ "@types/node": { "version": "10.12.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz", - "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==", - "dev": true + "integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q==" }, "@types/on-finished": { "version": "2.3.1", @@ -118,13 +96,6 @@ "integrity": "sha512-mzVYaYcFs5Jd2n/O6uYIRUsFRR1cHyZLRvkLCU0E7+G5WhY0qBDAR5fUCeZbvecYOSh9ikhlesyi2UfI8B9ckQ==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "@types/qs": { @@ -153,13 +124,6 @@ "integrity": "sha512-q8d51ZdF/D8xebrtNDsZH+4XBUFdz8xEgWhE4U4F4WWmcBZ8+i/r/qs9DmjAprYh5qQTYlY4BxaVKDrWIwNQ9w==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", - "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" - } } }, "accept-language": { @@ -661,9 +625,9 @@ } }, "http-status": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.3.1.tgz", - "integrity": "sha512-PcI9NUm6EUOhHlaxYABCqDQQWS7IgoBZ/PmPkhuzj+oR01ffjv3EJfKnnWJZcUhILtUh6/NdJi1Zs/mIr6v8DA==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.3.2.tgz", + "integrity": "sha512-vR1YTaDyi2BukI0UiH01xy92oiZi4in7r0dmSPnrZg72Vu1SzyOLalwWP5NUk1rNiB2L+XVK2lcSVOqaertX8A==" }, "iconv-lite": { "version": "0.4.23", @@ -719,9 +683,9 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -988,9 +952,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "range-parser": { "version": "1.2.0", diff --git a/packages/rest/package.json b/packages/rest/package.json index f67903f36b7b..4dda62d998ea 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest", - "version": "1.9.1", + "version": "1.10.0", "description": "", "engines": { "node": ">=8.9" @@ -20,11 +20,11 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", - "@loopback/http-server": "^1.1.10", - "@loopback/openapi-v3": "^1.3.2", - "@loopback/openapi-v3-types": "^1.0.10", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", + "@loopback/http-server": "^1.1.11", + "@loopback/openapi-v3": "^1.3.3", + "@loopback/openapi-v3-types": "^1.0.11", "@types/body-parser": "^1.17.0", "@types/cors": "^2.8.3", "@types/express": "^4.11.1", @@ -51,13 +51,13 @@ "validator": "^10.4.0" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/openapi-spec-builder": "^1.1.2", - "@loopback/repository": "^1.2.1", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/openapi-spec-builder": "^1.1.3", + "@loopback/repository": "^1.3.0", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/debug": "^4.1.0", - "@types/js-yaml": "^3.11.1", + "@types/js-yaml": "^3.12.1", "@types/lodash": "^4.14.106", "@types/multer": "^1.3.7", "@types/node": "^10.11.2", diff --git a/packages/rest/src/__tests__/acceptance/routing/routing.acceptance.ts b/packages/rest/src/__tests__/acceptance/routing/routing.acceptance.ts index 56b77ede2499..c7f8c752cd22 100644 --- a/packages/rest/src/__tests__/acceptance/routing/routing.acceptance.ts +++ b/packages/rest/src/__tests__/acceptance/routing/routing.acceptance.ts @@ -262,7 +262,7 @@ describe('Routing', () => { server.bind('flag').to('original'); // create a special binding returning the current context instance - server.bind('context').getValue = ctx => ctx; + server.bind('context').getValue = (ctx: Context) => ctx; const spec = anOpenApiSpec() .withOperationReturningString('put', '/flag', 'setFlag') diff --git a/packages/rest/src/__tests__/integration/rest.application.integration.ts b/packages/rest/src/__tests__/integration/rest.application.integration.ts index 5f7dc3bd9d01..8ed4d5097d12 100644 --- a/packages/rest/src/__tests__/integration/rest.application.integration.ts +++ b/packages/rest/src/__tests__/integration/rest.application.integration.ts @@ -5,9 +5,17 @@ import {anOperationSpec} from '@loopback/openapi-spec-builder'; import {Client, createRestAppClient, expect} from '@loopback/testlab'; +import * as express from 'express'; +import {Request, Response} from 'express'; import * as fs from 'fs'; import * as path from 'path'; -import {RestApplication, RestServer, RestServerConfig, get} from '../..'; +import { + get, + RestApplication, + RestServer, + RestServerConfig, + RouterSpec, +} from '../..'; const ASSETS = path.resolve(__dirname, '../../../fixtures/assets'); @@ -163,6 +171,103 @@ describe('RestApplication (integration)', () => { await client.get(response.header.location).expect(200, 'Hi'); }); + context('mounting an Express router on a LoopBack application', async () => { + beforeEach('set up RestApplication', async () => { + givenApplication(); + await restApp.start(); + client = createRestAppClient(restApp); + }); + + it('gives precedence to an external route over a static route', async () => { + const router = express.Router(); + router.get('/', function(_req: Request, res: Response) { + res.send('External dog'); + }); + + restApp.static('/dogs', ASSETS); + restApp.mountExpressRouter('/dogs', router); + + await client.get('/dogs/').expect(200, 'External dog'); + }); + + it('mounts an express Router without spec', async () => { + const router = express.Router(); + router.get('/poodle/', function(_req: Request, res: Response) { + res.send('Poodle!'); + }); + router.get('/pug', function(_req: Request, res: Response) { + res.send('Pug!'); + }); + restApp.mountExpressRouter('/dogs', router); + + await client.get('/dogs/poodle/').expect(200, 'Poodle!'); + await client.get('/dogs/pug').expect(200, 'Pug!'); + }); + + it('mounts an express Router with spec', async () => { + const router = express.Router(); + function greetDogs(_req: Request, res: Response) { + res.send('Hello dogs!'); + } + + const spec: RouterSpec = { + paths: { + '/hello': { + get: { + responses: { + '200': { + description: 'greet the dogs', + content: { + 'text/plain': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }, + }; + router.get('/hello', greetDogs); + restApp.mountExpressRouter('/dogs', router, spec); + await client.get('/dogs/hello').expect(200, 'Hello dogs!'); + + const openApiSpec = restApp.restServer.getApiSpec(); + expect(openApiSpec.paths).to.deepEqual({ + '/dogs/hello': { + get: { + responses: { + '200': { + description: 'greet the dogs', + content: {'text/plain': {schema: {type: 'string'}}}, + }, + }, + }, + }, + }); + }); + + it('mounts more than one express Router', async () => { + const router = express.Router(); + router.get('/poodle', function(_req: Request, res: Response) { + res.send('Poodle!'); + }); + + restApp.mountExpressRouter('/dogs', router); + + const secondRouter = express.Router(); + + secondRouter.get('/persian', function(_req: Request, res: Response) { + res.send('Persian cat.'); + }); + + restApp.mountExpressRouter('/cats', secondRouter); + + await client.get('/dogs/poodle').expect(200, 'Poodle!'); + await client.get('/cats/persian').expect(200, 'Persian cat.'); + }); + }); + function givenApplication(options?: {rest: RestServerConfig}) { options = options || {rest: {port: 0, host: '127.0.0.1'}}; restApp = new RestApplication(options); diff --git a/packages/rest/src/__tests__/integration/rest.server.integration.ts b/packages/rest/src/__tests__/integration/rest.server.integration.ts index c5ebdca63072..f968d8c334a3 100644 --- a/packages/rest/src/__tests__/integration/rest.server.integration.ts +++ b/packages/rest/src/__tests__/integration/rest.server.integration.ts @@ -6,12 +6,12 @@ import {Application} from '@loopback/core'; import { createClientForHandler, + createRestAppClient, expect, givenHttpServerConfig, httpsGetAsync, itSkippedOnTravis, supertest, - createRestAppClient, } from '@loopback/testlab'; import * as fs from 'fs'; import {IncomingMessage, ServerResponse} from 'http'; @@ -26,11 +26,11 @@ import { Request, requestBody, RequestContext, + RestApplication, RestBindings, RestComponent, RestServer, RestServerConfig, - RestApplication, } from '../..'; const readFileAsync = util.promisify(fs.readFile); @@ -38,20 +38,76 @@ const FIXTURES = path.resolve(__dirname, '../../../fixtures'); const ASSETS = path.resolve(FIXTURES, 'assets'); describe('RestServer (integration)', () => { - it('exports url property', async () => { - const server = await givenAServer(); - server.handler(dummyRequestHandler); - expect(server.url).to.be.undefined(); - await server.start(); - expect(server) - .to.have.property('url') - .which.is.a.String() - .match(/http|https\:\/\//); - await supertest(server.url) - .get('/') - .expect(200, 'Hello'); - await server.stop(); - expect(server.url).to.be.undefined(); + describe('url/rootUrl properties', () => { + let server: RestServer; + + afterEach('shuts down server', async () => { + if (!server) return; + await server.stop(); + expect(server.url).to.be.undefined(); + expect(server.rootUrl).to.be.undefined(); + }); + + describe('url', () => { + it('exports url property', async () => { + server = await givenAServer(); + server.handler(dummyRequestHandler); + expect(server.url).to.be.undefined(); + await server.start(); + expect(server) + .to.have.property('url') + .which.is.a.String() + .match(/http|https\:\/\//); + await supertest(server.url) + .get('/') + .expect(200, 'Hello'); + }); + + it('includes basePath in the url property', async () => { + server = await givenAServer({rest: {basePath: '/api'}}); + server.handler(dummyRequestHandler); + expect(server.url).to.be.undefined(); + await server.start(); + expect(server) + .to.have.property('url') + .which.is.a.String() + .match(/http|https\:\/\//); + expect(server.url).to.match(/api$/); + await supertest(server.url) + .get('/') + .expect(200, 'Hello'); + }); + }); + + describe('rootUrl', () => { + it('exports rootUrl property', async () => { + server = await givenAServer(); + server.handler(dummyRequestHandler); + expect(server.rootUrl).to.be.undefined(); + await server.start(); + expect(server) + .to.have.property('rootUrl') + .which.is.a.String() + .match(/http|https\:\/\//); + await supertest(server.rootUrl) + .get('/api') + .expect(200, 'Hello'); + }); + + it('does not include basePath in rootUrl', async () => { + server = await givenAServer({rest: {basePath: '/api'}}); + server.handler(dummyRequestHandler); + expect(server.rootUrl).to.be.undefined(); + await server.start(); + expect(server) + .to.have.property('rootUrl') + .which.is.a.String() + .match(/http|https\:\/\//); + await supertest(server.rootUrl) + .get('/api') + .expect(200, 'Hello'); + }); + }); }); it('parses query without decorated rest query params', async () => { diff --git a/packages/rest/src/__tests__/unit/router/assign-router-spec.unit.ts b/packages/rest/src/__tests__/unit/router/assign-router-spec.unit.ts new file mode 100644 index 000000000000..95dff1f931f3 --- /dev/null +++ b/packages/rest/src/__tests__/unit/router/assign-router-spec.unit.ts @@ -0,0 +1,151 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import {assignRouterSpec, RouterSpec} from '../../../'; + +describe('assignRouterSpec', () => { + it('duplicates the additions spec if the target spec is empty', async () => { + const target: RouterSpec = {paths: {}}; + const additions: RouterSpec = { + paths: { + '/': { + get: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + Greeting: { + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + }, + }, + }, + tags: [{name: 'greeting', description: 'greetings'}], + }; + + assignRouterSpec(target, additions); + expect(target).to.eql(additions); + }); + + it('does not assign components without schema', async () => { + const target: RouterSpec = { + paths: {}, + components: {}, + }; + + const additions: RouterSpec = { + paths: {}, + components: { + parameters: { + addParam: { + name: 'add', + in: 'query', + description: 'number of items to add', + required: true, + schema: { + type: 'integer', + format: 'int32', + }, + }, + }, + responses: { + Hello: { + description: 'Hello.', + }, + }, + }, + }; + + assignRouterSpec(target, additions); + expect(target.components).to.be.empty(); + }); + + it('uses the route registered first', async () => { + const originalPath = { + '/': { + get: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }; + + const target: RouterSpec = {paths: originalPath}; + + const additions: RouterSpec = { + paths: { + '/': { + get: { + responses: { + '200': { + description: 'additional greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + '404': { + description: 'Error: no greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }, + }; + + assignRouterSpec(target, additions); + expect(target.paths).to.eql(originalPath); + }); + + it('does not duplicate tags from the additional spec', async () => { + const target: RouterSpec = { + paths: {}, + tags: [{name: 'greeting', description: 'greetings'}], + }; + const additions: RouterSpec = { + paths: {}, + tags: [ + {name: 'greeting', description: 'additional greetings'}, + {name: 'salutation', description: 'salutations!'}, + ], + }; + + assignRouterSpec(target, additions); + expect(target.tags).to.containDeep([ + {name: 'greeting', description: 'greetings'}, + {name: 'salutation', description: 'salutations!'}, + ]); + }); +}); diff --git a/packages/rest/src/__tests__/unit/router/rebase-openapi-spec.unit.ts b/packages/rest/src/__tests__/unit/router/rebase-openapi-spec.unit.ts new file mode 100644 index 000000000000..59f318db3aa3 --- /dev/null +++ b/packages/rest/src/__tests__/unit/router/rebase-openapi-spec.unit.ts @@ -0,0 +1,144 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import {rebaseOpenApiSpec} from '../../../router'; + +describe('rebaseOpenApiSpec', () => { + it('does not modify an OpenAPI spec if it does not have paths', async () => { + const spec = { + title: 'Greetings', + components: { + responses: { + Hello: { + description: 'Hello.', + }, + }, + }, + tags: [{name: 'greeting', description: 'greetings'}], + }; + const rebasedSpec = rebaseOpenApiSpec(spec, '/api'); + + expect(rebasedSpec).to.eql(spec); + }); + + it('does not modify the OpenApiSpec if basePath is empty or `/`', async () => { + const spec = { + paths: { + '/': { + get: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + '/hello': { + post: { + summary: 'says hello', + consumes: 'application/json', + responses: { + '200': { + description: 'OK', + }, + }, + }, + }, + }, + }; + + let rebasedSpec = rebaseOpenApiSpec(spec, ''); + expect(spec).to.eql(rebasedSpec); + + rebasedSpec = rebaseOpenApiSpec(spec, '/'); + expect(rebasedSpec).to.eql(spec); + }); + + it('rebases OpenApiSpec if there is a basePath', async () => { + const spec = { + paths: { + '/': { + get: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + '/hello': { + post: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }, + }; + + const rebasedSpec = rebaseOpenApiSpec(spec, '/greetings'); + const rebasedPaths = Object.keys(rebasedSpec.paths); + + expect(rebasedPaths).to.eql(['/greetings/', '/greetings/hello']); + }); + + it('does not modify the original OpenApiSpec', async () => { + const spec = { + paths: { + '/': { + get: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + '/hello': { + post: { + responses: { + '200': { + description: 'greeting', + content: { + 'application/json': { + schema: {type: 'string'}, + }, + }, + }, + }, + }, + }, + }, + }; + + rebaseOpenApiSpec(spec, '/greetings'); + + const specPaths = Object.keys(spec.paths); + expect(specPaths).to.deepEqual(['/', '/hello']); + }); +}); diff --git a/packages/rest/src/rest.application.ts b/packages/rest/src/rest.application.ts index 70d26e8272bd..05c34e390d78 100644 --- a/packages/rest/src/rest.application.ts +++ b/packages/rest/src/rest.application.ts @@ -3,18 +3,24 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Binding, Constructor, BindingAddress} from '@loopback/context'; +import {Binding, BindingAddress, Constructor} from '@loopback/context'; import {Application, ApplicationConfig, Server} from '@loopback/core'; import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3-types'; import {PathParams} from 'express-serve-static-core'; import {ServeStaticOptions} from 'serve-static'; import {format} from 'util'; +import {BodyParser} from './body-parsers'; import {RestBindings} from './keys'; import {RestComponent} from './rest.component'; import {HttpRequestListener, HttpServerLike, RestServer} from './rest.server'; -import {ControllerClass, ControllerFactory, RouteEntry} from './router'; +import { + ControllerClass, + ControllerFactory, + ExpressRequestHandler, + RouteEntry, +} from './router'; +import {RouterSpec} from './router/router-spec'; import {SequenceFunction, SequenceHandler} from './sequence'; -import {BodyParser} from './body-parsers'; export const ERR_NO_MULTI_SERVER = format( 'RestApplication does not support multiple servers!', @@ -264,4 +270,23 @@ export class RestApplication extends Application implements HttpServerLike { api(spec: OpenApiSpec): Binding { return this.bind(RestBindings.API_SPEC).to(spec); } + + /** + * Mount an Express router to expose additional REST endpoints handled + * via legacy Express-based stack. + * + * @param basePath Path where to mount the router at, e.g. `/` or `/api`. + * @param router The Express router to handle the requests. + * @param spec A partial OpenAPI spec describing endpoints provided by the + * router. LoopBack will prepend `basePath` to all endpoints automatically. + * This argument is optional. You can leave it out if you don't want to + * document the routes. + */ + mountExpressRouter( + basePath: string, + router: ExpressRequestHandler, + spec?: RouterSpec, + ): void { + this.restServer.mountExpressRouter(basePath, router, spec); + } } diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index f64aaf93c25e..f63603b7330d 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -38,13 +38,16 @@ import { ControllerInstance, ControllerRoute, createControllerFactoryForBinding, + ExpressRequestHandler, ExternalExpressRoutes, RedirectRoute, RestRouterOptions, Route, RouteEntry, + RouterSpec, RoutingTable, } from './router'; +import {assignRouterSpec} from './router/router-spec'; import {DefaultSequence, SequenceFunction, SequenceHandler} from './sequence'; import { FindRoute, @@ -151,7 +154,23 @@ export class RestServer extends Context implements Server, HttpServerLike { return this._httpServer ? this._httpServer.listening : false; } + /** + * The base url for the server, including the basePath if set. For example, + * the value will be 'http://localhost:3000/api' if `basePath` is set to + * '/api'. + */ get url(): string | undefined { + let serverUrl = this.rootUrl; + if (!serverUrl) return serverUrl; + serverUrl = serverUrl + (this._basePath || ''); + return serverUrl; + } + + /** + * The root url for the server without the basePath. For example, the value + * will be 'http://localhost:3000' regardless of the `basePath`. + */ + get rootUrl(): string | undefined { return this._httpServer && this._httpServer.url; } @@ -665,6 +684,8 @@ export class RestServer extends Context implements Server, HttpServerLike { spec.components = spec.components || {}; spec.components.schemas = cloneDeep(defs); } + + assignRouterSpec(spec, this._externalRoutes.routerSpec); return spec; } @@ -811,6 +832,25 @@ export class RestServer extends Context implements Server, HttpServerLike { throw err; }); } + + /** + * Mount an Express router to expose additional REST endpoints handled + * via legacy Express-based stack. + * + * @param basePath Path where to mount the router at, e.g. `/` or `/api`. + * @param router The Express router to handle the requests. + * @param spec A partial OpenAPI spec describing endpoints provided by the + * router. LoopBack will prepend `basePath` to all endpoints automatically. + * This argument is optional. You can leave it out if you don't want to + * document the routes. + */ + mountExpressRouter( + basePath: string, + router: ExpressRequestHandler, + spec?: RouterSpec, + ): void { + this._externalRoutes.mountRouter(basePath, router, spec); + } } /** diff --git a/packages/rest/src/router/external-express-routes.ts b/packages/rest/src/router/external-express-routes.ts index dba6216a0f92..2e3ffcb1b4d7 100644 --- a/packages/rest/src/router/external-express-routes.ts +++ b/packages/rest/src/router/external-express-routes.ts @@ -4,7 +4,11 @@ // License text available at https://opensource.org/licenses/MIT import {Context} from '@loopback/context'; -import {OperationObject, SchemasObject} from '@loopback/openapi-v3-types'; +import { + OpenApiSpec, + OperationObject, + SchemasObject, +} from '@loopback/openapi-v3-types'; import * as express from 'express'; import {RequestHandler} from 'express'; import {PathParams} from 'express-serve-static-core'; @@ -21,6 +25,7 @@ import { Response, } from '../types'; import {ResolvedRoute, RouteEntry} from './route-entry'; +import {assignRouterSpec, RouterSpec} from './router-spec'; export type ExpressRequestHandler = express.RequestHandler; @@ -32,7 +37,13 @@ export type ExpressRequestHandler = express.RequestHandler; * @private */ export class ExternalExpressRoutes { + protected _externalRoutes: express.Router = express.Router(); protected _staticRoutes: express.Router = express.Router(); + protected _specForExternalRoutes: RouterSpec = {paths: {}}; + + get routerSpec(): RouterSpec { + return this._specForExternalRoutes; + } public registerAssets( path: PathParams, @@ -42,12 +53,29 @@ export class ExternalExpressRoutes { this._staticRoutes.use(path, express.static(rootDir, options)); } + public mountRouter( + basePath: string, + router: ExpressRequestHandler, + spec: RouterSpec = {paths: {}}, + ) { + this._externalRoutes.use(basePath, router); + + spec = rebaseOpenApiSpec(spec, basePath); + assignRouterSpec(this._specForExternalRoutes, spec); + } + find(request: Request): ResolvedRoute { - return new ExternalRoute(this._staticRoutes, request.method, request.url, { - description: 'LoopBack static assets route', - 'x-visibility': 'undocumented', - responses: {}, - }); + return new ExternalRoute( + this._externalRoutes, + this._staticRoutes, + request.method, + request.url, + { + description: 'External route or a static asset', + 'x-visibility': 'undocumented', + responses: {}, + }, + ); } } @@ -57,6 +85,7 @@ class ExternalRoute implements RouteEntry, ResolvedRoute { readonly schemas: SchemasObject = {}; constructor( + private readonly _externalRouter: express.Router, private readonly _staticAssets: express.Router, public readonly verb: string, public readonly path: string, @@ -71,7 +100,14 @@ class ExternalRoute implements RouteEntry, ResolvedRoute { {request, response}: RequestContext, args: OperationArgs, ): Promise { - const handled = await executeRequestHandler( + let handled = await executeRequestHandler( + this._externalRouter, + request, + response, + ); + if (handled) return; + + handled = await executeRequestHandler( this._staticAssets, request, response, @@ -90,6 +126,24 @@ class ExternalRoute implements RouteEntry, ResolvedRoute { } } +export function rebaseOpenApiSpec>( + spec: T, + basePath: string, +): T { + if (!spec.paths) return spec; + if (!basePath || basePath === '/') return spec; + + const localPaths = spec.paths; + // Don't modify the spec object provided to us. + spec = Object.assign({}, spec); + spec.paths = {}; + for (const url in localPaths) { + spec.paths[`${basePath}${url}`] = localPaths[url]; + } + + return spec; +} + const onFinishedAsync = promisify(onFinished); /** diff --git a/packages/rest/src/router/index.ts b/packages/rest/src/router/index.ts index 61dffd500fbe..395024076bdc 100644 --- a/packages/rest/src/router/index.ts +++ b/packages/rest/src/router/index.ts @@ -21,3 +21,4 @@ export * from './routing-table'; export * from './route-sort'; export * from './openapi-path'; export * from './trie'; +export * from './router-spec'; diff --git a/packages/rest/src/router/router-spec.ts b/packages/rest/src/router/router-spec.ts new file mode 100644 index 000000000000..31b8aa2e0a40 --- /dev/null +++ b/packages/rest/src/router/router-spec.ts @@ -0,0 +1,34 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {OpenApiSpec} from '@loopback/openapi-v3-types'; + +export type RouterSpec = Pick; + +export function assignRouterSpec(target: RouterSpec, additions: RouterSpec) { + if (additions.components && additions.components.schemas) { + if (!target.components) target.components = {}; + if (!target.components.schemas) target.components.schemas = {}; + Object.assign(target.components.schemas, additions.components.schemas); + } + + for (const url in additions.paths) { + if (!(url in target.paths)) target.paths[url] = {}; + for (const verbOrKey in additions.paths[url]) { + // routes registered earlier takes precedence + if (verbOrKey in target.paths[url]) continue; + target.paths[url][verbOrKey] = additions.paths[url][verbOrKey]; + } + } + + if (additions.tags && additions.tags.length > 0) { + if (!target.tags) target.tags = []; + for (const tag of additions.tags) { + // tags defined earlier take precedence + if (target.tags.some(t => t.name === tag.name)) continue; + target.tags.push(tag); + } + } +} diff --git a/packages/rest/src/router/routing-table.ts b/packages/rest/src/router/routing-table.ts index 54a1e45904c7..234c525293a6 100644 --- a/packages/rest/src/router/routing-table.ts +++ b/packages/rest/src/router/routing-table.ts @@ -19,10 +19,10 @@ import { ControllerFactory, ControllerRoute, } from './controller-route'; +import {ExternalExpressRoutes} from './external-express-routes'; import {validateApiPath} from './openapi-path'; import {RestRouter} from './rest-router'; import {ResolvedRoute, RouteEntry} from './route-entry'; -import {ExternalExpressRoutes} from './external-express-routes'; import {TrieRouter} from './trie-router'; const debug = debugFactory('loopback:rest:routing-table'); @@ -137,7 +137,7 @@ export class RoutingTable { if (this._externalRoutes) { debug( - 'No API route found for %s %s, trying to find a static asset', + 'No API route found for %s %s, trying to find an external Express route', request.method, request.path, ); diff --git a/packages/service-proxy/CHANGELOG.md b/packages/service-proxy/CHANGELOG.md index 6539d711874f..b4fea0ab9dd6 100644 --- a/packages/service-proxy/CHANGELOG.md +++ b/packages/service-proxy/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.1.2](https://github.com/strongloop/loopback-next/compare/@loopback/service-proxy@1.1.1...@loopback/service-proxy@1.1.2) (2019-04-05) + +**Note:** Version bump only for package @loopback/service-proxy + + + + + ## [1.1.1](https://github.com/strongloop/loopback-next/compare/@loopback/service-proxy@1.1.0...@loopback/service-proxy@1.1.1) (2019-03-22) **Note:** Version bump only for package @loopback/service-proxy diff --git a/packages/service-proxy/package-lock.json b/packages/service-proxy/package-lock.json index 76f06fb1c8fb..0484dfc8cc47 100644 --- a/packages/service-proxy/package-lock.json +++ b/packages/service-proxy/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/service-proxy", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -232,9 +232,9 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "loopback-connector": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.6.1.tgz", - "integrity": "sha512-2bVA3sMokZBoijxYhLJshNK5ADgdo4XA/j5sIItKyDvXvlkBvkpwP55G9qXw98PP3K5DC8tE+x597lFGY1MmFg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.6.2.tgz", + "integrity": "sha512-EJEactR/t8sdnpF+UqNQTo39ALWr3FB6b6rkEb6NdTEFOnXI+ZnW47sufqJmmh7U9JvIvm9WC61PNLvoP5y0cg==", "requires": { "async": "^2.1.5", "bluebird": "^3.4.6", @@ -497,17 +497,17 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", "requires": { "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", + "debug": "^4.1.1", + "globalize": "^1.4.2", "lodash": "^4.17.4", "md5": "^2.2.1", "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", + "os-locale": "^3.1.0", "yamljs": "^0.3.0" } }, diff --git a/packages/service-proxy/package.json b/packages/service-proxy/package.json index 354f9a3be780..c58f6cd5ae90 100644 --- a/packages/service-proxy/package.json +++ b/packages/service-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/service-proxy", - "version": "1.1.1", + "version": "1.1.2", "description": "Service integration for LoopBack 4", "engines": { "node": ">=8.9" @@ -24,14 +24,14 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/testlab": "^1.2.1", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/testlab": "^1.2.2", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "dependencies": { - "@loopback/context": "^1.8.1", - "@loopback/core": "^1.2.1", + "@loopback/context": "^1.9.0", + "@loopback/core": "^1.3.0", "loopback-datasource-juggler": "^4.0.0" }, "files": [ diff --git a/packages/testlab/CHANGELOG.md b/packages/testlab/CHANGELOG.md index 432bd6c60de3..9cc440445fa4 100644 --- a/packages/testlab/CHANGELOG.md +++ b/packages/testlab/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.2](https://github.com/strongloop/loopback-next/compare/@loopback/testlab@1.2.1...@loopback/testlab@1.2.2) (2019-04-05) + + +### Bug Fixes + +* **rest:** make sure basePath is included in RestServer.url ([705bce4](https://github.com/strongloop/loopback-next/commit/705bce4)) + + + + + ## [1.2.1](https://github.com/strongloop/loopback-next/compare/@loopback/testlab@1.2.0...@loopback/testlab@1.2.1) (2019-03-22) diff --git a/packages/testlab/package-lock.json b/packages/testlab/package-lock.json index 7d9f4bf3aff7..929fbab8d820 100644 --- a/packages/testlab/package-lock.json +++ b/packages/testlab/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/testlab", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -23,11 +23,11 @@ } }, "@babel/runtime": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz", - "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.2.tgz", + "integrity": "sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==", "requires": { - "regenerator-runtime": "^0.12.0" + "regenerator-runtime": "^0.13.2" } }, "@sinonjs/commons": { @@ -48,9 +48,9 @@ } }, "@sinonjs/samsam": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.0.tgz", - "integrity": "sha512-beHeJM/RRAaLLsMJhsCvHK31rIqZuobfPLa/80yGH5hnD8PV1hyh9xJBJNFfNmO7yWqm+zomijHsXpI6iTQJfQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", "requires": { "@sinonjs/commons": "^1.0.2", "array-from": "^2.1.1", @@ -69,13 +69,6 @@ "requires": { "@types/connect": "*", "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/connect": { @@ -84,13 +77,6 @@ "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/cookiejar": { @@ -109,19 +95,12 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz", - "integrity": "sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz", + "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==", "requires": { "@types/node": "*", "@types/range-parser": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/fs-extra": { @@ -130,13 +109,6 @@ "integrity": "sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/mime": { @@ -147,8 +119,7 @@ "@types/node": { "version": "10.14.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.1.tgz", - "integrity": "sha512-Rymt08vh1GaW4vYB6QP61/5m/CFLGnFZP++bJpWbiNxceNa6RBipDmb413jvtSf/R1gg5a/jQVl2jY4XVRscEA==", - "dev": true + "integrity": "sha512-Rymt08vh1GaW4vYB6QP61/5m/CFLGnFZP++bJpWbiNxceNa6RBipDmb413jvtSf/R1gg5a/jQVl2jY4XVRscEA==" }, "@types/range-parser": { "version": "1.2.3", @@ -170,19 +141,12 @@ "integrity": "sha512-Xv+n8yfccuicMlwBY58K5PVVNtXRm7uDzcwwmCarBxMP+XxGfnh1BI06YiVAsPbTAzcnYsrzpoS5QHeyV7LS8A==", "requires": { "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/sinon": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.10.tgz", - "integrity": "sha512-4w7SvsiUOtd4mUfund9QROPSJ5At/GQskDpqd87pJIRI6ULWSJqHI3GIZE337wQuN3aznroJGr94+o8fwvL37Q==" + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.11.tgz", + "integrity": "sha512-6ee09Ugx6GyEr0opUIakmxIWFNmqYPjkqa3/BuxCBokA0klsOLPgMD5K4q40lH7/yZVuJVzOfQpd7pipwjngkQ==" }, "@types/superagent": { "version": "4.1.1", @@ -191,13 +155,6 @@ "requires": { "@types/cookiejar": "*", "@types/node": "*" - }, - "dependencies": { - "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" - } } }, "@types/supertest": { @@ -633,9 +590,9 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "hoek": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", - "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==" + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" }, "http-errors": { "version": "1.6.3", @@ -920,9 +877,9 @@ } }, "oas-resolver": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.2.2.tgz", - "integrity": "sha512-+k7Pn/4WDK8ZtMiu09LjXRGbVmcr8h5g76YsQhH9og2zM8fc9MZLdNXuQTWFJG7R6OlHHEru7CFQLmz+tjBQhw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.2.3.tgz", + "integrity": "sha512-C3tQ9gmgVtu4sninufsqvqYreQgDzz6hQmXe5t1TqhnihbANQyW7Wm0+lO/LaguCVtJLyRRdDFxKRKX7xUO0OQ==", "requires": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.7", @@ -937,16 +894,16 @@ "integrity": "sha512-Q9xqeUtc17ccP/dpUfARci4kwFFszyJAgR/wbDhrRR/73GqsY5uSmKaIK+RmBqO8J4jVYrrDPjQKvt1IcpQdGw==" }, "oas-validator": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.2.2.tgz", - "integrity": "sha512-LQUmTq96aLqF/tNQkkHJWssODL5hbKSFeIsxeXbAwlM+9lGmhgK3dM0KOYc5QZv9ZTQhf0FYjhOmm1t7klqoJA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.2.4.tgz", + "integrity": "sha512-RO4r4Ni4YXS8q3cLCtBxsRS9H3/6LdQM3ncUNuOeAgkRvWXwYH2wl8h0vkIUHXks9af5nPZfzIoUztu+d9QHHg==", "requires": { "ajv": "^5.5.2", "better-ajv-errors": "^0.5.2", "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.7", "oas-linter": "^3.0.1", - "oas-resolver": "^2.2.2", + "oas-resolver": "^2.2.3", "oas-schema-walker": "^1.1.2", "reftools": "^1.0.7", "should": "^13.2.1", @@ -1111,9 +1068,9 @@ "integrity": "sha512-J4rugWI8+trddvJxXzK0VeEW9YBfofY5SOJzmvRRiVYRzbR8RbFjtlP2eZbJlqz5GwkvO9iCJZLvkem7dGA5zg==" }, "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" }, "require-directory": { "version": "2.1.1", @@ -1262,13 +1219,13 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.7.tgz", - "integrity": "sha512-rlrre9F80pIQr3M36gOdoCEWzFAMDgHYD8+tocqOw+Zw9OZ8F84a80Ds69eZfcjnzDqqG88ulFld0oin/6rG/g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.1.tgz", + "integrity": "sha512-eQKMaeWovtOtYe2xThEvaHmmxf870Di+bim10c3ZPrL5bZhLGtu8cz+rOBTFz0CwBV4Q/7dYwZiqZbGVLZ+vjQ==", "requires": { - "@sinonjs/commons": "^1.3.1", + "@sinonjs/commons": "^1.4.0", "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.2.0", + "@sinonjs/samsam": "^3.3.1", "diff": "^3.5.0", "lolex": "^3.1.0", "nise": "^1.4.10", diff --git a/packages/testlab/package.json b/packages/testlab/package.json index c133ca3e6442..d38411faaed0 100644 --- a/packages/testlab/package.json +++ b/packages/testlab/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/testlab", - "version": "1.2.1", + "version": "1.2.2", "description": "A collection of test utilities we use to write LoopBack tests.", "engines": { "node": ">=8.9" @@ -27,12 +27,12 @@ "oas-validator": "^3.1.0", "shot": "^4.0.7", "should": "^13.2.3", - "sinon": "^7.2.2", + "sinon": "^7.3.1", "supertest": "^4.0.2" }, "devDependencies": { - "@loopback/build": "^1.4.0", - "@loopback/tslint-config": "^2.0.3", + "@loopback/build": "^1.4.1", + "@loopback/tslint-config": "^2.0.4", "@types/node": "^10.11.2" }, "files": [ diff --git a/packages/testlab/src/client.ts b/packages/testlab/src/client.ts index 21ff87bc0c0a..ede41af880f4 100644 --- a/packages/testlab/src/client.ts +++ b/packages/testlab/src/client.ts @@ -33,7 +33,7 @@ export function createClientForHandler( * @param app A running (listening) instance of a RestApplication. */ export function createRestAppClient(app: RestApplicationLike) { - const url = app.restServer.url; + const url = app.restServer.rootUrl || app.restServer.url; if (!url) { throw new Error( `Cannot create client for ${app.constructor.name}, it is not listening.`, @@ -53,4 +53,5 @@ export interface RestApplicationLike { export interface RestServerLike { url?: string; + rootUrl?: string; } diff --git a/packages/tslint-config/CHANGELOG.md b/packages/tslint-config/CHANGELOG.md index a73f73c98e3e..417af433cd92 100644 --- a/packages/tslint-config/CHANGELOG.md +++ b/packages/tslint-config/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.0.4](https://github.com/strongloop/loopback-next/compare/@loopback/tslint-config@2.0.3...@loopback/tslint-config@2.0.4) (2019-04-05) + +**Note:** Version bump only for package @loopback/tslint-config + + + + + ## [2.0.3](https://github.com/strongloop/loopback-next/compare/@loopback/tslint-config@2.0.2...@loopback/tslint-config@2.0.3) (2019-03-22) **Note:** Version bump only for package @loopback/tslint-config diff --git a/packages/tslint-config/package-lock.json b/packages/tslint-config/package-lock.json index 451c4d6bf201..41e39007f325 100644 --- a/packages/tslint-config/package-lock.json +++ b/packages/tslint-config/package-lock.json @@ -1,6 +1,6 @@ { "name": "@loopback/tslint-config", - "version": "2.0.3", + "version": "2.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -16,9 +16,9 @@ }, "dependencies": { "tsutils": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.8.0.tgz", - "integrity": "sha512-XQdPhgcoTbCD8baXC38PQ0vpTZ8T3YrE+vR66YIj/xvDt1//8iAhafpIT/4DmvzzC1QFapEImERu48Pa01dIUA==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.9.1.tgz", + "integrity": "sha512-hrxVtLtPqQr//p8/msPT1X1UYXUjizqSit5d9AQ5k38TcV38NyecL5xODNxa73cLe/5sdiJ+w1FqzDhRBA/anA==", "requires": { "tslib": "^1.8.1" } @@ -36,9 +36,9 @@ } }, "get-caller-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.1.tgz", - "integrity": "sha512-SpOZHfz845AH0wJYVuZk2jWDqFmu7Xubsx+ldIpwzy5pDUpu7OJHK7QYNSA2NPlDSKQwM1GFaAkciOWjjW92Sg==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "inversify": { "version": "5.0.1", diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 38bf69360685..a8c617307e0c 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/tslint-config", - "version": "2.0.3", + "version": "2.0.4", "description": "", "engines": { "node": ">=8.9" diff --git a/sandbox/example/.npmrc b/sandbox/example/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/sandbox/example/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/sandbox/example/CHANGELOG.md b/sandbox/example/CHANGELOG.md index de8492ff5bc6..7e1517e6e6b7 100644 --- a/sandbox/example/CHANGELOG.md +++ b/sandbox/example/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.6](https://github.com/strongloop/loopback-next/compare/@loopback/sandbox-example@1.0.5...@loopback/sandbox-example@1.0.6) (2019-04-05) + +**Note:** Version bump only for package @loopback/sandbox-example + + + + + ## [1.0.5](https://github.com/strongloop/loopback-next/compare/@loopback/sandbox-example@1.0.4...@loopback/sandbox-example@1.0.5) (2019-03-22) **Note:** Version bump only for package @loopback/sandbox-example diff --git a/sandbox/example/package-lock.json b/sandbox/example/package-lock.json index 620a6a975ca2..48e341a0954d 100644 --- a/sandbox/example/package-lock.json +++ b/sandbox/example/package-lock.json @@ -1,15 +1,3 @@ { - "name": "@loopback/sandbox-example", - "version": "1.0.4", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@loopback/tslint-config": { - "version": "2.0.2", - "dev": true, - "requires": { - "tslint-consistent-codestyle": "^1.14.1" - } - } - } + "lockfileVersion": 1 } diff --git a/sandbox/example/package.json b/sandbox/example/package.json index e8aaa503ad62..5ab4ec1748af 100644 --- a/sandbox/example/package.json +++ b/sandbox/example/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/sandbox-example", - "version": "1.0.5", + "version": "1.0.6", "description": "Sample project for sandbox", "main": "index.js", "private": true, @@ -18,6 +18,6 @@ "url": "https://github.com/strongloop/loopback-next.git" }, "devDependencies": { - "@loopback/tslint-config": "^2.0.3" + "@loopback/tslint-config": "^2.0.4" } }