From 37d727a3041898a656d1baff03dfe2f2bb99167a Mon Sep 17 00:00:00 2001 From: Matthew Gabeler-Lee Date: Fri, 24 Aug 2018 19:05:30 -0400 Subject: [PATCH 01/30] fix: fix #1643: import MetadataAccessor direct from metadata to avoid TypeScript 3 compiler issue --- packages/authentication/package.json | 1 + packages/authentication/src/keys.ts | 3 ++- packages/repository-json-schema/package.json | 1 + packages/repository-json-schema/src/keys.ts | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 5b52638b891d..2797693fa5f1 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -24,6 +24,7 @@ "license": "MIT", "dependencies": { "@loopback/context": "^0.12.5", + "@loopback/metadata": "^0.9.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", "@loopback/openapi-v3": "^0.12.5", diff --git a/packages/authentication/src/keys.ts b/packages/authentication/src/keys.ts index 7d0c5e885137..8c91ce52af03 100644 --- a/packages/authentication/src/keys.ts +++ b/packages/authentication/src/keys.ts @@ -6,7 +6,8 @@ import {Strategy} from 'passport'; import {AuthenticateFn, UserProfile} from './types'; import {AuthenticationMetadata} from './decorators/authenticate.decorator'; -import {BindingKey, MetadataAccessor} from '@loopback/context'; +import {BindingKey} from '@loopback/context'; +import {MetadataAccessor} from '@loopback/metadata'; /** * Binding keys used by this component. diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index d5d8303c0569..7bd5ed571674 100644 --- a/packages/repository-json-schema/package.json +++ b/packages/repository-json-schema/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@loopback/context": "^0.12.5", + "@loopback/metadata": "^0.9.5", "@loopback/dist-util": "^0.3.6", "@loopback/repository": "^0.15.1", "@types/json-schema": "^6.0.1" diff --git a/packages/repository-json-schema/src/keys.ts b/packages/repository-json-schema/src/keys.ts index b2776b0a8ce6..4ff2894c402d 100644 --- a/packages/repository-json-schema/src/keys.ts +++ b/packages/repository-json-schema/src/keys.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {MetadataAccessor} from '@loopback/context'; +import {MetadataAccessor} from '@loopback/metadata'; import {JSONSchema6 as JSONSchema} from 'json-schema'; /** From 294c50ee85acc24596a863a102b1e30390ea3f89 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 24 Aug 2018 17:11:02 -0700 Subject: [PATCH 02/30] chore: publish release - @loopback/benchmark@0.2.3 - @loopback/docs@0.16.4 - @loopback/example-hello-world@0.9.6 - @loopback/example-log-extension@0.11.6 - @loopback/example-soap-calculator@0.2.3 - @loopback/example-todo-list@0.3.3 - @loopback/example-todo@0.16.3 - @loopback/authentication@0.11.6 - @loopback/boot@0.12.6 - @loopback/cli@0.21.4 - @loopback/openapi-v3@0.12.6 - @loopback/repository-json-schema@0.10.6 - @loopback/rest@0.19.6 --- benchmark/CHANGELOG.md | 9 +++++++ benchmark/package.json | 4 +-- docs/CHANGELOG.md | 9 +++++++ docs/package.json | 2 +- examples/hello-world/CHANGELOG.md | 9 +++++++ examples/hello-world/package.json | 4 +-- examples/log-extension/CHANGELOG.md | 9 +++++++ examples/log-extension/package.json | 6 ++--- examples/soap-calculator/CHANGELOG.md | 9 +++++++ examples/soap-calculator/package.json | 8 +++--- examples/todo-list/CHANGELOG.md | 9 +++++++ examples/todo-list/package.json | 8 +++--- examples/todo/CHANGELOG.md | 9 +++++++ examples/todo/package.json | 8 +++--- packages/authentication/CHANGELOG.md | 12 +++++++++ packages/authentication/package.json | 8 +++--- packages/boot/CHANGELOG.md | 9 +++++++ packages/boot/package.json | 6 ++--- packages/cli/CHANGELOG.md | 9 +++++++ packages/cli/package.json | 26 ++++++++++---------- packages/openapi-v3/CHANGELOG.md | 9 +++++++ packages/openapi-v3/package.json | 4 +-- packages/repository-json-schema/CHANGELOG.md | 12 +++++++++ packages/repository-json-schema/package.json | 4 +-- packages/rest/CHANGELOG.md | 9 +++++++ packages/rest/package.json | 4 +-- 26 files changed, 169 insertions(+), 46 deletions(-) diff --git a/benchmark/CHANGELOG.md b/benchmark/CHANGELOG.md index caf9b4eef4c0..800b3ceeee5f 100644 --- a/benchmark/CHANGELOG.md +++ b/benchmark/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.3](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@0.2.2...@loopback/benchmark@0.2.3) (2018-08-25) + +**Note:** Version bump only for package @loopback/benchmark + + + + + ## [0.2.2](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@0.2.1...@loopback/benchmark@0.2.2) (2018-08-24) diff --git a/benchmark/package.json b/benchmark/package.json index 5e08d5f81c6f..aa3db3de777d 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/benchmark", - "version": "0.2.2", + "version": "0.2.3", "private": true, "description": "Benchmarks measuring performance of our framework.", "keywords": [ @@ -39,7 +39,7 @@ ], "dependencies": { "@loopback/dist-util": "^0.3.6", - "@loopback/example-todo": "^0.16.2", + "@loopback/example-todo": "^0.16.3", "@types/byline": "^4.2.31", "@types/debug": "0.0.30", "@types/p-event": "^1.3.0", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2f2ed8011f21..67fbf3ece648 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.16.4](https://github.com/strongloop/loopback-next/compare/@loopback/docs@0.16.3...@loopback/docs@0.16.4) (2018-08-25) + +**Note:** Version bump only for package @loopback/docs + + + + + ## [0.16.3](https://github.com/strongloop/loopback-next/compare/@loopback/docs@0.16.2...@loopback/docs@0.16.3) (2018-08-24) diff --git a/docs/package.json b/docs/package.json index c9912f34cde3..7b2fc114019d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/docs", - "version": "0.16.3", + "version": "0.16.4", "description": "Documentation for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/docs", "author": { diff --git a/examples/hello-world/CHANGELOG.md b/examples/hello-world/CHANGELOG.md index 3d3fdc89cb69..18d041aff2df 100644 --- a/examples/hello-world/CHANGELOG.md +++ b/examples/hello-world/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.9.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@0.9.5...@loopback/example-hello-world@0.9.6) (2018-08-25) + +**Note:** Version bump only for package @loopback/example-hello-world + + + + + ## [0.9.5](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@0.9.4...@loopback/example-hello-world@0.9.5) (2018-08-24) diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index 1c10c8fcff0d..e0c3703a4102 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-hello-world", - "version": "0.9.5", + "version": "0.9.6", "description": "A simple hello-world Application using LoopBack 4", "main": "index.js", "engines": { @@ -42,7 +42,7 @@ "dependencies": { "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/rest": "^0.19.5" + "@loopback/rest": "^0.19.6" }, "devDependencies": { "@loopback/build": "^0.7.1", diff --git a/examples/log-extension/CHANGELOG.md b/examples/log-extension/CHANGELOG.md index 8cb18e915e20..f185ffcd30c4 100644 --- a/examples/log-extension/CHANGELOG.md +++ b/examples/log-extension/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.11.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@0.11.5...@loopback/example-log-extension@0.11.6) (2018-08-25) + +**Note:** Version bump only for package @loopback/example-log-extension + + + + + ## [0.11.5](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@0.11.4...@loopback/example-log-extension@0.11.5) (2018-08-24) diff --git a/examples/log-extension/package.json b/examples/log-extension/package.json index 9888e1b6a57b..4217f90e8b85 100644 --- a/examples/log-extension/package.json +++ b/examples/log-extension/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-log-extension", - "version": "0.11.5", + "version": "0.11.6", "description": "An example extension project for LoopBack 4", "main": "index.js", "engines": { @@ -53,8 +53,8 @@ "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.5", - "@loopback/rest": "^0.19.5", + "@loopback/openapi-v3": "^0.12.6", + "@loopback/rest": "^0.19.6", "chalk": "^2.3.2", "debug": "^3.1.0" } diff --git a/examples/soap-calculator/CHANGELOG.md b/examples/soap-calculator/CHANGELOG.md index cda818cf865e..1bef60acc62a 100644 --- a/examples/soap-calculator/CHANGELOG.md +++ b/examples/soap-calculator/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@0.2.2...@loopback/example-soap-calculator@0.2.3) (2018-08-25) + +**Note:** Version bump only for package @loopback/example-soap-calculator + + + + + ## [0.2.2](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@0.2.1...@loopback/example-soap-calculator@0.2.2) (2018-08-24) diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index 2307dcd1a21a..c08d463642cf 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-soap-calculator", - "version": "0.2.2", + "version": "0.2.3", "description": "Integrate a SOAP webservice with LoopBack 4", "keywords": [ "loopback", @@ -52,13 +52,13 @@ "src" ], "dependencies": { - "@loopback/boot": "^0.12.5", + "@loopback/boot": "^0.12.6", "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.5", + "@loopback/openapi-v3": "^0.12.6", "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.5", + "@loopback/rest": "^0.19.6", "@loopback/service-proxy": "^0.7.1", "loopback-connector-soap": "^4.2.0" }, diff --git a/examples/todo-list/CHANGELOG.md b/examples/todo-list/CHANGELOG.md index 1a22dbe9d7fa..5a2b9dbf8b39 100644 --- a/examples/todo-list/CHANGELOG.md +++ b/examples/todo-list/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@0.3.2...@loopback/example-todo-list@0.3.3) (2018-08-25) + +**Note:** Version bump only for package @loopback/example-todo-list + + + + + ## [0.3.2](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@0.3.1...@loopback/example-todo-list@0.3.2) (2018-08-24) diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index 9e4664a611d9..9b56c7744aa8 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo-list", - "version": "0.3.2", + "version": "0.3.3", "description": "Continuation of the todo example using relations in LoopBack 4.", "main": "index.js", "engines": { @@ -37,14 +37,14 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^0.12.5", + "@loopback/boot": "^0.12.6", "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.5", + "@loopback/openapi-v3": "^0.12.6", "@loopback/openapi-v3-types": "^0.8.5", "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.5", + "@loopback/rest": "^0.19.6", "@loopback/service-proxy": "^0.7.1", "loopback-connector-rest": "^3.1.1" }, diff --git a/examples/todo/CHANGELOG.md b/examples/todo/CHANGELOG.md index d2eb677f2ffa..1ddf137150f8 100644 --- a/examples/todo/CHANGELOG.md +++ b/examples/todo/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.16.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@0.16.2...@loopback/example-todo@0.16.3) (2018-08-25) + +**Note:** Version bump only for package @loopback/example-todo + + + + + ## [0.16.2](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@0.16.1...@loopback/example-todo@0.16.2) (2018-08-24) diff --git a/examples/todo/package.json b/examples/todo/package.json index 165d5d81469d..6fdf55cb5e0b 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo", - "version": "0.16.2", + "version": "0.16.3", "description": "Tutorial example on how to build an application with LoopBack 4.", "main": "index.js", "engines": { @@ -37,14 +37,14 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^0.12.5", + "@loopback/boot": "^0.12.6", "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.5", + "@loopback/openapi-v3": "^0.12.6", "@loopback/openapi-v3-types": "^0.8.5", "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.5", + "@loopback/rest": "^0.19.6", "@loopback/service-proxy": "^0.7.1", "loopback-connector-rest": "^3.1.1" }, diff --git a/packages/authentication/CHANGELOG.md b/packages/authentication/CHANGELOG.md index 60b03098e439..0c16d249c6f7 100644 --- a/packages/authentication/CHANGELOG.md +++ b/packages/authentication/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. + +## [0.11.6](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@0.11.5...@loopback/authentication@0.11.6) (2018-08-25) + + +### Bug Fixes + +* fix [#1643](https://github.com/strongloop/loopback-next/issues/1643): import MetadataAccessor direct from metadata to avoid TypeScript 3 compiler issue ([37d727a](https://github.com/strongloop/loopback-next/commit/37d727a)) + + + + + ## [0.11.5](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@0.11.4...@loopback/authentication@0.11.5) (2018-08-24) diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 2797693fa5f1..368859306d37 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/authentication", - "version": "0.11.5", + "version": "0.11.6", "description": "A LoopBack component for authentication support.", "engines": { "node": ">=8.9" @@ -24,11 +24,11 @@ "license": "MIT", "dependencies": { "@loopback/context": "^0.12.5", - "@loopback/metadata": "^0.9.5", "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.5", - "@loopback/rest": "^0.19.5", + "@loopback/metadata": "^0.9.5", + "@loopback/openapi-v3": "^0.12.6", + "@loopback/rest": "^0.19.6", "passport": "^0.4.0", "passport-strategy": "^1.0.0" }, diff --git a/packages/boot/CHANGELOG.md b/packages/boot/CHANGELOG.md index cab7f7aaf474..ae8e2632ff01 100644 --- a/packages/boot/CHANGELOG.md +++ b/packages/boot/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.12.6](https://github.com/strongloop/loopback-next/compare/@loopback/boot@0.12.5...@loopback/boot@0.12.6) (2018-08-25) + +**Note:** Version bump only for package @loopback/boot + + + + + ## [0.12.5](https://github.com/strongloop/loopback-next/compare/@loopback/boot@0.12.4...@loopback/boot@0.12.5) (2018-08-24) diff --git a/packages/boot/package.json b/packages/boot/package.json index 5cc714e01379..cc4850eda2af 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/boot", - "version": "0.12.5", + "version": "0.12.6", "description": "A collection of Booters for LoopBack 4 Applications", "engines": { "node": ">=8.9" @@ -37,8 +37,8 @@ }, "devDependencies": { "@loopback/build": "^0.7.1", - "@loopback/openapi-v3": "^0.12.5", - "@loopback/rest": "^0.19.5", + "@loopback/openapi-v3": "^0.12.6", + "@loopback/rest": "^0.19.6", "@loopback/testlab": "^0.11.5", "@types/node": "^10.1.1" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 1dfdc9d411d3..ed3a8e967392 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.21.4](https://github.com/strongloop/loopback-next/compare/@loopback/cli@0.21.3...@loopback/cli@0.21.4) (2018-08-25) + +**Note:** Version bump only for package @loopback/cli + + + + + ## [0.21.3](https://github.com/strongloop/loopback-next/compare/@loopback/cli@0.21.2...@loopback/cli@0.21.3) (2018-08-24) diff --git a/packages/cli/package.json b/packages/cli/package.json index 5473fc7ce784..e8111bee90d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/cli", - "version": "0.21.3", + "version": "0.21.4", "description": "Yeoman generator for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/packages/cli", "author": { @@ -86,33 +86,33 @@ "strong-docs": "^4.0.0", "tslint": "^5.9.1", "typescript": "^3.0.1", - "@loopback/authentication": "^0.11.5", - "@loopback/boot": "^0.12.5", + "@loopback/authentication": "^0.11.6", + "@loopback/boot": "^0.12.6", "@loopback/build": "^0.7.1", - "@loopback/cli": "^0.21.3", + "@loopback/cli": "^0.21.4", "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/metadata": "^0.9.5", "@loopback/openapi-spec-builder": "^0.8.5", "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/openapi-v3": "^0.12.5", - "@loopback/repository-json-schema": "^0.10.5", + "@loopback/openapi-v3": "^0.12.6", + "@loopback/repository-json-schema": "^0.10.6", "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.5", + "@loopback/rest": "^0.19.6", "@loopback/testlab": "^0.11.5", - "@loopback/docs": "^0.16.3", + "@loopback/docs": "^0.16.4", "glob": "^7.1.2", - "@loopback/example-hello-world": "^0.9.5", - "@loopback/example-log-extension": "^0.11.5", + "@loopback/example-hello-world": "^0.9.6", + "@loopback/example-log-extension": "^0.11.6", "@loopback/example-rpc-server": "^0.11.2", - "@loopback/example-todo": "^0.16.2", - "@loopback/example-soap-calculator": "^0.2.2", + "@loopback/example-todo": "^0.16.3", + "@loopback/example-soap-calculator": "^0.2.3", "@loopback/dist-util": "^0.3.6", "@loopback/service-proxy-proxy": "^0.4.0", "@loopback/service-proxy": "^0.7.1", "@loopback/http-caching-proxy": "^0.3.5", "@loopback/http-server": "^0.3.5", - "@loopback/example-todo-list": "^0.3.2" + "@loopback/example-todo-list": "^0.3.3" } }, "copyright.owner": "IBM Corp.", diff --git a/packages/openapi-v3/CHANGELOG.md b/packages/openapi-v3/CHANGELOG.md index dbbe42cedbf9..b145e19ab91e 100644 --- a/packages/openapi-v3/CHANGELOG.md +++ b/packages/openapi-v3/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.12.6](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@0.12.5...@loopback/openapi-v3@0.12.6) (2018-08-25) + +**Note:** Version bump only for package @loopback/openapi-v3 + + + + + ## [0.12.5](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@0.12.4...@loopback/openapi-v3@0.12.5) (2018-08-24) diff --git a/packages/openapi-v3/package.json b/packages/openapi-v3/package.json index ad3958b4e995..5ddeead9e225 100644 --- a/packages/openapi-v3/package.json +++ b/packages/openapi-v3/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/openapi-v3", - "version": "0.12.5", + "version": "0.12.6", "description": "Processes openapi v3 related metadata", "engines": { "node": ">=8.9" @@ -54,7 +54,7 @@ "@loopback/context": "^0.12.5", "@loopback/dist-util": "^0.3.6", "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/repository-json-schema": "^0.10.5", + "@loopback/repository-json-schema": "^0.10.6", "debug": "^3.1.0", "lodash": "^4.17.5" } diff --git a/packages/repository-json-schema/CHANGELOG.md b/packages/repository-json-schema/CHANGELOG.md index d639c0c1b9c9..f567ad8eb441 100644 --- a/packages/repository-json-schema/CHANGELOG.md +++ b/packages/repository-json-schema/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. + +## [0.10.6](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@0.10.5...@loopback/repository-json-schema@0.10.6) (2018-08-25) + + +### Bug Fixes + +* fix [#1643](https://github.com/strongloop/loopback-next/issues/1643): import MetadataAccessor direct from metadata to avoid TypeScript 3 compiler issue ([37d727a](https://github.com/strongloop/loopback-next/commit/37d727a)) + + + + + ## [0.10.5](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@0.10.4...@loopback/repository-json-schema@0.10.5) (2018-08-24) diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index 7bd5ed571674..01bb2ee73ec6 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": "0.10.5", + "version": "0.10.6", "description": "Converts TS classes into JSON Schemas using TypeScript's reflection API", "engines": { "node": ">=8.9" @@ -28,8 +28,8 @@ }, "dependencies": { "@loopback/context": "^0.12.5", - "@loopback/metadata": "^0.9.5", "@loopback/dist-util": "^0.3.6", + "@loopback/metadata": "^0.9.5", "@loopback/repository": "^0.15.1", "@types/json-schema": "^6.0.1" }, diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index 129b330f1d36..145a33d102e2 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.19.6](https://github.com/strongloop/loopback-next/compare/@loopback/rest@0.19.5...@loopback/rest@0.19.6) (2018-08-25) + +**Note:** Version bump only for package @loopback/rest + + + + + ## [0.19.5](https://github.com/strongloop/loopback-next/compare/@loopback/rest@0.19.4...@loopback/rest@0.19.5) (2018-08-24) diff --git a/packages/rest/package.json b/packages/rest/package.json index b4613982db51..4ac590e13c63 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest", - "version": "0.19.5", + "version": "0.19.6", "description": "", "engines": { "node": ">=8.9" @@ -26,7 +26,7 @@ "@loopback/context": "^0.12.5", "@loopback/core": "^0.11.5", "@loopback/http-server": "^0.3.5", - "@loopback/openapi-v3": "^0.12.5", + "@loopback/openapi-v3": "^0.12.6", "@loopback/openapi-v3-types": "^0.8.5", "@types/cors": "^2.8.3", "@types/express": "^4.11.1", From 35b916b2d4aac457edf62956e35b2057a3794b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 24 Aug 2018 14:55:52 +0200 Subject: [PATCH 03/30] fix: remove extra imports for mixin dependencies With the new TypeScript version, it is no longer necessary to manually import types referenced by mixin classes. This commit removes those extra imports from the CLI template and scaffolded "application.ts" files; and also sorts import using VS Code's feature "Organize imports". While removing unused imports, I cleaned up few more places where we were importing extra types. --- docs/site/Booting-an-Application.md | 7 +------ .../site/todo-tutorial-putting-it-together.md | 15 ++------------- examples/soap-calculator/src/application.ts | 15 ++------------- examples/todo-list/src/application.ts | 15 ++------------- examples/todo/src/application.ts | 17 +++-------------- packages/boot/test/fixtures/application.ts | 16 +++------------- .../app/templates/src/application.ts.ejs | 19 ++++--------------- packages/repository/README.md | 11 ++--------- .../src/repositories/legacy-juggler-bridge.ts | 3 --- .../test/unit/query/query-builder.unit.ts | 9 +-------- packages/rest/src/keys.ts | 3 --- 11 files changed, 20 insertions(+), 110 deletions(-) diff --git a/docs/site/Booting-an-Application.md b/docs/site/Booting-an-Application.md index 5a45a5e901b2..ea308cf760c7 100644 --- a/docs/site/Booting-an-Application.md +++ b/docs/site/Booting-an-Application.md @@ -61,13 +61,8 @@ example below)_ ### Using the BootMixin -`Booter` and `Binding` types must be imported alongside `BootMixin` to allow -TypeScript to infer types and avoid errors. _If using `tslint` with the -`no-unused-variable` rule, you can disable it for the import line by adding -`// tslint:disable-next-line:no-unused-variable` above the import statement_. - ```ts -import {BootMixin, Booter, Binding} from "@loopback/boot"; +import {BootMixin} from "@loopback/boot"; class MyApplication extends BootMixin(Application) { constructor(options?: ApplicationConfig) { diff --git a/docs/site/todo-tutorial-putting-it-together.md b/docs/site/todo-tutorial-putting-it-together.md index 4b873bf71494..4f7ce558029c 100644 --- a/docs/site/todo-tutorial-putting-it-together.md +++ b/docs/site/todo-tutorial-putting-it-together.md @@ -34,23 +34,12 @@ artifacts and inject them into our application for use. #### src/application.ts ```ts +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; import {RestApplication, RestServer} from '@loopback/rest'; import {MySequence} from './sequence'; -/* tslint:disable:no-unused-variable */ -// Binding and Booter imports are required to infer types for BootMixin! -import {BootMixin, Booter, Binding} from '@loopback/boot'; - -// juggler imports are required to infer types for RepositoryMixin! -import { - Class, - Repository, - RepositoryMixin, - juggler, -} from '@loopback/repository'; -/* tslint:enable:no-unused-variable */ - export class TodoListApplication extends BootMixin( RepositoryMixin(RestApplication), ) { diff --git a/examples/soap-calculator/src/application.ts b/examples/soap-calculator/src/application.ts index d1d8dd039dc3..f5a345f13ad0 100644 --- a/examples/soap-calculator/src/application.ts +++ b/examples/soap-calculator/src/application.ts @@ -1,21 +1,10 @@ +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig, Constructor, Provider} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {MySequence} from './sequence'; import {CalculatorServiceProvider} from './services/calculator.service'; -// Binding and Booter imports are required to infer types for BootMixin! -/* tslint:disable:no-unused-variable */ -import {BootMixin, Booter, Binding} from '@loopback/boot'; - -// juggler imports are required to infer types for RepositoryMixin! -import { - Class, - Repository, - RepositoryMixin, - juggler, -} from '@loopback/repository'; -/* tslint:enable:no-unused-variable */ - export class SoapCalculatorApplication extends BootMixin( RepositoryMixin(RestApplication), ) { diff --git a/examples/todo-list/src/application.ts b/examples/todo-list/src/application.ts index aee875e43b05..7394660e0ac3 100644 --- a/examples/todo-list/src/application.ts +++ b/examples/todo-list/src/application.ts @@ -3,23 +3,12 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {MySequence} from './sequence'; -/* tslint:disable:no-unused-variable */ -// Binding and Booter imports are required to infer types for BootMixin! -import {BootMixin, Booter, Binding} from '@loopback/boot'; - -// juggler imports are required to infer types for RepositoryMixin! -import { - Class, - Repository, - RepositoryMixin, - juggler, -} from '@loopback/repository'; -/* tslint:enable:no-unused-variable */ - export class TodoListApplication extends BootMixin( RepositoryMixin(RestApplication), ) { diff --git a/examples/todo/src/application.ts b/examples/todo/src/application.ts index 8f530e17910c..8c5db992351f 100644 --- a/examples/todo/src/application.ts +++ b/examples/todo/src/application.ts @@ -3,24 +3,13 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {ApplicationConfig, Provider, Constructor} from '@loopback/core'; +import {BootMixin} from '@loopback/boot'; +import {ApplicationConfig, Constructor, Provider} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {MySequence} from './sequence'; import {GeocoderServiceProvider} from './services'; -/* tslint:disable:no-unused-variable */ -// Binding and Booter imports are required to infer types for BootMixin! -import {BootMixin, Booter, Binding} from '@loopback/boot'; - -// juggler imports are required to infer types for RepositoryMixin! -import { - Class, - Repository, - RepositoryMixin, - juggler, -} from '@loopback/repository'; -/* tslint:enable:no-unused-variable */ - export class TodoListApplication extends BootMixin( RepositoryMixin(RestApplication), ) { diff --git a/packages/boot/test/fixtures/application.ts b/packages/boot/test/fixtures/application.ts index b39a55b3b508..567096bff60a 100644 --- a/packages/boot/test/fixtures/application.ts +++ b/packages/boot/test/fixtures/application.ts @@ -3,20 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {RestApplication} from '@loopback/rest'; import {ApplicationConfig} from '@loopback/core'; -// tslint:disable:no-unused-variable -import { - RepositoryMixin, - Class, - Repository, - juggler, -} from '@loopback/repository'; -// tslint:enable:no-unused-variable - -// Binding and Booter imports are required to infer types for BootMixin! -// tslint:disable-next-line:no-unused-variable -import {BootMixin, Booter, Binding} from '../../index'; +import {RepositoryMixin} from '@loopback/repository'; +import {RestApplication} from '@loopback/rest'; +import {BootMixin} from '../../index'; export class BooterApp extends RepositoryMixin(BootMixin(RestApplication)) { constructor(options?: ApplicationConfig) { diff --git a/packages/cli/generators/app/templates/src/application.ts.ejs b/packages/cli/generators/app/templates/src/application.ts.ejs index fbd92c420db2..6fd34c1a2eba 100644 --- a/packages/cli/generators/app/templates/src/application.ts.ejs +++ b/packages/cli/generators/app/templates/src/application.ts.ejs @@ -1,21 +1,10 @@ +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; -import {RestApplication} from '@loopback/rest'; -import {MySequence} from './sequence'; - -/* tslint:disable:no-unused-variable */ -// Binding and Booter imports are required to infer types for BootMixin! -import {BootMixin, Booter, Binding} from '@loopback/boot'; <% if (project.enableRepository) { -%> - -// juggler imports are required to infer types for RepositoryMixin! -import { - Class, - Repository, - RepositoryMixin, - juggler, -} from '@loopback/repository'; +import {RepositoryMixin} from '@loopback/repository'; <% } -%> -/* tslint:enable:no-unused-variable */ +import {RestApplication} from '@loopback/rest'; +import {MySequence} from './sequence'; export class <%= project.applicationName %> <% if (!project.enableRepository) {-%>extends BootMixin(RestApplication) {<% } else { -%>extends BootMixin( RepositoryMixin(RestApplication), diff --git a/packages/repository/README.md b/packages/repository/README.md index 714d0e421a94..d5cc104bfa8e 100644 --- a/packages/repository/README.md +++ b/packages/repository/README.md @@ -130,18 +130,11 @@ We'll use `BootMixin` on top of `RepositoryMixin` so that Repository bindings can be taken care of automatically at boot time before the application starts. ```ts +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {db} from './datasources/db.datasource'; -/* tslint:disable:no-unused-variable */ -import {BootMixin, Booter, Binding} from '@loopback/boot'; -import { - RepositoryMixin, - Class, - Repository, - juggler, -} from '@loopback/repository'; -/* tslint:enable:no-unused-variable */ export class RepoApplication extends BootMixin( RepositoryMixin(RestApplication), diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts index 8b5732239aaa..c862cbcb4e48 100644 --- a/packages/repository/src/repositories/legacy-juggler-bridge.ts +++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts @@ -23,9 +23,6 @@ import { HasManyRepositoryFactory, } from './relation.factory'; import {HasManyDefinition} from '../decorators/relation.decorator'; -// need the import for exporting of a return type -// tslint:disable-next-line:no-unused-variable -import {HasManyRepository} from './relation.repository'; export namespace juggler { export import DataSource = legacy.DataSource; diff --git a/packages/repository/test/unit/query/query-builder.unit.ts b/packages/repository/test/unit/query/query-builder.unit.ts index e03be68a5c94..2858602ea3ef 100644 --- a/packages/repository/test/unit/query/query-builder.unit.ts +++ b/packages/repository/test/unit/query/query-builder.unit.ts @@ -4,14 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; -import { - FilterBuilder, - Filter, - WhereBuilder, - Where, - filterTemplate, - isFilter, -} from '../../../'; +import {FilterBuilder, WhereBuilder, filterTemplate, isFilter} from '../../../'; describe('WhereBuilder', () => { it('builds where object', () => { diff --git a/packages/rest/src/keys.ts b/packages/rest/src/keys.ts index 9e3a225bc666..77c94063ff8b 100644 --- a/packages/rest/src/keys.ts +++ b/packages/rest/src/keys.ts @@ -22,9 +22,6 @@ import { Send, } from './types'; -// NOTE(bajtos) The following import is required to satisfy TypeScript compiler -// tslint:disable-next-line:no-unused-variable -import {OpenAPIObject} from '@loopback/openapi-v3-types'; import {HttpProtocol} from '@loopback/http-server'; import * as https from 'https'; From f5fc74f04cefb885de6c9498aeb5ac236a794d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 24 Aug 2018 16:08:29 +0200 Subject: [PATCH 04/30] build: enable tslint for "benchmark" package --- benchmark/src/benchmark.ts | 8 +------- benchmark/src/worker.ts | 6 +----- tsconfig.json | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/benchmark/src/benchmark.ts b/benchmark/src/benchmark.ts index b9e1ee629a95..4120974825ce 100644 --- a/benchmark/src/benchmark.ts +++ b/benchmark/src/benchmark.ts @@ -35,8 +35,6 @@ export type AutocannonFactory = (url: string) => Autocannon; export class Benchmark { private options: Options; - private worker: ChildProcess; - private url: string; // Customization points public cannonFactory: AutocannonFactory; @@ -81,7 +79,7 @@ export class Benchmark { const result = await runner.execute(autocannon); debug('Stats: %j', result); - closeWorker(worker); + await closeWorker(worker); debug('Worker stopped, done.'); this.logger(name, result); @@ -115,7 +113,3 @@ async function closeWorker(worker: ChildProcess) { worker.kill(); await pEvent(worker, 'close'); } - -function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/benchmark/src/worker.ts b/benchmark/src/worker.ts index 93a6a7491947..00c4c2d49f95 100644 --- a/benchmark/src/worker.ts +++ b/benchmark/src/worker.ts @@ -3,11 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - TodoListApplication, - TodoRepository, - Route, -} from '@loopback/example-todo'; +import {TodoListApplication} from '@loopback/example-todo'; async function main() { const app = new TodoListApplication({ diff --git a/tsconfig.json b/tsconfig.json index 3e83ce277542..c6d681524147 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,15 @@ { "extends": "./packages/build/config/tsconfig.common.json", - "include": ["packages", "examples"], - "exclude": ["node_modules/**", "packages/*/node_modules/**", "examples/*/node_modules/**", "**/*.d.ts"] + "include": [ + "benchmark", + "examples", + "packages" + ], + "exclude": [ + "node_modules/**", + "benchmark/node_modules/**", + "examples/*/node_modules/**", + "packages/*/node_modules/**", + "**/*.d.ts" + ] } From a40837782af8a7fc23d1b75f86f58f0b8eb913bb Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sat, 25 Aug 2018 12:47:21 -0700 Subject: [PATCH 05/30] chore: add a package to sandbox to avoid publish failure See https://github.com/lerna/lerna/issues/1602 --- .gitignore | 5 +++++ sandbox/.gitignore | 4 ---- sandbox/README.md | 13 ++++++++----- sandbox/example/index.js | 7 +++++++ sandbox/example/package.json | 23 +++++++++++++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) delete mode 100644 sandbox/.gitignore create mode 100644 sandbox/example/index.js create mode 100644 sandbox/example/package.json diff --git a/.gitignore b/.gitignore index b3f8fd40e49c..0fa74525b692 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ benchmark/dist* **/package .sandbox packages/cli/generators/datasource/connectors.json + +# Exclude all files under sandbox except README.md and example +/sandbox/* +!/sandbox/README.md +!/sandbox/example/ diff --git a/sandbox/.gitignore b/sandbox/.gitignore deleted file mode 100644 index a27261b52804..000000000000 --- a/sandbox/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/* -!README.md -!.gitignore - diff --git a/sandbox/README.md b/sandbox/README.md index 9dceaef6a5f1..d37f1839f101 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -15,13 +15,16 @@ cd loopback-next/sandbox ``` Now you can scaffold your Node.js modules or copy existing projects into the -`sandbox` directory. +`sandbox` directory, for example, `sandbox/example`. -To link the `@loopback/*` dependencies against the source code: +To link the `@loopback/*` dependencies for your project, run the following +commands: ```sh -cd loopback/next -npm run bootstrap +cd loopback-next +npm install +cd sandbox/example +node . ``` -Your project is ready against the LoopBack 4 source code now. +Your project is now ready against the LoopBack 4 source code. diff --git a/sandbox/example/index.js b/sandbox/example/index.js new file mode 100644 index 000000000000..4f257154224d --- /dev/null +++ b/sandbox/example/index.js @@ -0,0 +1,7 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/sandbox-example +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +const distUtilPkg = require('@loopback/dist-util/package.json'); +console.log('Resolved dependency: %s@%s', distUtilPkg.name, distUtilPkg.version); diff --git a/sandbox/example/package.json b/sandbox/example/package.json new file mode 100644 index 000000000000..eb8b28712d02 --- /dev/null +++ b/sandbox/example/package.json @@ -0,0 +1,23 @@ +{ + "name": "@loopback/sandbox-example", + "version": "0.1.0", + "description": "Sample project for sandbox", + "main": "index.js", + "private": true, + "scripts": { + "test": "echo \"This is an example for sandbox\"" + }, + "engines": { + "node": ">=8.9" + }, + "author": "IBM", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git" + }, + "dependencies": { + "@loopback/dist-util": "^0.3.6" + } +} From f68a45ced9a392286d484b72cc690a6599aeb0c0 Mon Sep 17 00:00:00 2001 From: Hage Yaapa Date: Mon, 20 Aug 2018 20:35:20 +0530 Subject: [PATCH 06/30] feat: default 404 for request to non-existent resource send 404 for requests to non-existent resoure by default. --- docs/site/Model.md | 8 +++++ .../src/repositories/legacy-juggler-bridge.ts | 2 +- .../test/acceptance/repository.acceptance.ts | 30 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/site/Model.md b/docs/site/Model.md index 3a8b5dff6b0f..172cf82933aa 100644 --- a/docs/site/Model.md +++ b/docs/site/Model.md @@ -147,6 +147,14 @@ class MyFlexibleModel extends Entity { } ``` +The default response for a delete request to a non-existent resource is a `404`. +You can change this behavior to `200` by setting `strictDelete` to `false`. + +```ts +@model({settings: {strictDelete: false}}) +class Todo extends Entity { ... } +``` + ### Model Decorator The model decorator can be used without any additional parameters, or can be diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts index c862cbcb4e48..61e551346370 100644 --- a/packages/repository/src/repositories/legacy-juggler-bridge.ts +++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts @@ -125,7 +125,7 @@ export class DefaultCrudRepository this.modelClass = dataSource.createModel( definition.name, properties, - Object.assign({strict: true}, definition.settings), + Object.assign({strict: true, strictDelete: true}, definition.settings), ); this.modelClass.attachTo(dataSource); } diff --git a/packages/repository/test/acceptance/repository.acceptance.ts b/packages/repository/test/acceptance/repository.acceptance.ts index 8dd64452e548..50647333d5e1 100644 --- a/packages/repository/test/acceptance/repository.acceptance.ts +++ b/packages/repository/test/acceptance/repository.acceptance.ts @@ -71,6 +71,36 @@ describe('Repository in Thinking in LoopBack', () => { expect(stored).to.containDeep({extra: 'additional data'}); }); + it('enables strict delete by default', async () => { + await repo.create({slug: 'pencil'}); + await expect(repo.deleteById(10000)).to.be.rejectedWith( + /No instance with id/, + ); + }); + + it('disables strict delete via configuration', async () => { + @model({settings: {strictDelete: false}}) + class Pencil extends Entity { + @property({id: true}) + id: number; + @property({type: 'string'}) + name: string; + } + + const pencilRepo = new DefaultCrudRepository< + Pencil, + typeof Pencil.prototype.id + >(Pencil, new DataSource({connector: 'memory'})); + + await pencilRepo.create({ + name: 'Green Pencil', + }); + + // When `strictDelete` is set to `false`, `deleteById()` on a non-existing + // resource is resolved with `false`, instead of being rejected. + await expect(pencilRepo.deleteById(10000)).to.be.fulfilledWith(false); + }); + function givenProductRepository() { const db = new DataSource({ connector: 'memory', From da30fd434a74870ba371f0bb50cdaa4a50f6f366 Mon Sep 17 00:00:00 2001 From: Mario Estrada Date: Sun, 26 Aug 2018 22:27:31 -0600 Subject: [PATCH 07/30] docs(example-soap-calculator): fix image link from README.md file add full path of image and adjust 2 links that were still not working from inside the first page --- docs/site/soap-calculator-tutorial.md | 4 ++-- examples/soap-calculator/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/site/soap-calculator-tutorial.md b/docs/site/soap-calculator-tutorial.md index 43e11ec9a61d..6fbca8136e11 100644 --- a/docs/site/soap-calculator-tutorial.md +++ b/docs/site/soap-calculator-tutorial.md @@ -45,8 +45,8 @@ Follow the following steps to start buiding your application: 3. [Add a data source](soap-calculator-tutorial-add-datasource.md) 4. [Add a Service](soap-calculator-tutorial-add-service.md) 5. [Add a controller](soap-calculator-tutorial-add-controller.md) -6. [Register the service](soap-calculator-tutorial-make-service-available.md) -7. [Run and Test the application](soap-calculator-run-and-and-test.md) +6. [Register the service](soap-calculator-tutorial-register-service.md) +7. [Run and Test the application](soap-calculator-tutorial-run-and-test.md) ## or Try it out diff --git a/examples/soap-calculator/README.md b/examples/soap-calculator/README.md index aa9cb3657c4d..435d091abd7e 100644 --- a/examples/soap-calculator/README.md +++ b/examples/soap-calculator/README.md @@ -8,7 +8,7 @@ This example project shows how to integrate a SOAP web service with LoopBack 4 and expose its methods through the REST API server. Acceptance and Integration tests are provided. -![soap-calculator-overview](../../docs/img/loopback-example-soap-calculator_figure1.png) +![soap-calculator-overview](https://loopback.io/pages/en/lb4/imgs/loopback-example-soap-calculator_figure1.png) ## Setup From be71ece26acd4421392329675cc422562ca6e5b1 Mon Sep 17 00:00:00 2001 From: Mario Estrada Date: Mon, 27 Aug 2018 10:53:22 -0600 Subject: [PATCH 08/30] docs(example-soap-calculator): standardize the content of README file and reference it from tutorial --- docs/site/soap-calculator-tutorial.md | 88 ++------------------------- examples/soap-calculator/README.md | 56 ++++++++--------- 2 files changed, 31 insertions(+), 113 deletions(-) diff --git a/docs/site/soap-calculator-tutorial.md b/docs/site/soap-calculator-tutorial.md index 6fbca8136e11..4b343336429d 100644 --- a/docs/site/soap-calculator-tutorial.md +++ b/docs/site/soap-calculator-tutorial.md @@ -2,90 +2,10 @@ lang: en title: 'SOAP calculator Web Service integration tutorial' keywords: LoopBack 4.0, LoopBack 4 +layout: readme +source: loopback-next +file: examples/soap-calculator/README.md sidebar: lb4_sidebar permalink: /doc/en/lb4/soap-calculator-tutorial.html -summary: Integrating a Calculator SOAP web service with LoopBack 4. +summary: LoopBack 4 SOAP Web Service Tutorial --- - -# @loopback/example-soap-calculator - -Integrating a Calculator SOAP web service with LoopBack 4. - -## Overview - -This example project shows how to integrate a SOAP web service with LoopBack 4 -and expose its methods through the REST API server. Acceptance and Integration -tests are provided. - -Before each step, you will be presented an image containing the artifacts that -you will be creating in blue. - -![soap-calculator-overview](./imgs/loopback-example-soap-calculator_figure1.png) - -## Setup - -You'll need to make sure you have some things installed: - -- [Node.js](https://nodejs.org/en/) at v8.9 or greater - -Lastly, you'll need to install the LoopBack 4 CLI toolkit: - -```sh -npm i -g @loopback/cli -``` - -## Start the Tutorial - -Follow the following steps to start buiding your application: - -### Steps - -1. [SOAP Web Service Overview](soap-calculator-tutorial-web-service-overview.md) -2. [Scaffold the Application](soap-calculator-tutorial-scaffolding.md) -3. [Add a data source](soap-calculator-tutorial-add-datasource.md) -4. [Add a Service](soap-calculator-tutorial-add-service.md) -5. [Add a controller](soap-calculator-tutorial-add-controller.md) -6. [Register the service](soap-calculator-tutorial-register-service.md) -7. [Run and Test the application](soap-calculator-tutorial-run-and-test.md) - -## or Try it out - -If you'd like to see the final results of this tutorial as an example -application, follow these steps: - -### Generate the example using CLI - -1.Run the `lb4 example` command to select and clone the soap-calculator -repository: - -```sh -$ lb4 example -? What example would you like to clone? (Use arrow keys) - todo: Tutorial example on how to build an application with LoopBack 4. - todo-list: Continuation of the todo example using relations in LoopBack 4. - 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. -``` - -2.Jump into the directory and then install the required dependencies: - -```sh -cd loopback4-example-soap-calculator -``` - -3.Finally, start the application! - - ```sh - $ npm start - - Server is running on port 3000 - ``` - -Feel free to look around in the application's code to get a feel for how it -works. - -## License - -MIT diff --git a/examples/soap-calculator/README.md b/examples/soap-calculator/README.md index 435d091abd7e..bc6d3ec6d28a 100644 --- a/examples/soap-calculator/README.md +++ b/examples/soap-calculator/README.md @@ -8,6 +8,9 @@ This example project shows how to integrate a SOAP web service with LoopBack 4 and expose its methods through the REST API server. Acceptance and Integration tests are provided. +Before each step, you will be presented an image containing the artifacts that +you will be creating in blue. + ![soap-calculator-overview](https://loopback.io/pages/en/lb4/imgs/loopback-example-soap-calculator_figure1.png) ## Setup @@ -22,10 +25,29 @@ Lastly, you'll need to install the LoopBack 4 CLI toolkit: npm i -g @loopback/cli ``` -## Generate the example using CLI +## Start the Tutorial + +Follow the following steps to start buiding your application: + +### Steps + +1. [SOAP Web Service Overview](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-web-service-overview.html) +2. [Scaffold the Application](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-scaffolding.html) +3. [Add a data source](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-add-datasource.html) +4. [Add a Service](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-add-service.html) +5. [Add a controller](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-add-controller.html) +6. [Register the service](https://loopback.io/doc/en/lb4/soap-calculator-tutorial-make-service-available.md) +7. [Run and Test the application](https://loopback.io/doc/en/lb4/soap-calculator-run-and-and-test.md) + +## or Try it out -1. Run the `lb4 example` command to select and clone the soap-calculator - repository: +If you'd like to see the final results of this tutorial as an example +application, follow these steps: + +### Generate the example using CLI + +1.Run the `lb4 example` command to select and clone the soap-calculator +repository: ```sh $ lb4 example @@ -38,13 +60,13 @@ $ lb4 example > soap-calculator: An example on how to integrate SOAP web services. ``` -2. Jump into the directory and then install the required dependencies: +2.Jump into the directory and then install the required dependencies: ```sh cd loopback4-example-soap-calculator ``` -3. Finally, start the application! +3.Finally, start the application! ```sh $ npm start @@ -55,30 +77,6 @@ cd loopback4-example-soap-calculator Feel free to look around in the application's code to get a feel for how it works. -### Stuck? - -Check out our [Gitter channel](https://gitter.im/strongloop/loopback) and ask -for help with this tutorial! - -### Bugs/Feedback - -Open an issue in [loopback-next](https://github.com/strongloop/loopback-next) -and we'll take a look! - -## 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 From fb01931d4e193c21560811f4d6d078c89941fcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 24 Aug 2018 14:47:11 +0200 Subject: [PATCH 09/30] feat(service-proxy): add service mixin Implement "ServiceMixin" for applications. This mixin enhances component registration so that service providers exported by a component are automatically registered for dependency injection; and adds a new sugar API for registering service providers manually: app.serviceProvider(MyServiceProvicer); The method name "serviceProvider" was chosen deliberately to make it clear that we are binding a Provider, not a class constructor. Compare this to `app.repository(MyRepo)` that accepts a class construct. In the future, we may add `app.service(MyService)` method if there is enough user demand. --- .../Calling-other-APIs-and-Web-Services.md | 21 +- ...ap-calculator-tutorial-register-service.md | 49 ++--- docs/site/todo-tutorial-geocoding-service.md | 11 +- examples/soap-calculator/src/application.ts | 12 +- examples/todo/src/application.ts | 15 +- .../boot/src/booters/datasource.booter.ts | 9 +- .../boot/src/booters/repository.booter.ts | 5 +- .../unit/booters/datasource.booter.unit.ts | 7 +- .../unit/booters/repository.booter.unit.ts | 7 +- packages/cli/generators/app/index.js | 22 ++ .../app/templates/src/application.ts.ejs | 12 +- .../project/templates/package.json.ejs | 9 +- .../integration/generators/app.integration.js | 2 +- .../repository/src/mixins/repository.mixin.ts | 8 +- .../has-many.relation.acceptance.ts | 4 +- packages/service-proxy/docs.json | 1 + packages/service-proxy/src/index.ts | 1 + packages/service-proxy/src/mixins/index.ts | 6 + .../service-proxy/src/mixins/service.mixin.ts | 198 ++++++++++++++++++ .../test/unit/mixin/service.mixin.unit.ts | 97 +++++++++ 20 files changed, 407 insertions(+), 89 deletions(-) create mode 100644 packages/service-proxy/src/mixins/index.ts create mode 100644 packages/service-proxy/src/mixins/service.mixin.ts create mode 100644 packages/service-proxy/test/unit/mixin/service.mixin.unit.ts diff --git a/docs/site/Calling-other-APIs-and-Web-Services.md b/docs/site/Calling-other-APIs-and-Web-Services.md index 7787d84b36b9..e8da9d5e84dc 100644 --- a/docs/site/Calling-other-APIs-and-Web-Services.md +++ b/docs/site/Calling-other-APIs-and-Web-Services.md @@ -60,19 +60,6 @@ Install the REST connector used by the new datasource: $ npm install --save loopback-connector-rest ``` -### Bind data sources to the context - -```ts -import {Context} from '@loopback/context'; - -const context = new Context(); -context.bind('dataSources.geoService').to(ds); -``` - -**NOTE**: Once we start to support declarative datasources with -`@loopback/boot`, the datasource configuration files can be dropped into -`src/datasources` to be discovered and bound automatically. - ### Declare the service interface To promote type safety, we recommend you to declare data types and service @@ -162,10 +149,12 @@ export class GeoServiceProvider implements Provider { } ``` -In your application setup, create an explicit binding for the geo service proxy: +In your application, apply +[ServiceMixin](http://apidocs.loopback.io/@loopback%2fdocs/service-proxy.html#ServiceMixin) +and use `app.serviceProvider` API to create binding for the geo service proxy. ```ts -app.bind('services.geo').toProvider(GeoServiceProvider); +app.serviceProvider(GeoServiceProvider); ``` Finally, modify the controller to receive our new service proxy in the @@ -173,7 +162,7 @@ constructor: ```ts export class MyController { - @inject('services.geo') + @inject('services.GeoService') private geoService: GeoService; } ``` diff --git a/docs/site/soap-calculator-tutorial-register-service.md b/docs/site/soap-calculator-tutorial-register-service.md index 2fb97a8e2d20..c41944becd12 100644 --- a/docs/site/soap-calculator-tutorial-register-service.md +++ b/docs/site/soap-calculator-tutorial-register-service.md @@ -15,33 +15,33 @@ Injection)_. #### Importing the service and helper classes -Add the following import statement after all the previous imports. +Add the following import statements after all the previous imports. ```ts +import {ServiceMixin} from '@loopback/service-proxy'; import {CalculatorServiceProvider} from './services/calculator.service'; ``` -Now change the following line to include a Constructor and Provider class from -_LB4_ core. +#### Applying `ServiceMixin` on our Application class -```ts -import {ApplicationConfig} from '@loopback/core'; -``` - -change it to +Modify the inheritance chain of our Application class as follows: ```ts -import {ApplicationConfig, Constructor, Provider} from '@loopback/core'; +export class SoapCalculatorApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + // (no changes in application constructor or methods) +} ``` #### Registering the Service and bind it to a key -Let's continue by adding the following generic method that we will use in order -to register our service and any other service that we might work in the future. - -Notice that it removes the Provider key from the name of the service, so for our -service name CalculatorServiceProvider, its key will become -**services.CalculatorService** which matches the +Let's continue by creating a method to register services used by our +application. Notice that we are using `this.serviceProvider` method contributed +by `ServiceMixin`, this method removes the suffix `Provider` from the class name +and uses the remaining string as the binding key. For our service provider +called `CalculatorServiceProvider`, the binding key becomes +**services.CalculatorService** and matches the `@inject('services.CalculatorService')` decorator parameter we used in our controller. @@ -49,18 +49,9 @@ controller. registration for services in the same way we do now for other artifacts in **LB4**. -```ts -service(provider: Constructor>) { - const key = `services.${provider.name.replace(/Provider$/, '')}`; - this.bind(key).toProvider(provider); - } -``` - -Now let's add a method that will make use of this generic `service` method. - ```ts setupServices() { - this.service(CalculatorServiceProvider); + this.serviceProvider(CalculatorServiceProvider); } ``` @@ -72,10 +63,10 @@ constructor after the `this.sequence(MySequence);` statement. this.setupServices(); ``` -**Note:** We could have achieved the above result by just one line inside the -setupServices() method, replacing the generic method. However, the generic one -is more efficient when you need to register multiple services, to keep the -_keys_ standard. +**Note:** We could have achieved the above result by calling the following line +inside the setupServices() method, replacing the method provided by the mixin. +However, the mixin-provided method is more efficient when you need to register +multiple services, to keep the _keys_ standard. ```ts this.bind('services.CalculatorService').toProvider(CalculatorServiceProvider); diff --git a/docs/site/todo-tutorial-geocoding-service.md b/docs/site/todo-tutorial-geocoding-service.md index 81633f68a741..3da83c76488e 100644 --- a/docs/site/todo-tutorial-geocoding-service.md +++ b/docs/site/todo-tutorial-geocoding-service.md @@ -149,8 +149,10 @@ to add few code snippets to our Application class to take care of this task. #### src/application.ts ```ts +import {ServiceMixin} from '@loopback/service-proxy'; + export class TodoListApplication extends BootMixin( - RepositoryMixin(RestApplication), + ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); @@ -162,12 +164,7 @@ export class TodoListApplication extends BootMixin( // ADD THE FOLLOWING TWO METHODS setupServices() { - this.service(GeocoderServiceProvider); - } - - service(provider: Constructor>) { - const key = `services.${provider.name.replace(/Provider$/, '')}`; - this.bind(key).toProvider(provider); + this.serviceProvider(GeocoderServiceProvider); } } ``` diff --git a/examples/soap-calculator/src/application.ts b/examples/soap-calculator/src/application.ts index f5a345f13ad0..f83914987231 100644 --- a/examples/soap-calculator/src/application.ts +++ b/examples/soap-calculator/src/application.ts @@ -1,12 +1,13 @@ import {BootMixin} from '@loopback/boot'; -import {ApplicationConfig, Constructor, Provider} from '@loopback/core'; +import {ApplicationConfig} from '@loopback/core'; import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; +import {ServiceMixin} from '@loopback/service-proxy'; import {MySequence} from './sequence'; import {CalculatorServiceProvider} from './services/calculator.service'; export class SoapCalculatorApplication extends BootMixin( - RepositoryMixin(RestApplication), + ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); @@ -30,11 +31,6 @@ export class SoapCalculatorApplication extends BootMixin( } setupServices() { - this.service(CalculatorServiceProvider); - } - - service(provider: Constructor>) { - const key = `services.${provider.name.replace(/Provider$/, '')}`; - this.bind(key).toProvider(provider); + this.serviceProvider(CalculatorServiceProvider); } } diff --git a/examples/todo/src/application.ts b/examples/todo/src/application.ts index 8c5db992351f..8a5b428c5e99 100644 --- a/examples/todo/src/application.ts +++ b/examples/todo/src/application.ts @@ -4,14 +4,15 @@ // License text available at https://opensource.org/licenses/MIT import {BootMixin} from '@loopback/boot'; -import {ApplicationConfig, Constructor, Provider} from '@loopback/core'; +import {ApplicationConfig} from '@loopback/core'; import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; +import {ServiceMixin} from '@loopback/service-proxy'; import {MySequence} from './sequence'; import {GeocoderServiceProvider} from './services'; export class TodoListApplication extends BootMixin( - RepositoryMixin(RestApplication), + ServiceMixin(RepositoryMixin(RestApplication)), ) { constructor(options?: ApplicationConfig) { super(options); @@ -36,14 +37,6 @@ export class TodoListApplication extends BootMixin( } setupServices() { - this.service(GeocoderServiceProvider); - } - - // TODO(bajtos) app.service should be provided either by core Application - // class or a mixin provided by @loopback/service-proxy - // See https://github.com/strongloop/loopback-next/issues/1439 - service(provider: Constructor>) { - const key = `services.${provider.name.replace(/Provider$/, '')}`; - this.bind(key).toProvider(provider); + this.serviceProvider(GeocoderServiceProvider); } } diff --git a/packages/boot/src/booters/datasource.booter.ts b/packages/boot/src/booters/datasource.booter.ts index 556f64ca2c4d..665a626164a1 100644 --- a/packages/boot/src/booters/datasource.booter.ts +++ b/packages/boot/src/booters/datasource.booter.ts @@ -4,7 +4,11 @@ // License text available at https://opensource.org/licenses/MIT import {CoreBindings} from '@loopback/core'; -import {AppWithRepository, juggler, Class} from '@loopback/repository'; +import { + ApplicationWithRepositories, + juggler, + Class, +} from '@loopback/repository'; import {inject} from '@loopback/context'; import {ArtifactOptions} from '../interfaces'; import {BaseArtifactBooter} from './base-artifact.booter'; @@ -22,7 +26,8 @@ import {BootBindings} from '../keys'; */ export class DataSourceBooter extends BaseArtifactBooter { constructor( - @inject(CoreBindings.APPLICATION_INSTANCE) public app: AppWithRepository, + @inject(CoreBindings.APPLICATION_INSTANCE) + public app: ApplicationWithRepositories, @inject(BootBindings.PROJECT_ROOT) public projectRoot: string, @inject(`${BootBindings.BOOT_OPTIONS}#datasources`) public datasourceConfig: ArtifactOptions = {}, diff --git a/packages/boot/src/booters/repository.booter.ts b/packages/boot/src/booters/repository.booter.ts index 1ee22da9f261..a8c2ce841fbf 100644 --- a/packages/boot/src/booters/repository.booter.ts +++ b/packages/boot/src/booters/repository.booter.ts @@ -5,7 +5,7 @@ import {CoreBindings} from '@loopback/core'; import {inject} from '@loopback/context'; -import {AppWithRepository} from '@loopback/repository'; +import {ApplicationWithRepositories} from '@loopback/repository'; import {BaseArtifactBooter} from './base-artifact.booter'; import {BootBindings} from '../keys'; import {ArtifactOptions} from '../interfaces'; @@ -23,7 +23,8 @@ import {ArtifactOptions} from '../interfaces'; */ export class RepositoryBooter extends BaseArtifactBooter { constructor( - @inject(CoreBindings.APPLICATION_INSTANCE) public app: AppWithRepository, + @inject(CoreBindings.APPLICATION_INSTANCE) + public app: ApplicationWithRepositories, @inject(BootBindings.PROJECT_ROOT) public projectRoot: string, @inject(`${BootBindings.BOOT_OPTIONS}#repositories`) public repositoryOptions: ArtifactOptions = {}, diff --git a/packages/boot/test/unit/booters/datasource.booter.unit.ts b/packages/boot/test/unit/booters/datasource.booter.unit.ts index 4f0048dbfde9..c82d99df9468 100644 --- a/packages/boot/test/unit/booters/datasource.booter.unit.ts +++ b/packages/boot/test/unit/booters/datasource.booter.unit.ts @@ -5,7 +5,10 @@ import {expect, TestSandbox, sinon} from '@loopback/testlab'; import {resolve} from 'path'; -import {AppWithRepository, RepositoryMixin} from '@loopback/repository'; +import { + ApplicationWithRepositories, + RepositoryMixin, +} from '@loopback/repository'; import {DataSourceBooter, DataSourceDefaults} from '../../../src'; import {Application} from '@loopback/core'; @@ -33,7 +36,7 @@ describe('datasource booter unit tests', () => { ); const booterInst = new DataSourceBooter( - normalApp as AppWithRepository, + normalApp as ApplicationWithRepositories, SANDBOX_PATH, ); diff --git a/packages/boot/test/unit/booters/repository.booter.unit.ts b/packages/boot/test/unit/booters/repository.booter.unit.ts index 280edef8616d..c63f93c186f5 100644 --- a/packages/boot/test/unit/booters/repository.booter.unit.ts +++ b/packages/boot/test/unit/booters/repository.booter.unit.ts @@ -5,7 +5,10 @@ import {expect, TestSandbox, sinon} from '@loopback/testlab'; import {Application} from '@loopback/core'; -import {RepositoryMixin, AppWithRepository} from '@loopback/repository'; +import { + RepositoryMixin, + ApplicationWithRepositories, +} from '@loopback/repository'; import {RepositoryBooter, RepositoryDefaults} from '../../../index'; import {resolve} from 'path'; @@ -33,7 +36,7 @@ describe('repository booter unit tests', () => { ); const booterInst = new RepositoryBooter( - normalApp as AppWithRepository, + normalApp as ApplicationWithRepositories, SANDBOX_PATH, ); diff --git a/packages/cli/generators/app/index.js b/packages/cli/generators/app/index.js index 0e676740d828..81e0bac3e683 100644 --- a/packages/cli/generators/app/index.js +++ b/packages/cli/generators/app/index.js @@ -12,6 +12,7 @@ module.exports = class AppGenerator extends ProjectGenerator { constructor(args, opts) { super(args, opts); this.buildOptions.push('enableRepository'); + this.buildOptions.push('enableServices'); } _setupGenerator() { @@ -27,6 +28,11 @@ module.exports = class AppGenerator extends ProjectGenerator { description: 'Include repository imports and RepositoryMixin', }); + this.option('enableServices', { + type: Boolean, + description: 'Include service-proxy imports and ServiceMixin', + }); + return super._setupGenerator(); } @@ -80,6 +86,22 @@ module.exports = class AppGenerator extends ProjectGenerator { return super.promptOptions(); } + buildAppClassMixins() { + if (this.shouldExit()) return false; + const {enableRepository, enableServices} = this.projectInfo || {}; + if (!enableRepository && !enableServices) return; + + let appClassWithMixins = 'RestApplication'; + if (enableRepository) { + appClassWithMixins = `RepositoryMixin(${appClassWithMixins})`; + } + if (enableServices) { + appClassWithMixins = `ServiceMixin(${appClassWithMixins})`; + } + + this.projectInfo.appClassWithMixins = appClassWithMixins; + } + scaffold() { return super.scaffold(); } diff --git a/packages/cli/generators/app/templates/src/application.ts.ejs b/packages/cli/generators/app/templates/src/application.ts.ejs index 6fd34c1a2eba..4f48132b77fe 100644 --- a/packages/cli/generators/app/templates/src/application.ts.ejs +++ b/packages/cli/generators/app/templates/src/application.ts.ejs @@ -4,11 +4,19 @@ import {ApplicationConfig} from '@loopback/core'; import {RepositoryMixin} from '@loopback/repository'; <% } -%> import {RestApplication} from '@loopback/rest'; +<% if (project.enableServices) { -%> +import {ServiceMixin} from '@loopback/service-proxy'; +<% } -%> import {MySequence} from './sequence'; -export class <%= project.applicationName %> <% if (!project.enableRepository) {-%>extends BootMixin(RestApplication) {<% } else { -%>extends BootMixin( - RepositoryMixin(RestApplication), +<% if (project.appClassWithMixins) { -%> +export class <%= project.applicationName %> extends BootMixin( + <%= project.appClassWithMixins %>, ) { +<% +} else { // no optional mixins +-%> +export class <%= project.applicationName %> extends BootMixin(RestApplication) { <% } -%> constructor(options?: ApplicationConfig) { super(options); diff --git a/packages/cli/generators/project/templates/package.json.ejs b/packages/cli/generators/project/templates/package.json.ejs index 66a610c81d2f..bd13fcf193ea 100644 --- a/packages/cli/generators/project/templates/package.json.ejs +++ b/packages/cli/generators/project/templates/package.json.ejs @@ -78,9 +78,16 @@ "@loopback/core": "<%= project.dependencies['@loopback/core'] -%>", "@loopback/dist-util": "<%= project.dependencies['@loopback/dist-util'] -%>", "@loopback/openapi-v3": "<%= project.dependencies['@loopback/openapi-v3'] -%>", +<% if (project.enableRepository) { -%> "@loopback/repository": "<%= project.dependencies['@loopback/repository'] -%>", - "@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>" +<% } -%> +<% if (project.enableServices) { -%> + "@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>", + "@loopback/service-proxy": "<%= project.dependencies['@loopback/service-proxy'] -%>" <% } else { -%> + "@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>" +<% } -%> +<% } else { /* NOT AN APPLICATION */-%> "@loopback/core": "<%= project.dependencies['@loopback/core'] -%>", "@loopback/dist-util": "<%= project.dependencies['@loopback/dist-util'] -%>" <% } -%> diff --git a/packages/cli/test/integration/generators/app.integration.js b/packages/cli/test/integration/generators/app.integration.js index 629693aeae29..82442bab4980 100644 --- a/packages/cli/test/integration/generators/app.integration.js +++ b/packages/cli/test/integration/generators/app.integration.js @@ -35,7 +35,7 @@ describe('app-generator specific files', () => { ); assert.fileContent( 'src/application.ts', - /RepositoryMixin\(RestApplication\)/, + /ServiceMixin\(RepositoryMixin\(RestApplication\)\)/, ); assert.fileContent('src/application.ts', /constructor\(/); assert.fileContent('src/application.ts', /this.projectRoot = __dirname/); diff --git a/packages/repository/src/mixins/repository.mixin.ts b/packages/repository/src/mixins/repository.mixin.ts index 70e853bdcb45..8ed4f2957424 100644 --- a/packages/repository/src/mixins/repository.mixin.ts +++ b/packages/repository/src/mixins/repository.mixin.ts @@ -143,7 +143,7 @@ export function RepositoryMixin>(superClass: T) { */ public component(component: Class<{}>) { super.component(component); - this.mountComponentRepository(component); + this.mountComponentRepositories(component); } /** @@ -153,7 +153,7 @@ export function RepositoryMixin>(superClass: T) { * * @param component The component to mount repositories of */ - mountComponentRepository(component: Class<{}>) { + mountComponentRepositories(component: Class<{}>) { const componentKey = `components.${component.name}`; const compInstance = this.getSync(componentKey); @@ -169,7 +169,7 @@ export function RepositoryMixin>(superClass: T) { /** * Interface for an Application mixed in with RepositoryMixin */ -export interface AppWithRepository extends Application { +export interface ApplicationWithRepositories extends Application { // tslint:disable-next-line:no-any repository(repo: Class): void; // tslint:disable-next-line:no-any @@ -179,7 +179,7 @@ export interface AppWithRepository extends Application { name?: string, ): void; component(component: Class<{}>): void; - mountComponentRepository(component: Class<{}>): void; + mountComponentRepositories(component: Class<{}>): void; } /** diff --git a/packages/repository/test/acceptance/has-many.relation.acceptance.ts b/packages/repository/test/acceptance/has-many.relation.acceptance.ts index 5da5cd482767..f834914830d2 100644 --- a/packages/repository/test/acceptance/has-many.relation.acceptance.ts +++ b/packages/repository/test/acceptance/has-many.relation.acceptance.ts @@ -12,7 +12,7 @@ import { hasMany, repository, RepositoryMixin, - AppWithRepository, + ApplicationWithRepositories, HasManyRepositoryFactory, } from '../..'; import {expect} from '@loopback/testlab'; @@ -23,7 +23,7 @@ import {Application} from '@loopback/core'; describe('HasMany relation', () => { // Given a Customer and Order models - see definitions at the bottom - let app: AppWithRepository; + let app: ApplicationWithRepositories; let controller: CustomerController; let customerRepo: CustomerRepository; let orderRepo: OrderRepository; diff --git a/packages/service-proxy/docs.json b/packages/service-proxy/docs.json index 699495ede9e4..05af59883d4e 100644 --- a/packages/service-proxy/docs.json +++ b/packages/service-proxy/docs.json @@ -3,6 +3,7 @@ "./index.ts", "./src/index.ts", "./src/decorators/service.decorator.ts", + "./src/mixins/service.mixin.ts", "./src/legacy-juggler-bridge.ts" ], "codeSectionDepth": 4 diff --git a/packages/service-proxy/src/index.ts b/packages/service-proxy/src/index.ts index 7a9bd70f7eee..2b4349da11a3 100644 --- a/packages/service-proxy/src/index.ts +++ b/packages/service-proxy/src/index.ts @@ -5,3 +5,4 @@ export * from './legacy-juggler-bridge'; export * from './decorators/service.decorator'; +export * from './mixins'; diff --git a/packages/service-proxy/src/mixins/index.ts b/packages/service-proxy/src/mixins/index.ts new file mode 100644 index 000000000000..1a4ff954e2f9 --- /dev/null +++ b/packages/service-proxy/src/mixins/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/service-proxy +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './service.mixin'; diff --git a/packages/service-proxy/src/mixins/service.mixin.ts b/packages/service-proxy/src/mixins/service.mixin.ts new file mode 100644 index 000000000000..3fcb754a1a47 --- /dev/null +++ b/packages/service-proxy/src/mixins/service.mixin.ts @@ -0,0 +1,198 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/service-proxy +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Provider} from '@loopback/context'; +import {Application} from '@loopback/core'; + +/** + * Interface for classes with `new` operator. + */ +export interface Class { + // new MyClass(...args) ==> T + // tslint:disable-next-line:no-any + new (...args: any[]): T; +} + +/** + * A mixin class for Application that creates a .serviceProvider() + * function to register a service automatically. Also overrides + * component function to allow it to register repositories automatically. + * + * ```ts + * class MyApplication extends ServiceMixin(Application) {} + * ``` + * + * Please note: the members in the mixin function are documented in a dummy class + * called ServiceMixinDoc + * + */ +// tslint:disable-next-line:no-any +export function ServiceMixin>(superClass: T) { + return class extends superClass { + // A mixin class has to take in a type any[] argument! + // tslint:disable-next-line:no-any + constructor(...args: any[]) { + super(...args); + } + + /** + * Add a service to this application. + * + * @param provider The service provider to register. + * + * ```ts + * export interface GeocoderService { + * geocode(address: string): Promise; + * } + * + * export class GeocoderServiceProvider implements Provider { + * constructor( + * @inject('datasources.geocoder') + * protected datasource: juggler.DataSource = new GeocoderDataSource(), + * ) {} + * + * value(): Promise { + * return getService(this.datasource); + * } + * } + * + * app.serviceProvider(GeocoderServiceProvider); + * ``` + */ + serviceProvider(provider: Class>): void { + const serviceName = provider.name.replace(/Provider$/, ''); + const repoKey = `services.${serviceName}`; + this.bind(repoKey) + .toProvider(provider) + .tag('service'); + } + + /** + * Add a component to this application. Also mounts + * all the components services. + * + * @param component The component to add. + * + * ```ts + * + * export class ProductComponent { + * controllers = [ProductController]; + * repositories = [ProductRepo, UserRepo]; + * providers = { + * [AUTHENTICATION_STRATEGY]: AuthStrategy, + * [AUTHORIZATION_ROLE]: Role, + * }; + * }; + * + * app.component(ProductComponent); + * ``` + */ + public component(component: Class<{}>) { + super.component(component); + this.mountComponentServices(component); + } + + /** + * Get an instance of a component and mount all it's + * services. This function is intended to be used internally + * by component() + * + * @param component The component to mount services of + */ + mountComponentServices(component: Class<{}>) { + const componentKey = `components.${component.name}`; + const compInstance = this.getSync(componentKey); + + if (compInstance.serviceProviders) { + for (const provider of compInstance.serviceProviders) { + this.serviceProvider(provider); + } + } + } + }; +} + +/** + * Interface for an Application mixed in with ServiceMixin + */ +export interface ApplicationWithServices extends Application { + // tslint:disable-next-line:no-any + serviceProvider(provider: Class>): void; + component(component: Class<{}>): void; + mountComponentServices(component: Class<{}>): void; +} + +/** + * A dummy class created to generate the tsdoc for the members in service + * mixin. Please don't use it. + * + * The members are implemented in function + * ServiceMixin + */ +export class ServiceMixinDoc { + // tslint:disable-next-line:no-any + constructor(...args: any[]) { + throw new Error( + 'This is a dummy class created for apidoc! Please do not use it!', + ); + } + + /** + * Add a service to this application. + * + * @param provider The service provider to register. + * + * ```ts + * export interface GeocoderService { + * geocode(address: string): Promise; + * } + * + * export class GeocoderServiceProvider implements Provider { + * constructor( + * @inject('datasources.geocoder') + * protected datasource: juggler.DataSource = new GeocoderDataSource(), + * ) {} + * + * value(): Promise { + * return getService(this.datasource); + * } + * } + * + * app.serviceProvider(GeocoderServiceProvider); + * ``` + */ + serviceProvider(provider: Class>): void {} + + /** + * Add a component to this application. Also mounts + * all the components services. + * + * @param component The component to add. + * + * ```ts + * + * export class ProductComponent { + * controllers = [ProductController]; + * repositories = [ProductRepo, UserRepo]; + * providers = { + * [AUTHENTICATION_STRATEGY]: AuthStrategy, + * [AUTHORIZATION_ROLE]: Role, + * }; + * }; + * + * app.component(ProductComponent); + * ``` + */ + public component(component: Class<{}>) {} + + /** + * Get an instance of a component and mount all it's + * services. This function is intended to be used internally + * by component() + * + * @param component The component to mount services of + */ + mountComponentServices(component: Class<{}>) {} +} diff --git a/packages/service-proxy/test/unit/mixin/service.mixin.unit.ts b/packages/service-proxy/test/unit/mixin/service.mixin.unit.ts new file mode 100644 index 000000000000..c581f53548b3 --- /dev/null +++ b/packages/service-proxy/test/unit/mixin/service.mixin.unit.ts @@ -0,0 +1,97 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/service-proxy +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application, Component, Provider} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import {Class, ServiceMixin} from '../../../'; + +// tslint:disable:no-any + +describe('ServiceMixin', () => { + it('mixed class has .serviceProvider()', () => { + const myApp = new AppWithServiceMixin(); + expect(typeof myApp.serviceProvider).to.be.eql('function'); + }); + + it('binds repository from app.serviceProvider()', async () => { + const myApp = new AppWithServiceMixin(); + + expectGeocoderToNotBeBound(myApp); + myApp.serviceProvider(GeocoderServiceProvider); + await expectGeocoderToBeBound(myApp); + }); + + it('binds a component without services', () => { + class EmptyTestComponent {} + + const myApp = new AppWithServiceMixin(); + myApp.component(EmptyTestComponent); + + expectComponentToBeBound(myApp, EmptyTestComponent); + }); + + it('binds a component with a service provider from .component()', async () => { + const myApp = new AppWithServiceMixin(); + + const boundComponentsBefore = myApp.find('components.*').map(b => b.key); + expect(boundComponentsBefore).to.be.empty(); + expectGeocoderToNotBeBound(myApp); + + myApp.component(GeocoderComponent); + + expectComponentToBeBound(myApp, GeocoderComponent); + await expectGeocoderToBeBound(myApp); + }); + + class AppWithServiceMixin extends ServiceMixin(Application) {} + + interface GeoPoint { + lat: number; + lng: number; + } + + interface GeocoderService { + geocode(address: string): Promise; + } + + // A dummy service instance to make unit testing easier + const GeocoderSingleton: GeocoderService = { + geocode(address: string) { + return Promise.resolve({lat: 0, lng: 0}); + }, + }; + + class GeocoderServiceProvider implements Provider { + value(): Promise { + return Promise.resolve(GeocoderSingleton); + } + } + + class GeocoderComponent { + serviceProviders = [GeocoderServiceProvider]; + } + + async function expectGeocoderToBeBound(myApp: Application) { + const boundRepositories = myApp.find('services.*').map(b => b.key); + expect(boundRepositories).to.containEql('services.GeocoderService'); + const repoInstance = await myApp.get('services.GeocoderService'); + expect(repoInstance).to.equal(GeocoderSingleton); + } + + function expectGeocoderToNotBeBound(myApp: Application) { + const boundRepos = myApp.find('services.*').map(b => b.key); + expect(boundRepos).to.be.empty(); + } + + function expectComponentToBeBound( + myApp: Application, + component: Class, + ) { + const boundComponents = myApp.find('components.*').map(b => b.key); + expect(boundComponents).to.containEql(`components.${component.name}`); + const componentInstance = myApp.getSync(`components.${component.name}`); + expect(componentInstance).to.be.instanceOf(component); + } +}); From 0364b59409ce3691babf5f17b5cd444b4e7f72aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 27 Aug 2018 09:57:19 +0200 Subject: [PATCH 10/30] build: fix tslint issues related to promises --- packages/build/config/tslint.build.json | 4 ++-- packages/context/test/unit/value-promise.unit.ts | 3 +++ packages/rest/test/acceptance/coercion/coercion.acceptance.ts | 2 +- .../rest/test/acceptance/validation/validation.acceptance.ts | 2 +- packages/rest/test/integration/rest.server.integration.ts | 4 ++-- packages/rest/test/unit/rest.server/rest.server.unit.ts | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/build/config/tslint.build.json b/packages/build/config/tslint.build.json index 00b53823746a..819a34736d9c 100644 --- a/packages/build/config/tslint.build.json +++ b/packages/build/config/tslint.build.json @@ -15,8 +15,8 @@ // User-land promises like Bluebird implement "PromiseLike" (not "Promise") // interface only. The string "PromiseLike" bellow is needed to // tell tslint that it's ok to `await` such promises. - "await-promise": [true, "PromiseLike"], - "no-floating-promises": true, + "await-promise": [true, "PromiseLike", "RequestPromise"], + "no-floating-promises": [true, "PromiseLike", "RequestPromise"], "no-unused-variable": true, "no-void-expression": [true, "ignore-arrow-function-shorthand"] } diff --git a/packages/context/test/unit/value-promise.unit.ts b/packages/context/test/unit/value-promise.unit.ts index f2e7e7180b02..480e74d62868 100644 --- a/packages/context/test/unit/value-promise.unit.ts +++ b/packages/context/test/unit/value-promise.unit.ts @@ -251,6 +251,7 @@ describe('resolveUntil', () => { v => Promise.reject(new Error(v)), (s, v) => true, ); + // tslint:disable-next-line:no-floating-promises expect(result).be.rejectedWith('a'); }); @@ -276,6 +277,7 @@ describe('resolveUntil', () => { throw new Error(v); }, ); + // tslint:disable-next-line:no-floating-promises expect(result).be.rejectedWith('A'); }); @@ -343,6 +345,7 @@ describe('transformValueOrPromise', () => { const result = transformValueOrPromise('a', v => Promise.reject(new Error(v)), ); + // tslint:disable-next-line:no-floating-promises expect(result).be.rejectedWith('a'); }); diff --git a/packages/rest/test/acceptance/coercion/coercion.acceptance.ts b/packages/rest/test/acceptance/coercion/coercion.acceptance.ts index 2478ece9bf7e..8631ec1d91eb 100644 --- a/packages/rest/test/acceptance/coercion/coercion.acceptance.ts +++ b/packages/rest/test/acceptance/coercion/coercion.acceptance.ts @@ -53,6 +53,6 @@ describe('Coercion', () => { app = new RestApplication(); app.controller(MyController); await app.start(); - client = await createClientForHandler(app.requestHandler); + client = createClientForHandler(app.requestHandler); } }); diff --git a/packages/rest/test/acceptance/validation/validation.acceptance.ts b/packages/rest/test/acceptance/validation/validation.acceptance.ts index 85eebc282122..e596d6bd21b1 100644 --- a/packages/rest/test/acceptance/validation/validation.acceptance.ts +++ b/packages/rest/test/acceptance/validation/validation.acceptance.ts @@ -156,6 +156,6 @@ describe('Validation at REST level', () => { app.controller(controller); await app.start(); - client = await createClientForHandler(app.requestHandler); + client = createClientForHandler(app.requestHandler); } }); diff --git a/packages/rest/test/integration/rest.server.integration.ts b/packages/rest/test/integration/rest.server.integration.ts index eeaeddb65e77..4b78e22d9f22 100644 --- a/packages/rest/test/integration/rest.server.integration.ts +++ b/packages/rest/test/integration/rest.server.integration.ts @@ -45,7 +45,7 @@ describe('RestServer (integration)', () => { it('honors port binding after instantiation', async () => { const server = await givenAServer({rest: {port: 80}}); - await server.bind(RestBindings.PORT).to(0); + server.bind(RestBindings.PORT).to(0); await server.start(); expect(server.getSync(RestBindings.PORT)).to.not.equal(80); await server.stop(); @@ -358,7 +358,7 @@ servers: let serverUrl = server.getSync(RestBindings.URL); await expect(httpsGetAsync(serverUrl)).to.be.rejectedWith(/EPROTO/); await server.stop(); - await server.bind(RestBindings.HTTPS_OPTIONS).to({ + server.bind(RestBindings.HTTPS_OPTIONS).to({ key: fs.readFileSync(keyPath), cert: fs.readFileSync(certPath), }); diff --git a/packages/rest/test/unit/rest.server/rest.server.unit.ts b/packages/rest/test/unit/rest.server/rest.server.unit.ts index 53ebcd1008ba..ba7e6c6e7798 100644 --- a/packages/rest/test/unit/rest.server/rest.server.unit.ts +++ b/packages/rest/test/unit/rest.server/rest.server.unit.ts @@ -64,7 +64,7 @@ describe('RestServer', () => { const app = new Application(); app.component(RestComponent); const server = await app.getServer(RestServer); - const host = await server.getSync(RestBindings.HOST); + const host = await server.get(RestBindings.HOST); expect(host).to.be.undefined(); }); From 178a94e83c3fa07d84a553049d3f4b4bf79c24e7 Mon Sep 17 00:00:00 2001 From: bschrammIBM Date: Wed, 29 Aug 2018 17:20:03 -0700 Subject: [PATCH 11/30] docs: edits to decorators topic --- docs/site/Decorators.md | 82 +++++++++++----------- docs/site/todo-list-tutorial-model.md | 31 ++++---- docs/site/todo-list-tutorial-repository.md | 16 ++--- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/docs/site/Decorators.md b/docs/site/Decorators.md index 93f362886182..cb1fed67a6c5 100644 --- a/docs/site/Decorators.md +++ b/docs/site/Decorators.md @@ -90,10 +90,10 @@ Syntax: [`@operation(verb: string, path: string, spec?: OperationObject)`](http://apidocs.loopback.io/@loopback%2fdocs/openapi-v3.html#operation) `@operation` is a controller method decorator. It exposes a Controller method as -a REST API operation, and is represented in OpenAPI spec as an +a REST API operation and is represented in the OpenAPI spec as an [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operation-object). -You can specify the verb, path, parameters and response as specification of your -endpoint, for example: +You can specify the verb, path, parameters, and response as a specification of +your endpoint, for example: ```ts const spec = { @@ -150,7 +150,7 @@ class MyController { Syntax: see [API documentation](https://github.com/strongloop/loopback-next/tree/master/packages/openapi-v3/src/decorators/parameter.decorator.ts#L17-L29) -`@param` is applied to controller method parameters to generate OpenAPI +`@param` is applied to controller method parameters to generate an OpenAPI parameter specification for them. For example: @@ -185,7 +185,7 @@ Writing the whole parameter specification is tedious, so we've created shortcuts to define the params with the pattern `@param.${in}.${type}(${name})`: - in: The parameter location. It can be one of the following values: `query`, - `header`, `path`. + `header`, or `path`. - type: A [common name of OpenAPI primitive data type](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types). - name: Name of the parameter. It should be a `string`. @@ -210,7 +210,7 @@ You can find specific use cases in [Writing Controller methods](Controllers.md#writing-controller-methods) _The parameter location cookie is not supported yet, see_ -_https://github.com/strongloop/loopback-next/issues/997_ +_(https://github.com/strongloop/loopback-next/issues/997)_ ### RequestBody Decorator @@ -237,8 +237,8 @@ requestBodySpec: { } ``` -In order to use `@requestBody`, the parameter type it's decorating needs to have -its model decorated with `@model` and `@property`: +In order to use `@requestBody` in a parameter type, the model in the parameter +type must be decorated with `@model` and `@property`: ```ts import {model, property} from '@loopback/repository'; @@ -255,11 +255,11 @@ class User { } ``` -_To learn more about decorating models and the corresponding OpenAPI schema, -please check [model decorators](#model-decorators)._ +_To learn more about decorating models and the corresponding OpenAPI schema, see +[model decorators](#model-decorators)._ -This allows type information of the model to be visible to the spec generator so -that `@requestBody` can be used on the parameter: +The model decorators allow type information of the model to be visible to the +spec generator so that `@requestBody` can be used on the parameter: ```ts // in file '/src/controllers/user.controller.ts' @@ -279,9 +279,9 @@ For the simplest use case, you can leave the input of `@requestBody` empty since we automatically detect the type of `user` and generate the corresponding schema for it. The default content type is set to be `application/json`. -You can also customize the generated `requestBody` specification in 3 ways: +You can also customize the generated `requestBody` specification in three ways: -- add optional fields `description` and `required` +- Add the optional fields `description` and `required` ```ts class MyController { @@ -297,7 +297,7 @@ class MyController { } ``` -- override the content type or define multiple content types +- Override the content type or define multiple content types ```ts class MyController { @@ -316,7 +316,7 @@ class MyController { } ``` -- override the schema specification +- Override the schema specification ```ts import {UserSchema, User} from '../model/user.schema'; @@ -335,8 +335,9 @@ class MyController { } ``` -_We are supporting more `@requestBody` shortcuts in the future, track the -feature in story_ _https://github.com/strongloop/loopback-next/issues/1064_ +_We plan to support more `@requestBody` shortcuts in the future. You can track +the feature in story_ +_(https://github.com/strongloop/loopback-next/issues/1064)_ ## Dependency Injection @@ -348,9 +349,9 @@ used on non-static properties or constructor parameters of a Class. The `@inject` decorator allows you to inject dependencies bound to any implementation of the [Context](Context.md) object, such as an Application -instance or a request context instance. You can bind values, class definitions -and provider functions to those contexts and then resolve values (or the results -of functions that return those values!) in other areas of your code. +instance or a request context instance. You can bind values, class definitions, +and provider functions to those contexts and then resolve the values (or the +results of functions that return those values!) in other areas of your code. ```ts // src/application.ts @@ -375,7 +376,7 @@ export class MyApp extends RestApplication { ``` Now that we've bound the 'config.widget' key to our configuration object, and -'logger.widget' key to the function `logInfo()`, we can inject them in our +the 'logger.widget' key to the function `logInfo()`, we can inject them in our WidgetController: ```ts @@ -422,7 +423,7 @@ export class HelloController { } ``` -- `@inject.setter`: inject a setter function to set bound value of the key +- `@inject.setter`: inject a setter function to set the bound value of the key Syntax: `@inject.setter(bindingKey: string)`. @@ -481,7 +482,7 @@ const component = ctx.getSync('my-component'); ``` **NOTE**: It's recommended to use `@inject` with specific keys for dependency -injection if possible. Use `@inject.context` only when the code need to access +injection if possible. Use `@inject.context` only when the code needs to access the current context object for advanced use cases. For more information, see the [Dependency Injection](Dependency-Injection.md) @@ -527,27 +528,27 @@ For more information on authentication with LoopBack, visit As a [domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design) concept, the repository is a layer between your domain object and data mapping -layers using a collection-like interface for accessing domain objects. +layers that uses a collection-like interface for accessing domain objects. -In LoopBack, a domain object is usually a TypeScript/JavaScript Class instance, -and a typical example of a data mapping layer module could be a database's -node.js driver. +In LoopBack, a domain object is usually a TypeScript/JavaScript Class instance. +A typical example of a data mapping layer module could be a database's node.js +driver. -LoopBack repository encapsulates your TypeScript/JavaScript Class instance, and -its methods that communicate with your database. It is an interface to implement +LoopBack repository encapsulates your TypeScript/JavaScript Class instance and +the methods that communicate with your database. It is an interface to implement data persistence. Repository decorators are used for defining models (domain objects) for use with -your chosen datasources, and the navigation strategies among models. +your chosen datasources and for the navigation strategies among models. If you are not familiar with repository related concepts like `Model`, `Entity` -and `Datasource`, please see LoopBack concept [Repositories](Repositories.md) to -learn more. +and `Datasource`, see LoopBack concept [Repositories](Repositories.md) to learn +more. ### Model Decorators -Model is a class that LoopBack builds for you to organize the data that share -same configurations and properties. You can use model decorators to define a +Model is a class that LoopBack builds for you to organize the data that shares +the same configurations and properties. You can use model decorators to define a model and its properties. #### Model Decorator @@ -566,12 +567,13 @@ By using a model decorator, you can define a model as your repository's metadata, which then allows you to choose between two ways of creating the repository instance: -One is to inject your repository and resolve it with Legacy Juggler that's -complete with CRUD operations for accessing the model's data. A use case can be -found in section [Repository decorator](#repository-decorator) +1. Inject your repository and resolve it with the datasource juggler bridge + that's complete with CRUD operations for accessing the model's data. A use + case can be found in this section: + [Repository decorator](#repository-decorator) -The other one is defining your own repository without using legacy juggler, and -use an ORM/ODM of your choice. +2. Define your own repository without using the datasource juggler bridge, and + use an ORM/ODM of your choice. ```ts // Missing example here diff --git a/docs/site/todo-list-tutorial-model.md b/docs/site/todo-list-tutorial-model.md index 18066eab237a..c8d5e60dfbff 100644 --- a/docs/site/todo-list-tutorial-model.md +++ b/docs/site/todo-list-tutorial-model.md @@ -10,17 +10,16 @@ summary: LoopBack 4 TodoList Application Tutorial - Add TodoList Model ### Building a checklist for your Todo models A todo item is often grouped into a checklist along with other todo items so -that it can be used to measure the progress of a bigger picture that the item is -a part of. +that it can be used to measure the progress of a bigger picture. -A set of data can often be related to another; an entity may be able to provide -access to another based on its relationship with the other entity. To take -`TodoListApplication` one step further and establish relations with the existing -`Todo` model as real-world applications often tend to do, we'll introduce the -model `TodoList`. +A data set can often be related to another data set, so that an entity may be +able to provide access to another entity based on its relationship with the +other entity. To take `TodoListApplication` one step further and establish +relations with the existing `Todo` model as real-world applications often tend +to do, we'll introduce the model `TodoList`. -We'll create `TodoList` model to represent a checklist that contains multiple -Todo items. Let's define TodoList model with the following properties: +We'll create the `TodoList` model to represent a checklist that contains +multiple Todo items. Let's define TodoList model with the following properties: - a unique id - a title @@ -68,8 +67,8 @@ Enter an empty property name when done Model TodoList was created in src/models/ ``` -Now that we have our new model, it's time to define its relation with the `Todo` -model. To `TodoList` model, add in the following property: +Now that we have our new model, we need to define its relation with the `Todo` +model. Add the following property to the `TodoList` model: #### src/models/todo-list.model.ts @@ -85,12 +84,12 @@ export class TodoList extends Entity { } ``` -Notice that `@hasMany()` decorator is used to define this property. As the -decorator's name suggests, this will let LoopBack 4 know that a todo list can -have many todo items. +The `@hasMany()` decorator defines this property. As the decorator's name +suggests, `@hasMany()` informs LoopBack 4 that a todo list can have many todo +items. -To complement `TodoList`'s relationship to `Todo`, we'll add in `todoListId` -property on `Todo` model to complete defining the relation on both ends: +To complement `TodoList`'s relationship to `Todo`, we'll add in the `todoListId` +property on the `Todo` model to define the relation on both ends: ### src/models/todo.model.ts diff --git a/docs/site/todo-list-tutorial-repository.md b/docs/site/todo-list-tutorial-repository.md index c9e6769bdafe..3bd259f1d0ad 100644 --- a/docs/site/todo-list-tutorial-repository.md +++ b/docs/site/todo-list-tutorial-repository.md @@ -9,11 +9,11 @@ summary: LoopBack 4 TodoList Application Tutorial - Add TodoList Repository ### Repositories with related models -One great feature a related model's repository has is its ability to expose a -factory function, a function that return a newly instantiated object, that can -return a 'constrained' version of the related model's repository. This factory -function is useful because it allows you to create a repository whose operations -are limited by the data set that the factory function takes in. +One great feature of a related model's repository is its ability to expose a +factory function (a function that returns a newly instantiated object) to return +a 'constrained' version of the related model's repository. A factory function is +useful because it allows you to create a repository whose operations are limited +by the data set that applies to the factory function. In this section, we'll build `TodoListRepository` to have the capability of building a constrained version of `TodoRepository`. @@ -30,12 +30,12 @@ Like `TodoRepository`, we'll use `DefaultCrudRepository` to extend our `TodoRepository`, inject `datasources.db` in this repository as well. From there we'll need to make two more additions: -- define `todos` property; this property will be used to build a constrained +- define the `todos` property, which will be used to build a constrained `TodoRepository` - inject `TodoRepository` instance -Once the property type for `todos` have been defined, use -`this._createHasManyRepositoryFactoryFor` to assign it a repository contraining +Once the property type for `todos` has been defined, use +`this._createHasManyRepositoryFactoryFor` to assign it a repository constraining factory function. Pass in the name of the relationship (`todos`) and the Todo repository instance to constrain as the arguments for the function. From 1445ebd95befd45a555acc88a679653b3f1f7679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 28 Aug 2018 16:48:39 +0200 Subject: [PATCH 12/30] feat(testlab): expose "sandbox.path" property Allow TestSandbox consumers to access the path of the sandbox directory. Clean up the code by moving "validateInst" check into "path" getter, thus avoiding the need to explicitly call "validateInst" in every public API method. --- packages/testlab/src/test-sandbox.ts | 32 +++++++++++----------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/testlab/src/test-sandbox.ts b/packages/testlab/src/test-sandbox.ts index 2138614c17a6..2aafbb379ddf 100644 --- a/packages/testlab/src/test-sandbox.ts +++ b/packages/testlab/src/test-sandbox.ts @@ -20,7 +20,16 @@ import { */ export class TestSandbox { // Path of the TestSandbox - private path: string; + private _path?: string; + + public get path(): string { + if (!this._path) { + throw new Error( + `TestSandbox instance was deleted. Create a new instance.`, + ); + } + return this._path; + } /** * Will create a directory if it doesn't already exist. If it exists, you @@ -30,26 +39,14 @@ export class TestSandbox { */ constructor(path: string) { // resolve ensures path is absolute / makes it absolute (relative to cwd()) - this.path = resolve(path); + this._path = resolve(path); ensureDirSync(this.path); } - /** - * This function ensures a valid instance is being used for operations. - */ - private validateInst() { - if (!this.path) { - throw new Error( - `TestSandbox instance was deleted. Create a new instance.`, - ); - } - } - /** * Returns the path of the TestSandbox */ getPath(): string { - this.validateInst(); return this.path; } @@ -57,8 +54,6 @@ export class TestSandbox { * Resets the TestSandbox. (Remove all files in it). */ async reset(): Promise { - this.validateInst(); - // Decache files from require's cache so future tests aren't affected incase // a file is recreated in sandbox with the same file name but different // contents after resetting the sandbox. @@ -75,9 +70,8 @@ export class TestSandbox { * Deletes the TestSandbox. */ async delete(): Promise { - this.validateInst(); await remove(this.path); - delete this.path; + delete this._path; } /** @@ -86,7 +80,6 @@ export class TestSandbox { * @param dir Name of directory to create (relative to TestSandbox path) */ async mkdir(dir: string): Promise { - this.validateInst(); await ensureDir(resolve(this.path, dir)); } @@ -101,7 +94,6 @@ export class TestSandbox { * (relative to TestSandbox). Original filename used if not specified. */ async copyFile(src: string, dest?: string): Promise { - this.validateInst(); dest = dest ? resolve(this.path, dest) : resolve(this.path, parse(src).base); From cdb63b74516d7bdedd6d3741a6a2cea6776ef86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 28 Aug 2018 16:51:11 +0200 Subject: [PATCH 13/30] feat(boot): add debug logs for better troubleshooting Make it easier to troubleshoot the situation when a booter is not recognizing artifact files and/or classes exported by those files. To make debug logs easy to read, absolute paths are converted to project-relative paths in debug logs. This change required a refactoring of Booter design, where "projectRoot" becomes a required constructor argument. While making these changes, I changed "options" to be a required constructor argument too, and chaged both "options" and "projectRoot" to readonly properties. --- .../boot/src/booters/base-artifact.booter.ts | 43 +++++++++++++++++-- packages/boot/src/booters/booter-utils.ts | 13 +++++- .../boot/src/booters/controller.booter.ts | 10 +++-- .../boot/src/booters/datasource.booter.ts | 10 +++-- .../boot/src/booters/repository.booter.ts | 10 +++-- .../unit/booters/base-artifact.booter.unit.ts | 38 +++++++++------- .../test/unit/booters/booter-utils.unit.ts | 8 ++-- 7 files changed, 96 insertions(+), 36 deletions(-) diff --git a/packages/boot/src/booters/base-artifact.booter.ts b/packages/boot/src/booters/base-artifact.booter.ts index ec653dc6c4f9..da244ea59968 100644 --- a/packages/boot/src/booters/base-artifact.booter.ts +++ b/packages/boot/src/booters/base-artifact.booter.ts @@ -4,8 +4,12 @@ // License text available at https://opensource.org/licenses/MIT import {Constructor} from '@loopback/context'; +import * as debugFactory from 'debug'; +import * as path from 'path'; +import {ArtifactOptions, Booter} from '../interfaces'; import {discoverFiles, loadClassesFromFiles} from './booter-utils'; -import {Booter, ArtifactOptions} from '../interfaces'; + +const debug = debugFactory('loopback:boot:base-artifact-booter'); /** * This class serves as a base class for Booters which follow a pattern of @@ -35,14 +39,27 @@ export class BaseArtifactBooter implements Booter { /** * Options being used by the Booter. */ - options: ArtifactOptions; - projectRoot: string; + readonly options: ArtifactOptions; + readonly projectRoot: string; dirs: string[]; extensions: string[]; glob: string; discovered: string[]; classes: Array>; + constructor(projectRoot: string, options: ArtifactOptions) { + this.projectRoot = projectRoot; + this.options = options; + } + + /** + * Get the name of the artifact loaded by this booter, e.g. "Controller". + * Subclasses can override the default logic based on the class name. + */ + get artifactName(): string { + return this.constructor.name.replace(/Booter$/, ''); + } + /** * Configure the Booter by initializing the 'dirs', 'extensions' and 'glob' * properties. @@ -78,7 +95,25 @@ export class BaseArtifactBooter implements Booter { * 'discovered' property. */ async discover() { + debug( + 'Discovering %s artifacts in %j using glob %j', + this.artifactName, + this.projectRoot, + this.glob, + ); + this.discovered = await discoverFiles(this.glob, this.projectRoot); + + if (debug.enabled) { + debug( + 'Artifact files found: %s', + JSON.stringify( + this.discovered.map(f => path.relative(this.projectRoot, f)), + null, + 2, + ), + ); + } } /** @@ -90,6 +125,6 @@ export class BaseArtifactBooter implements Booter { * and then process the artifact classes as appropriate. */ async load() { - this.classes = loadClassesFromFiles(this.discovered); + this.classes = loadClassesFromFiles(this.discovered, this.projectRoot); } } diff --git a/packages/boot/src/booters/booter-utils.ts b/packages/boot/src/booters/booter-utils.ts index db5024ce85b5..2504efa95ac8 100644 --- a/packages/boot/src/booters/booter-utils.ts +++ b/packages/boot/src/booters/booter-utils.ts @@ -4,9 +4,13 @@ // License text available at https://opensource.org/licenses/MIT import {Constructor} from '@loopback/context'; +import * as debugFactory from 'debug'; +import * as path from 'path'; import {promisify} from 'util'; const glob = promisify(require('glob')); +const debug = debugFactory('loopback:boot:booter-utils'); + /** * Returns all files matching the given glob pattern relative to root * @@ -42,16 +46,23 @@ export function isClass(target: any): target is Constructor { * @param files An array of string of absolute file paths * @returns {Constructor<{}>[]} An array of Class constructors from a file */ -export function loadClassesFromFiles(files: string[]): Constructor<{}>[] { +export function loadClassesFromFiles( + files: string[], + projectRootDir: string, +): Constructor<{}>[] { const classes: Array> = []; for (const file of files) { + debug('Loading artifact file %j', path.relative(projectRootDir, file)); const moduleObj = require(file); // WORKAROUND: use `for in` instead of Object.values(). // See https://github.com/nodejs/node/issues/20278 for (const k in moduleObj) { const exported = moduleObj[k]; if (isClass(exported)) { + debug(' add %s (class %s)', k, exported.name); classes.push(exported); + } else { + debug(' skip non-class %s', k); } } } diff --git a/packages/boot/src/booters/controller.booter.ts b/packages/boot/src/booters/controller.booter.ts index b5af53d91cef..8953bc227758 100644 --- a/packages/boot/src/booters/controller.booter.ts +++ b/packages/boot/src/booters/controller.booter.ts @@ -22,13 +22,15 @@ import {BootBindings} from '../keys'; export class ControllerBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: Application, - @inject(BootBindings.PROJECT_ROOT) public projectRoot: string, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, @inject(`${BootBindings.BOOT_OPTIONS}#controllers`) public controllerConfig: ArtifactOptions = {}, ) { - super(); - // Set Controller Booter Options if passed in via bootConfig - this.options = Object.assign({}, ControllerDefaults, controllerConfig); + super( + projectRoot, + // Set Controller Booter Options if passed in via bootConfig + Object.assign({}, ControllerDefaults, controllerConfig), + ); } /** diff --git a/packages/boot/src/booters/datasource.booter.ts b/packages/boot/src/booters/datasource.booter.ts index 665a626164a1..5529cc3e040d 100644 --- a/packages/boot/src/booters/datasource.booter.ts +++ b/packages/boot/src/booters/datasource.booter.ts @@ -28,13 +28,15 @@ export class DataSourceBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) public projectRoot: string, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, @inject(`${BootBindings.BOOT_OPTIONS}#datasources`) public datasourceConfig: ArtifactOptions = {}, ) { - super(); - // Set DataSource Booter Options if passed in via bootConfig - this.options = Object.assign({}, DataSourceDefaults, datasourceConfig); + super( + projectRoot, + // Set DataSource Booter Options if passed in via bootConfig + Object.assign({}, DataSourceDefaults, datasourceConfig), + ); } /** diff --git a/packages/boot/src/booters/repository.booter.ts b/packages/boot/src/booters/repository.booter.ts index a8c2ce841fbf..caf8e914a758 100644 --- a/packages/boot/src/booters/repository.booter.ts +++ b/packages/boot/src/booters/repository.booter.ts @@ -25,13 +25,15 @@ export class RepositoryBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) public projectRoot: string, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, @inject(`${BootBindings.BOOT_OPTIONS}#repositories`) public repositoryOptions: ArtifactOptions = {}, ) { - super(); - // Set Repository Booter Options if passed in via bootConfig - this.options = Object.assign({}, RepositoryDefaults, repositoryOptions); + super( + projectRoot, + // Set Repository Booter Options if passed in via bootConfig + Object.assign({}, RepositoryDefaults, repositoryOptions), + ); } /** diff --git a/packages/boot/test/unit/booters/base-artifact.booter.unit.ts b/packages/boot/test/unit/booters/base-artifact.booter.unit.ts index 9ba05542f2b9..68f38e92bb88 100644 --- a/packages/boot/test/unit/booters/base-artifact.booter.unit.ts +++ b/packages/boot/test/unit/booters/base-artifact.booter.unit.ts @@ -6,35 +6,38 @@ import {BaseArtifactBooter} from '../../../index'; import {expect} from '@loopback/testlab'; import {resolve} from 'path'; +import {ArtifactOptions} from '../../../src'; describe('base-artifact booter unit tests', () => { - let booterInst: BaseArtifactBooter; - - beforeEach(getBaseBooter); + const TEST_OPTIONS = { + dirs: ['test', 'test2'], + extensions: ['.test.js', 'test2.js'], + nested: false, + }; describe('configure()', () => { - const options = { - dirs: ['test', 'test2'], - extensions: ['.test.js', 'test2.js'], - nested: false, - }; - it(`sets 'dirs' / 'extensions' properties as an array if a string`, async () => { - booterInst.options = {dirs: 'test', extensions: '.test.js', nested: true}; + const booterInst = givenBaseBooter({ + dirs: 'test', + extensions: '.test.js', + nested: true, + }); await booterInst.configure(); expect(booterInst.dirs).to.be.eql(['test']); expect(booterInst.extensions).to.be.eql(['.test.js']); }); it(`creates and sets 'glob' pattern`, async () => { - booterInst.options = options; + const booterInst = givenBaseBooter(); const expected = '/@(test|test2)/*@(.test.js|test2.js)'; await booterInst.configure(); expect(booterInst.glob).to.equal(expected); }); it(`creates and sets 'glob' pattern (nested)`, async () => { - booterInst.options = Object.assign({}, options, {nested: true}); + const booterInst = givenBaseBooter( + Object.assign({}, TEST_OPTIONS, {nested: true}), + ); const expected = '/@(test|test2)/**/*@(.test.js|test2.js)'; await booterInst.configure(); expect(booterInst.glob).to.equal(expected); @@ -42,7 +45,9 @@ describe('base-artifact booter unit tests', () => { it(`sets 'glob' pattern to options.glob if present`, async () => { const expected = '/**/*.glob'; - booterInst.options = Object.assign({}, options, {glob: expected}); + const booterInst = givenBaseBooter( + Object.assign({}, TEST_OPTIONS, {glob: expected}), + ); await booterInst.configure(); expect(booterInst.glob).to.equal(expected); }); @@ -50,7 +55,7 @@ describe('base-artifact booter unit tests', () => { describe('discover()', () => { it(`sets 'discovered' property`, async () => { - booterInst.projectRoot = __dirname; + const booterInst = givenBaseBooter(); // Fake glob pattern so we get an empty array booterInst.glob = '/abc.xyz'; await booterInst.discover(); @@ -60,6 +65,7 @@ describe('base-artifact booter unit tests', () => { describe('load()', () => { it(`sets 'classes' property to Classes from a file`, async () => { + const booterInst = givenBaseBooter(); booterInst.discovered = [ resolve(__dirname, '../../fixtures/multiple.artifact.js'), ]; @@ -69,7 +75,7 @@ describe('base-artifact booter unit tests', () => { }); }); - async function getBaseBooter() { - booterInst = new BaseArtifactBooter(); + function givenBaseBooter(options?: ArtifactOptions) { + return new BaseArtifactBooter(__dirname, options || TEST_OPTIONS); } }); diff --git a/packages/boot/test/unit/booters/booter-utils.unit.ts b/packages/boot/test/unit/booters/booter-utils.unit.ts index 734b2d4a30e4..cf53d63f33d2 100644 --- a/packages/boot/test/unit/booters/booter-utils.unit.ts +++ b/packages/boot/test/unit/booters/booter-utils.unit.ts @@ -67,7 +67,7 @@ describe('booter-utils unit tests', () => { const files = [resolve(SANDBOX_PATH, 'multiple.artifact.js')]; const NUM_CLASSES = 2; // Number of classes in above file - const classes = loadClassesFromFiles(files); + const classes = loadClassesFromFiles(files, sandbox.path); expect(classes).to.have.lengthOf(NUM_CLASSES); expect(classes[0]).to.be.a.Function(); expect(classes[1]).to.be.a.Function(); @@ -79,14 +79,16 @@ describe('booter-utils unit tests', () => { ); const files = [resolve(SANDBOX_PATH, 'empty.artifact.js')]; - const classes = loadClassesFromFiles(files); + const classes = loadClassesFromFiles(files, sandbox.path); expect(classes).to.be.an.Array(); expect(classes).to.be.empty(); }); it('throws an error given a non-existent file', async () => { const files = [resolve(SANDBOX_PATH, 'fake.artifact.js')]; - expect(() => loadClassesFromFiles(files)).to.throw(/Cannot find module/); + expect(() => loadClassesFromFiles(files, sandbox.path)).to.throw( + /Cannot find module/, + ); }); }); }); From bf8e9c8c5a90b5cb8b88a6dd308e5d3649efdcbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 28 Aug 2018 16:57:37 +0200 Subject: [PATCH 14/30] feat(boot): implement Service booter --- packages/boot/README.md | 24 +++++ packages/boot/docs.json | 1 + packages/boot/package.json | 1 + packages/boot/src/boot.component.ts | 14 ++- packages/boot/src/booters/index.ts | 3 +- packages/boot/src/booters/service.booter.ts | 82 ++++++++++++++ packages/boot/test/fixtures/application.ts | 5 +- .../test/fixtures/service-class.artifact.ts | 10 ++ .../fixtures/service-provider.artifact.ts | 28 +++++ .../integration/service.booter.integration.ts | 49 +++++++++ .../boot/test/unit/boot.component.unit.ts | 8 ++ .../unit/booters/datasource.booter.unit.ts | 2 +- .../test/unit/booters/service.booter.unit.ts | 101 ++++++++++++++++++ 13 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 packages/boot/src/booters/service.booter.ts create mode 100644 packages/boot/test/fixtures/service-class.artifact.ts create mode 100644 packages/boot/test/fixtures/service-provider.artifact.ts create mode 100644 packages/boot/test/integration/service.booter.integration.ts create mode 100644 packages/boot/test/unit/booters/service.booter.unit.ts diff --git a/packages/boot/README.md b/packages/boot/README.md index 9b2f35255f4d..3b0b4f371985 100644 --- a/packages/boot/README.md +++ b/packages/boot/README.md @@ -48,6 +48,7 @@ List of Options available on BootOptions Object. | `controllers` | `ArtifactOptions` | ControllerBooter convention options | | `repositories` | `ArtifactOptions` | RepositoryBooter convention options | | `datasources` | `ArtifactOptions` | DataSourceBooter convention options | +| `services` | `ArtifactOptions` | ServiceBooter convention options | ### ArtifactOptions @@ -159,6 +160,29 @@ Available options on the `datasources` object on `BootOptions` are as follows: | `nested` | `boolean` | `true` | Look in nested directories in `dirs` for DataSource artifacts | | `glob` | `string` | | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | +### ServiceBooter + +#### Description + +Discovers and binds Service providers using `app.serviceProvider()` (Application +must use `ServiceMixin` from `@loopback/service-proxy`). + +#### Options + +The options for this can be passed via `BootOptions` when calling +`app.boot(options: BootOptions)`. + +The options for this are passed in a `services` object on `BootOptions`. + +Available options on the `services` object on `BootOptions` are as follows: + +| Options | Type | Default | Description | +| ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `dirs` | `string \| string[]` | `['repositories']` | Paths relative to projectRoot to look in for Service artifacts | +| `extensions` | `string \| string[]` | `['.repository.js']` | File extensions to match for Service artifacts | +| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for Service artifacts | +| `glob` | `string` | | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | + ## Contributions - [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) diff --git a/packages/boot/docs.json b/packages/boot/docs.json index c2180c24f099..e69aee6a5427 100644 --- a/packages/boot/docs.json +++ b/packages/boot/docs.json @@ -6,6 +6,7 @@ "src/booters/controller.booter.ts", "src/booters/datasource.booter.ts", "src/booters/repository.booter.ts", + "src/booters/service.booter.ts", "src/booters/index.ts", "src/mixins/boot.mixin.ts", "src/mixins/index.ts", diff --git a/packages/boot/package.json b/packages/boot/package.json index cc4850eda2af..a30fdbeb9800 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -30,6 +30,7 @@ "@loopback/core": "^0.11.5", "@loopback/dist-util": "^0.3.6", "@loopback/repository": "^0.15.1", + "@loopback/service-proxy": "^0.7.1", "@types/debug": "0.0.30", "@types/glob": "^5.0.35", "debug": "^3.1.0", diff --git a/packages/boot/src/boot.component.ts b/packages/boot/src/boot.component.ts index e13430864d3b..ec43376b922b 100644 --- a/packages/boot/src/boot.component.ts +++ b/packages/boot/src/boot.component.ts @@ -6,7 +6,12 @@ import {Bootstrapper} from './bootstrapper'; import {Component, Application, CoreBindings} from '@loopback/core'; import {inject, BindingScope} from '@loopback/context'; -import {ControllerBooter, RepositoryBooter, DataSourceBooter} from './booters'; +import { + ControllerBooter, + RepositoryBooter, + DataSourceBooter, + ServiceBooter, +} from './booters'; import {BootBindings} from './keys'; /** @@ -17,7 +22,12 @@ import {BootBindings} from './keys'; export class BootComponent implements Component { // Export a list of default booters in the component so they get bound // automatically when this component is mounted. - booters = [ControllerBooter, RepositoryBooter, DataSourceBooter]; + booters = [ + ControllerBooter, + RepositoryBooter, + ServiceBooter, + DataSourceBooter, + ]; /** * diff --git a/packages/boot/src/booters/index.ts b/packages/boot/src/booters/index.ts index f31c44a32175..fcf21cce42fd 100644 --- a/packages/boot/src/booters/index.ts +++ b/packages/boot/src/booters/index.ts @@ -6,5 +6,6 @@ export * from './base-artifact.booter'; export * from './booter-utils'; export * from './controller.booter'; -export * from './repository.booter'; export * from './datasource.booter'; +export * from './repository.booter'; +export * from './service.booter'; diff --git a/packages/boot/src/booters/service.booter.ts b/packages/boot/src/booters/service.booter.ts new file mode 100644 index 000000000000..ecb924cf8311 --- /dev/null +++ b/packages/boot/src/booters/service.booter.ts @@ -0,0 +1,82 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {CoreBindings} from '@loopback/core'; +import {ApplicationWithServices} from '@loopback/service-proxy'; +import {inject, Provider, Constructor} from '@loopback/context'; +import {ArtifactOptions} from '../interfaces'; +import {BaseArtifactBooter} from './base-artifact.booter'; +import {BootBindings} from '../keys'; + +type ServiceProviderClass = Constructor>; + +/** + * A class that extends BaseArtifactBooter to boot the 'DataSource' artifact type. + * Discovered DataSources are bound using `app.controller()`. + * + * Supported phases: configure, discover, load + * + * @param app Application instance + * @param projectRoot Root of User Project relative to which all paths are resolved + * @param [bootConfig] DataSource Artifact Options Object + */ +export class ServiceBooter extends BaseArtifactBooter { + serviceProviders: ServiceProviderClass[]; + + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + public app: ApplicationWithServices, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(`${BootBindings.BOOT_OPTIONS}#services`) + public serviceConfig: ArtifactOptions = {}, + ) { + super( + projectRoot, + // Set DataSource Booter Options if passed in via bootConfig + Object.assign({}, ServiceDefaults, serviceConfig), + ); + } + + /** + * Uses super method to get a list of Artifact classes. Boot each file by + * creating a DataSourceConstructor and binding it to the application class. + */ + async load() { + await super.load(); + + this.serviceProviders = this.classes.filter(isServiceProvider); + + /** + * If Service providers were discovered, we need to make sure ServiceMixin + * was used (so we have `app.serviceProvider()`) to perform the binding of a + * Service provider class. + */ + if (this.serviceProviders.length > 0) { + if (!this.app.serviceProvider) { + console.warn( + 'app.serviceProvider() function is needed for ServiceBooter. You can add ' + + 'it to your Application using ServiceMixin from @loopback/service-proxy.', + ); + } else { + this.serviceProviders.forEach(cls => { + this.app.serviceProvider(cls as Constructor>); + }); + } + } + } +} + +/** + * Default ArtifactOptions for DataSourceBooter. + */ +export const ServiceDefaults: ArtifactOptions = { + dirs: ['services'], + extensions: ['.service.js'], + nested: true, +}; + +function isServiceProvider(cls: Constructor<{}>): cls is ServiceProviderClass { + return /Provider$/.test(cls.name); +} diff --git a/packages/boot/test/fixtures/application.ts b/packages/boot/test/fixtures/application.ts index 567096bff60a..3d370abaac04 100644 --- a/packages/boot/test/fixtures/application.ts +++ b/packages/boot/test/fixtures/application.ts @@ -6,9 +6,12 @@ import {ApplicationConfig} from '@loopback/core'; import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; +import {ServiceMixin} from '@loopback/service-proxy'; import {BootMixin} from '../../index'; -export class BooterApp extends RepositoryMixin(BootMixin(RestApplication)) { +export class BooterApp extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { constructor(options?: ApplicationConfig) { super(options); this.projectRoot = __dirname; diff --git a/packages/boot/test/fixtures/service-class.artifact.ts b/packages/boot/test/fixtures/service-class.artifact.ts new file mode 100644 index 000000000000..25861df0d1f8 --- /dev/null +++ b/packages/boot/test/fixtures/service-class.artifact.ts @@ -0,0 +1,10 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export class GreetingService { + greet(whom: string = 'world') { + return Promise.resolve(`Hello ${whom}`); + } +} diff --git a/packages/boot/test/fixtures/service-provider.artifact.ts b/packages/boot/test/fixtures/service-provider.artifact.ts new file mode 100644 index 000000000000..b48c28b7d7f4 --- /dev/null +++ b/packages/boot/test/fixtures/service-provider.artifact.ts @@ -0,0 +1,28 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Provider} from '@loopback/core'; + +export interface GeoPoint { + lat: number; + lng: number; +} + +export interface GeocoderService { + geocode(address: string): Promise; +} + +// A dummy service instance to make unit testing easier +const GeocoderSingleton: GeocoderService = { + geocode(address: string) { + return Promise.resolve({lat: 0, lng: 0}); + }, +}; + +export class GeocoderServiceProvider implements Provider { + value(): Promise { + return Promise.resolve(GeocoderSingleton); + } +} diff --git a/packages/boot/test/integration/service.booter.integration.ts b/packages/boot/test/integration/service.booter.integration.ts new file mode 100644 index 000000000000..f42585486c3a --- /dev/null +++ b/packages/boot/test/integration/service.booter.integration.ts @@ -0,0 +1,49 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect, TestSandbox} from '@loopback/testlab'; +import {resolve} from 'path'; +import {BooterApp} from '../fixtures/application'; + +describe('service booter integration tests', () => { + const SANDBOX_PATH = resolve(__dirname, '../../.sandbox'); + const sandbox = new TestSandbox(SANDBOX_PATH); + + const SERVICES_PREFIX = 'services'; + const SERVICES_TAG = 'service'; + + let app: BooterApp; + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getApp); + + it('boots services when app.boot() is called', async () => { + const expectedBindings = [ + `${SERVICES_PREFIX}.GeocoderService`, + // greeting service is skipped - service classes are not supported (yet) + ]; + + await app.boot(); + + const bindings = app.findByTag(SERVICES_TAG).map(b => b.key); + expect(bindings.sort()).to.eql(expectedBindings.sort()); + }); + + async function getApp() { + await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/service-provider.artifact.js'), + 'services/geocoder.service.js', + ); + + await sandbox.copyFile( + resolve(__dirname, '../fixtures/service-class.artifact.js'), + 'services/greeting.service.js', + ); + + const MyApp = require(resolve(SANDBOX_PATH, 'application.js')).BooterApp; + app = new MyApp(); + } +}); diff --git a/packages/boot/test/unit/boot.component.unit.ts b/packages/boot/test/unit/boot.component.unit.ts index ae24503c5751..4bfdff3aee02 100644 --- a/packages/boot/test/unit/boot.component.unit.ts +++ b/packages/boot/test/unit/boot.component.unit.ts @@ -12,6 +12,7 @@ import { BootMixin, RepositoryBooter, DataSourceBooter, + ServiceBooter, } from '../../'; describe('boot.component unit tests', () => { @@ -47,6 +48,13 @@ describe('boot.component unit tests', () => { expect(booterInst).to.be.an.instanceOf(DataSourceBooter); }); + it('ServiceBooter is bound as a booter by default', async () => { + const booterInst = await app.get( + `${BootBindings.BOOTER_PREFIX}.ServiceBooter`, + ); + expect(booterInst).to.be.an.instanceOf(ServiceBooter); + }); + function getApp() { app = new BootableApp(); app.bind(BootBindings.PROJECT_ROOT).to(__dirname); diff --git a/packages/boot/test/unit/booters/datasource.booter.unit.ts b/packages/boot/test/unit/booters/datasource.booter.unit.ts index c82d99df9468..4d278b06cff9 100644 --- a/packages/boot/test/unit/booters/datasource.booter.unit.ts +++ b/packages/boot/test/unit/booters/datasource.booter.unit.ts @@ -29,7 +29,7 @@ describe('datasource booter unit tests', () => { beforeEach(createStub); afterEach(restoreStub); - it('gives a wanring if called on an app without RepositoryMixin', async () => { + it('gives a warning if called on an app without RepositoryMixin', async () => { const normalApp = new Application(); await sandbox.copyFile( resolve(__dirname, '../../fixtures/datasource.artifact.js'), diff --git a/packages/boot/test/unit/booters/service.booter.unit.ts b/packages/boot/test/unit/booters/service.booter.unit.ts new file mode 100644 index 000000000000..1abab13412da --- /dev/null +++ b/packages/boot/test/unit/booters/service.booter.unit.ts @@ -0,0 +1,101 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect, TestSandbox, sinon} from '@loopback/testlab'; +import {resolve} from 'path'; +import {ApplicationWithServices, ServiceMixin} from '@loopback/service-proxy'; +import {ServiceBooter, ServiceDefaults} from '../../../src'; +import {Application} from '@loopback/core'; + +describe('service booter unit tests', () => { + const SANDBOX_PATH = resolve(__dirname, '../../../.sandbox'); + const sandbox = new TestSandbox(SANDBOX_PATH); + + const SERVICES_PREFIX = 'services'; + const SERVICES_TAG = 'service'; + + class AppWithRepo extends ServiceMixin(Application) {} + + let app: AppWithRepo; + let stub: sinon.SinonStub; + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getApp); + beforeEach(createStub); + afterEach(restoreStub); + + it('gives a warning if called on an app without RepositoryMixin', async () => { + const normalApp = new Application(); + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/service-provider.artifact.js'), + ); + + const booterInst = new ServiceBooter( + normalApp as ApplicationWithServices, + SANDBOX_PATH, + ); + + booterInst.discovered = [ + resolve(SANDBOX_PATH, 'service-provider.artifact.js'), + ]; + await booterInst.load(); + + sinon.assert.calledOnce(stub); + sinon.assert.calledWith( + stub, + 'app.serviceProvider() function is needed for ServiceBooter. You can add ' + + 'it to your Application using ServiceMixin from @loopback/service-proxy.', + ); + }); + + it(`uses ServiceDefaults for 'options' if none are given`, () => { + const booterInst = new ServiceBooter(app, SANDBOX_PATH); + expect(booterInst.options).to.deepEqual(ServiceDefaults); + }); + + it('overrides defaults with provided options and uses defaults for the rest', () => { + const options = { + dirs: ['test'], + extensions: ['.ext1'], + }; + const expected = Object.assign({}, options, { + nested: ServiceDefaults.nested, + }); + + const booterInst = new ServiceBooter(app, SANDBOX_PATH, options); + expect(booterInst.options).to.deepEqual(expected); + }); + + it('binds services during the load phase', async () => { + const expected = [`${SERVICES_PREFIX}.GeocoderService`]; + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/service-provider.artifact.js'), + ); + const booterInst = new ServiceBooter(app, SANDBOX_PATH); + const NUM_CLASSES = 1; // 1 class in above file. + + booterInst.discovered = [ + resolve(SANDBOX_PATH, 'service-provider.artifact.js'), + ]; + await booterInst.load(); + + const services = app.findByTag(SERVICES_TAG); + const keys = services.map(binding => binding.key); + expect(keys).to.have.lengthOf(NUM_CLASSES); + expect(keys.sort()).to.eql(expected.sort()); + }); + + function getApp() { + app = new AppWithRepo(); + } + + function restoreStub() { + stub.restore(); + } + + function createStub() { + stub = sinon.stub(console, 'warn'); + } +}); From 9cab3a9881db66d2b4b99eecf215e0ae62453d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 30 Aug 2018 09:40:04 +0200 Subject: [PATCH 15/30] fixup! finish the changes - address review comments - use ServiceBooter in example apps - update tutorial pages - update docs --- docs/site/Booting-an-Application.md | 27 +++++++ docs/site/sidebars/lb4_sidebar.yml | 4 - ...soap-calculator-tutorial-add-controller.md | 3 +- ...ap-calculator-tutorial-register-service.md | 80 ------------------- .../soap-calculator-tutorial-run-and-test.md | 3 +- .../soap-calculator-tutorial-scaffolding.md | 13 +-- docs/site/todo-tutorial-geocoding-service.md | 35 +------- examples/soap-calculator/src/application.ts | 8 -- examples/todo/src/application.ts | 9 --- packages/boot/README.md | 4 + packages/boot/src/booters/service.booter.ts | 4 +- .../test/fixtures/service-class.artifact.ts | 2 + 12 files changed, 50 insertions(+), 142 deletions(-) delete mode 100644 docs/site/soap-calculator-tutorial-register-service.md diff --git a/docs/site/Booting-an-Application.md b/docs/site/Booting-an-Application.md index ea308cf760c7..b4332ddfa535 100644 --- a/docs/site/Booting-an-Application.md +++ b/docs/site/Booting-an-Application.md @@ -237,6 +237,33 @@ The `datasources` object support the following options: | `nested` | `boolean` | `true` | Look in nested directories in `dirs` for DataSource artifacts | | `glob` | `string` | | A `glob` pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). | +### Service Booter + +#### Description + +Discovers and binds Service providers using `app.serviceProvider()` (Application +must use `ServiceMixin` from `@loopback/service-proxy`). + +**IMPORTANT:** For a class to be recognized by `ServiceBooter` as a service +provider, its name must end with `Provider` suffix and its prototype must have a +`value()` method. + +#### Options + +The options for this can be passed via `BootOptions` when calling +`app.boot(options: BootOptions)`. + +The options for this are passed in a `services` object on `BootOptions`. + +Available options on the `services` object on `BootOptions` are as follows: + +| Options | Type | Default | Description | +| ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `dirs` | `string \| string[]` | `['repositories']` | Paths relative to projectRoot to look in for Service artifacts | +| `extensions` | `string \| string[]` | `['.repository.js']` | File extensions to match for Service artifacts | +| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for Service artifacts | +| `glob` | `string` | | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | + ### Custom Booters A custom Booter can be written as a Class that implements the `Booter` diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index b17c6c6c756d..9556a8a32b3d 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -95,10 +95,6 @@ children: url: soap-calculator-tutorial-add-controller.html output: 'web, pdf' - - title: 'Register the Service' - url: soap-calculator-tutorial-register-service.html - output: 'web, pdf' - - title: 'Run and Test it' url: soap-calculator-tutorial-run-and-test.html output: 'web, pdf' diff --git a/docs/site/soap-calculator-tutorial-add-controller.md b/docs/site/soap-calculator-tutorial-add-controller.md index c71823a32f44..a14793452ad5 100644 --- a/docs/site/soap-calculator-tutorial-add-controller.md +++ b/docs/site/soap-calculator-tutorial-add-controller.md @@ -193,4 +193,5 @@ the divide end point method. Previous step: [Add a Service](soap-calculator-tutorial-add-service.md) -Next step: [Register the Service](soap-calculator-tutorial-register-service.md) +Next step: +[Run and Test the Application](soap-calculator-tutorial-run-and-test.md) diff --git a/docs/site/soap-calculator-tutorial-register-service.md b/docs/site/soap-calculator-tutorial-register-service.md deleted file mode 100644 index c41944becd12..000000000000 --- a/docs/site/soap-calculator-tutorial-register-service.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -lang: en -title: 'Register the Service' -keywords: LoopBack 4.0, LoopBack 4 -sidebar: lb4_sidebar -permalink: /doc/en/lb4/soap-calculator-tutorial-register-service.html ---- - -### Registering the service in the Application src/application.ts - -We need to add the **CalculatorService** service to the `src/application.ts` -file so that it is loaded automatically at boot time and the application knows -about its correspondent **key** to make it available in the **DI** _(Dependency -Injection)_. - -#### Importing the service and helper classes - -Add the following import statements after all the previous imports. - -```ts -import {ServiceMixin} from '@loopback/service-proxy'; -import {CalculatorServiceProvider} from './services/calculator.service'; -``` - -#### Applying `ServiceMixin` on our Application class - -Modify the inheritance chain of our Application class as follows: - -```ts -export class SoapCalculatorApplication extends BootMixin( - ServiceMixin(RepositoryMixin(RestApplication)), -) { - // (no changes in application constructor or methods) -} -``` - -#### Registering the Service and bind it to a key - -Let's continue by creating a method to register services used by our -application. Notice that we are using `this.serviceProvider` method contributed -by `ServiceMixin`, this method removes the suffix `Provider` from the class name -and uses the remaining string as the binding key. For our service provider -called `CalculatorServiceProvider`, the binding key becomes -**services.CalculatorService** and matches the -`@inject('services.CalculatorService')` decorator parameter we used in our -controller. - -**NOTE:** This will be the method for now until we place the autodiscover and -registration for services in the same way we do now for other artifacts in -**LB4**. - -```ts - setupServices() { - this.serviceProvider(CalculatorServiceProvider); - } -``` - -Finally call this `setupServices()` method from inside the Application -constructor after the `this.sequence(MySequence);` statement. - -```ts -//bind services -this.setupServices(); -``` - -**Note:** We could have achieved the above result by calling the following line -inside the setupServices() method, replacing the method provided by the mixin. -However, the mixin-provided method is more efficient when you need to register -multiple services, to keep the _keys_ standard. - -```ts -this.bind('services.CalculatorService').toProvider(CalculatorServiceProvider); -``` - -### Navigation - -Previous step: [Add a Controller](soap-calculator-tutorial-add-controller.md) - -Next step: -[Run and Test the Application](soap-calculator-tutorial-run-and-test.md) diff --git a/docs/site/soap-calculator-tutorial-run-and-test.md b/docs/site/soap-calculator-tutorial-run-and-test.md index 783120ccbc20..e07a68aae57a 100644 --- a/docs/site/soap-calculator-tutorial-run-and-test.md +++ b/docs/site/soap-calculator-tutorial-run-and-test.md @@ -66,5 +66,4 @@ options = Object.assign( ### Navigation -Previous step: -[Register the Service](soap-calculator-tutorial-register-service.md) +Previous step: [Add a Controller](soap-calculator-tutorial-add-controller.md) diff --git a/docs/site/soap-calculator-tutorial-scaffolding.md b/docs/site/soap-calculator-tutorial-scaffolding.md index d0bb07cc6531..011dad75c97f 100644 --- a/docs/site/soap-calculator-tutorial-scaffolding.md +++ b/docs/site/soap-calculator-tutorial-scaffolding.md @@ -12,12 +12,14 @@ Let's start by creating the initial application by running the following command: ```sh -lb4 app soap-calculator --enableRepository +lb4 app soap-calculator --enableRepository --enableServices ``` **Note:** The option **--enableRepository** instructs the **CLI** to include a -RepositoryMixin class in the application constructor which will be needed when -we create the datasource. +`RepositoryMixin` class in the application constructor which will be needed when +we create the datasource. The option **--enableServices** instructs the **CLI** +to include a `ServiceMixin` class in the application constructor which will be +needed to register our SOAP service client. **LB4** will ask you a few questions _(you can leave the default options)_. The description and the root directory are obvious. The class name referes to the @@ -31,8 +33,9 @@ application.ts file. ``` Next you will see a list of options for the build settings, if you did not -specify --enableRepository in the last command, then you will see it in this -list, make sure you enable the repository for the application. +specify `--enableRepository` and `--enableServices` in the last command, then +you will see them in this list, make sure you enable both the repository and the +services for the application. **Note:** Enable all options, unless you know what you are doing, see [The Getting Started guide](Getting-started.md) for more information. diff --git a/docs/site/todo-tutorial-geocoding-service.md b/docs/site/todo-tutorial-geocoding-service.md index 3da83c76488e..13e51fabbf49 100644 --- a/docs/site/todo-tutorial-geocoding-service.md +++ b/docs/site/todo-tutorial-geocoding-service.md @@ -94,12 +94,12 @@ docs here: [REST connector](/doc/en/lb3/REST-connector.html). Create a new directory `src/services` and add the following two new files: -- `src/geocoder.service.ts` defining TypeScript interfaces for Geocoder service - and implementing a service proxy provider. +- `src/services/geocoder.service.ts` defining TypeScript interfaces for Geocoder + service and implementing a service proxy provider. - `src/index.ts` providing a conventient access to all services via a single `import` statement. -#### src/geocoder.service.ts +#### src/services/geocoder.service.ts ```ts import {getService, juggler} from '@loopback/service-proxy'; @@ -140,35 +140,6 @@ export class GeocoderServiceProvider implements Provider { export * from './geocoder.service'; ``` -### Register the service for dependency injection - -Because `@loopback/boot` does not support loading of services yet (see -[issue #1439](https://github.com/strongloop/loopback-next/issues/1439)), we need -to add few code snippets to our Application class to take care of this task. - -#### src/application.ts - -```ts -import {ServiceMixin} from '@loopback/service-proxy'; - -export class TodoListApplication extends BootMixin( - ServiceMixin(RepositoryMixin(RestApplication)), -) { - constructor(options?: ApplicationConfig) { - super(options); - // etc., keep the existing code without changes - - // ADD THE FOLLOWING LINE AT THE END - this.setupServices(); - } - - // ADD THE FOLLOWING TWO METHODS - setupServices() { - this.serviceProvider(GeocoderServiceProvider); - } -} -``` - ### Enhance Todo model with location data Add two new properties to our Todo model: `remindAtAddress` and `remindAtGeo`. diff --git a/examples/soap-calculator/src/application.ts b/examples/soap-calculator/src/application.ts index f83914987231..0022f310d71d 100644 --- a/examples/soap-calculator/src/application.ts +++ b/examples/soap-calculator/src/application.ts @@ -4,7 +4,6 @@ import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {ServiceMixin} from '@loopback/service-proxy'; import {MySequence} from './sequence'; -import {CalculatorServiceProvider} from './services/calculator.service'; export class SoapCalculatorApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), @@ -15,9 +14,6 @@ export class SoapCalculatorApplication extends BootMixin( // Set up the custom sequence this.sequence(MySequence); - //bind services - this.setupServices(); - this.projectRoot = __dirname; // Customize @loopback/boot Booter Conventions here this.bootOptions = { @@ -29,8 +25,4 @@ export class SoapCalculatorApplication extends BootMixin( }, }; } - - setupServices() { - this.serviceProvider(CalculatorServiceProvider); - } } diff --git a/examples/todo/src/application.ts b/examples/todo/src/application.ts index 8a5b428c5e99..154f2374baf0 100644 --- a/examples/todo/src/application.ts +++ b/examples/todo/src/application.ts @@ -9,7 +9,6 @@ import {RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import {ServiceMixin} from '@loopback/service-proxy'; import {MySequence} from './sequence'; -import {GeocoderServiceProvider} from './services'; export class TodoListApplication extends BootMixin( ServiceMixin(RepositoryMixin(RestApplication)), @@ -30,13 +29,5 @@ export class TodoListApplication extends BootMixin( nested: true, }, }; - - // TODO(bajtos) Services should be created and registered by @loopback/boot - // See https://github.com/strongloop/loopback-next/issues/1439 - this.setupServices(); - } - - setupServices() { - this.serviceProvider(GeocoderServiceProvider); } } diff --git a/packages/boot/README.md b/packages/boot/README.md index 3b0b4f371985..5bec4bf6e04a 100644 --- a/packages/boot/README.md +++ b/packages/boot/README.md @@ -167,6 +167,10 @@ Available options on the `datasources` object on `BootOptions` are as follows: Discovers and binds Service providers using `app.serviceProvider()` (Application must use `ServiceMixin` from `@loopback/service-proxy`). +**IMPORTANT:** For a class to be recognized by `ServiceBooter` as a service +provider, its name must end with `Provider` suffix and its prototype must have a +`value()` method. + #### Options The options for this can be passed via `BootOptions` when calling diff --git a/packages/boot/src/booters/service.booter.ts b/packages/boot/src/booters/service.booter.ts index ecb924cf8311..b897babb9e68 100644 --- a/packages/boot/src/booters/service.booter.ts +++ b/packages/boot/src/booters/service.booter.ts @@ -78,5 +78,7 @@ export const ServiceDefaults: ArtifactOptions = { }; function isServiceProvider(cls: Constructor<{}>): cls is ServiceProviderClass { - return /Provider$/.test(cls.name); + const hasSupportedName = cls.name.endsWith('Provider'); + const hasValueMethod = 'value' in cls.prototype; + return hasSupportedName && hasValueMethod; } diff --git a/packages/boot/test/fixtures/service-class.artifact.ts b/packages/boot/test/fixtures/service-class.artifact.ts index 25861df0d1f8..a15cbdcef2df 100644 --- a/packages/boot/test/fixtures/service-class.artifact.ts +++ b/packages/boot/test/fixtures/service-class.artifact.ts @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +// NOTE(bajtos) At the moment, ServiceBooter recognizes only service providers. +// This class is used by tests to verify that non-provider classes are ignored. export class GreetingService { greet(whom: string = 'world') { return Promise.resolve(`Hello ${whom}`); From f3290cd075e7d6f39d2422c3dc8aa60347967023 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 29 Aug 2018 07:45:04 -0700 Subject: [PATCH 16/30] docs: add basic docs for KeyValueRepository --- docs/site/Repositories.md | 105 +++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index ad55bbabbbf6..c8d2c38e203e 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -28,7 +28,7 @@ interface CustomerRepository extends Repository { See more examples at: - [Repository/CrudRepository/EntityRepository](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repositories/repository.ts) -- [KVRepository](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repositories/kv.repository.ts) +- [KeyValueRepository](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repositories/kv.repository.ts) ## Installation @@ -291,7 +291,108 @@ Please See [Testing Your Application](Testing-Your-Application.md) section in order to set up and write unit, acceptance, and integration tests for your application. -## Persisting Data without Juggler [Using MySQL database] +## Access KeyValue Stores + +We can now access key-value stores such as [Redis](https://redis.io/) using the +[KeyValueRepository]((https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repositories/kv.repository.ts). + +### Define a KeyValue Datasource + +We first need to define a datasource to configure the key-value store. For +better flexibility, we spilt the datasource definition into two files. The json +file captures the configuration properties and it can be possibly overridden by +dependency injection. + +1. redis.datasource.json + +```json +{ + "name": "redis", + "connector": "kv-redis", + "host": "127.0.0.1", + "port": 6379, + "password": "", + "db": 0 +} +``` + +2. redis.datasource.ts + +The class uses a configuration object to set up a datasource for the Redis +instance. By default, the configuration is loaded from `redis.datasource.json`. +We can override it by binding a new object to `datasources.config.redis` for a +context. + +```ts +import {inject} from '@loopback/core'; +import {juggler, AnyObject} from '@loopback/repository'; +const config = require('./redis.datasource.json'); + +export class RedisDataSource extends juggler.DataSource { + static dataSourceName = 'redis'; + + constructor( + @inject('datasources.config.redis', {optional: true}) + dsConfig: AnyObject = config, + ) { + super(dsConfig); + } +} +``` + +To generate the datasource automatically, use `lb4 datasource` command and +select `Redis key-value connector (supported by StrongLoop)`. + +### Define a KeyValueRepository + +The KeyValueRepository binds a model such as `ShoppingCart` to the +`RedisDataSource`. The base `DefaultKeyValueRepository` class provides an +implementation based on `loopback-datasource-juggler`. + +```ts +import {DefaultKeyValueRepository} from '@loopback/repository'; +import {ShoppingCart} from '../models/shopping-cart.model'; +import {RedisDataSource} from '../datasources/redis.datasource'; +import {inject} from '@loopback/context'; + +export class ShoppingCartRepository extends DefaultKeyValueRepository< + ShoppingCart +> { + constructor(@inject('datasources.redis') ds: RedisDataSource) { + super(ShoppingCart, ds); + } +} +``` + +### Perform Key Value Operations + +The KeyValueRepository provides a set of key based operations, such as `set`, +`get`, `delete`, `expire`, `ttl`, and `keys`. See +[KeyValueRepository](https://github.com/strongloop/loopback-next/blob/master/packages/repository/src/repositories/kv.repository.ts) +for a complete list. + +```ts +// Please note the ShoppingCartRepository can be instantiated using Dependency +// Injection +const repo: ShoppingCartRepository = + new ShoppingCartRepository(new RedisDataSource()); +const cart1: ShoppingCart = givenShoppingCart1(); +const cart2: ShoppingCart = givenShoppingCart2(); + +async function testKV() { + // Store carts using userId as the key + await repo.set(cart1.userId, cart1); + await repo.set(cart2.userId, cart2); + + // Retrieve a cart by its key + const result = await repo.get(cart1.userId); + console.log(result); +}); + +testKV(); +``` + +## Persist Data without Juggler [Using MySQL database] {% include important.html content="This section has not been updated and code examples may not work out of the box. From ac011f80b229b5dd5c69851ab14b39705b852629 Mon Sep 17 00:00:00 2001 From: shimks Date: Tue, 7 Aug 2018 16:47:50 -0400 Subject: [PATCH 17/30] fix(rest): use strong-error-handler for writing errors to the response body --- packages/rest/package.json | 1 + packages/rest/src/keys.ts | 8 ++ .../rest/src/providers/reject.provider.ts | 18 +---- packages/rest/src/writer.ts | 37 --------- .../integration/http-handler.integration.ts | 78 +++++++++++++++---- 5 files changed, 74 insertions(+), 68 deletions(-) diff --git a/packages/rest/package.json b/packages/rest/package.json index 4ac590e13c63..95531a46d2c1 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -41,6 +41,7 @@ "lodash": "^4.17.5", "openapi-schema-to-json-schema": "^2.1.0", "path-to-regexp": "^2.2.0", + "strong-error-handler": "^3.2.0", "validator": "^10.4.0" }, "devDependencies": { diff --git a/packages/rest/src/keys.ts b/packages/rest/src/keys.ts index 77c94063ff8b..dd6867ec447b 100644 --- a/packages/rest/src/keys.ts +++ b/packages/rest/src/keys.ts @@ -24,6 +24,7 @@ import { import {HttpProtocol} from '@loopback/http-server'; import * as https from 'https'; +import {ErrorWriterOptions} from 'strong-error-handler'; /** * RestServer-specific bindings @@ -59,6 +60,13 @@ export namespace RestBindings { * Internal binding key for http-handler */ export const HANDLER = BindingKey.create('rest.handler'); + /** + * Binding key for setting and injecting Reject action's error handling + * options + */ + export const ERROR_WRITER_OPTIONS = BindingKey.create( + 'rest.errorWriterOptions', + ); /** * Binding key for setting and injecting an OpenAPI spec diff --git a/packages/rest/src/providers/reject.provider.ts b/packages/rest/src/providers/reject.provider.ts index bd990feaa422..5012c1bcfd01 100644 --- a/packages/rest/src/providers/reject.provider.ts +++ b/packages/rest/src/providers/reject.provider.ts @@ -6,15 +6,15 @@ import {LogError, Reject, HandlerContext} from '../types'; import {inject, Provider} from '@loopback/context'; import {HttpError} from 'http-errors'; -import {writeErrorToResponse} from '../writer'; import {RestBindings} from '../keys'; - -const debug = require('debug')('loopback:rest:reject'); +import {writeErrorToResponse, ErrorWriterOptions} from 'strong-error-handler'; export class RejectProvider implements Provider { constructor( @inject(RestBindings.SequenceActions.LOG_ERROR) protected logError: LogError, + @inject(RestBindings.ERROR_WRITER_OPTIONS, {optional: true}) + protected errorWriterOptions?: ErrorWriterOptions, ) {} value(): Reject { @@ -24,17 +24,7 @@ export class RejectProvider implements Provider { action({request, response}: HandlerContext, error: Error) { const err = error; const statusCode = err.statusCode || err.status || 500; - writeErrorToResponse(response, err); - - // Always log the error in debug mode, even when the application - // has a custom error logger configured (e.g. in tests) - debug( - 'HTTP request %s %s was rejected: %s %s', - request.method, - request.url, - statusCode, - err.stack || err, - ); + writeErrorToResponse(err, request, response, this.errorWriterOptions); this.logError(error, statusCode, request); } } diff --git a/packages/rest/src/writer.ts b/packages/rest/src/writer.ts index dae47ee18e43..b3e0cd4262ac 100644 --- a/packages/rest/src/writer.ts +++ b/packages/rest/src/writer.ts @@ -4,7 +4,6 @@ // License text available at https://opensource.org/licenses/MIT import {OperationRetval, Response} from './types'; -import {HttpError} from 'http-errors'; import {Readable} from 'stream'; /** @@ -51,39 +50,3 @@ export function writeResultToResponse( } response.end(); } - -/** - * Write an error into the HTTP response - * @param response HTTP response - * @param error Error - */ -export function writeErrorToResponse(response: Response, error: Error) { - const e = error; - const statusCode = (response.statusCode = e.statusCode || e.status || 500); - if (e.headers) { - // Set response headers for the error - for (const h in e.headers) { - response.setHeader(h, e.headers[h]); - } - } - // Build an error object - const errObj: Partial = { - statusCode, - }; - if (e.expose) { - // Expose other properties if the `expose` flag is set to `true` - for (const p in e) { - if ( - p === 'headers' || - p === 'expose' || - p === 'status' || - p === 'statusCode' - ) - continue; - errObj[p] = e[p]; - } - } - response.setHeader('Content-Type', 'application/json'); - response.write(JSON.stringify(errObj)); - response.end(); -} diff --git a/packages/rest/test/integration/http-handler.integration.ts b/packages/rest/test/integration/http-handler.integration.ts index 050326fa7901..7051367c0486 100644 --- a/packages/rest/test/integration/http-handler.integration.ts +++ b/packages/rest/test/integration/http-handler.integration.ts @@ -15,7 +15,7 @@ import { } from '../..'; import {ControllerSpec, get} from '@loopback/openapi-v3'; import {Context} from '@loopback/context'; -import {Client, createClientForHandler} from '@loopback/testlab'; +import {Client, createClientForHandler, expect} from '@loopback/testlab'; import * as HttpErrors from 'http-errors'; import {ParameterObject, RequestBodyObject} from '@loopback/openapi-v3-types'; import {anOpenApiSpec, anOperationSpec} from '@loopback/openapi-spec-builder'; @@ -248,9 +248,12 @@ describe('HttpHandler', () => { .post('/show-body') .send('key=value') .expect(415, { - message: - 'Content-type application/x-www-form-urlencoded is not supported.', - statusCode: 415, + error: { + message: + 'Content-type application/x-www-form-urlencoded is not supported.', + name: 'UnsupportedMediaTypeError', + statusCode: 415, + }, }); }); @@ -260,7 +263,13 @@ describe('HttpHandler', () => { .post('/show-body') .set('content-type', 'application/json') .send('malformed-json') - .expect(400, {statusCode: 400}); + .expect(400, { + error: { + message: 'Unexpected token m in JSON at position 0', + name: 'SyntaxError', + statusCode: 400, + }, + }); }); function givenBodyParamController() { @@ -329,7 +338,7 @@ describe('HttpHandler', () => { }); context('error handling', () => { - it('handles errors throws by controller constructor', () => { + it('handles errors thrown by controller constructor', () => { const spec = anOpenApiSpec() .withOperationReturningString('get', '/hello', 'greet') .build(); @@ -344,7 +353,10 @@ describe('HttpHandler', () => { logErrorsExcept(500); return client.get('/hello').expect(500, { - statusCode: 500, + error: { + message: 'Internal Server Error', + statusCode: 500, + }, }); }); @@ -363,8 +375,11 @@ describe('HttpHandler', () => { logErrorsExcept(404); await client.get('/hello').expect(404, { - message: 'Controller method not found: TestController.unknownMethod', - statusCode: 404, + error: { + message: 'Controller method not found: TestController.unknownMethod', + name: 'NotFoundError', + statusCode: 404, + }, }); }); @@ -380,22 +395,20 @@ describe('HttpHandler', () => { class TestController { @get('/hello') hello() { - const err = new HttpErrors.BadRequest('Bad hello'); - err.headers = {'X-BAD-REQ': 'hello'}; - throw err; + throw new HttpErrors.BadRequest('Bad hello'); } } givenControllerClass(TestController, spec); logErrorsExcept(400); - await client - .get('/hello') - .expect('X-BAD-REQ', 'hello') - .expect(400, { + await client.get('/hello').expect(400, { + error: { message: 'Bad hello', + name: 'BadRequestError', statusCode: 400, - }); + }, + }); }); it('handles 500 error thrown from the method', async () => { @@ -418,6 +431,37 @@ describe('HttpHandler', () => { logErrorsExcept(500); await client.get('/hello').expect(500, { + error: { + message: 'Internal Server Error', + statusCode: 500, + }, + }); + }); + + it('respects error handler options', async () => { + rootContext.bind(RestBindings.ERROR_WRITER_OPTIONS).to({debug: true}); + + const spec = anOpenApiSpec() + .withOperation( + 'get', + '/hello', + anOperationSpec().withOperationName('hello'), + ) + .build(); + + class TestController { + @get('/hello') + hello() { + throw new HttpErrors.InternalServerError('Bad hello'); + } + } + + givenControllerClass(TestController, spec); + logErrorsExcept(500); + + const response = await client.get('/hello').expect(500); + expect(response.body.error).to.containDeep({ + message: 'Bad hello', statusCode: 500, }); }); From c089299f464893129e643fc0bc5d829920f9a809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 31 Aug 2018 09:12:02 +0200 Subject: [PATCH 18/30] fix(cli): rename repository/service feature flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `--enableRepository` to `--repositories` (notice the plural form) - Rename `--enableServices` to `--services` This commit fixes the inconsistency where `--enableRepository` was using the singular form, while `--enableServices` was using the plural form. It also improves the feature prompt so that the word "enable" is not repeated twice. Before: ? Select project build settings: ❯◉ Enable tslint ◉ Enable prettier ◉ Enable mocha ◉ Enable loopbackBuild ◉ Enable vscode ◉ Enable enableRepository ◉ Enable enableServices After: ? Select features to enable in the project: ❯◉ Enable tslint ◉ Enable prettier ◉ Enable mocha ◉ Enable loopbackBuild ◉ Enable vscode ◉ Enable repositories ◉ Enable services --- docs/site/Getting-started.md | 9 ++++++++- .../soap-calculator-tutorial-scaffolding.md | 18 ++++++++++-------- docs/site/todo-tutorial-scaffolding.md | 4 +++- examples/log-extension/README.md | 4 ++-- packages/cli/generators/app/index.js | 16 ++++++++-------- .../app/templates/src/application.ts.ejs | 4 ++-- .../project/templates/package.json.ejs | 4 ++-- packages/cli/lib/project-generator.js | 2 +- 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/docs/site/Getting-started.md b/docs/site/Getting-started.md index 2996fca829dc..aebaa5673c77 100644 --- a/docs/site/Getting-started.md +++ b/docs/site/Getting-started.md @@ -41,7 +41,14 @@ Answer the prompts as follows: ? Project description: Getting started tutorial ? Project root directory: (getting-started) ? Application class name: StarterApplication -? Select project build settings: Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild, Enable vscode +? Select features to enable in the project: +❯◉ Enable tslint + ◉ Enable prettier + ◉ Enable mocha + ◉ Enable loopbackBuild + ◉ Enable vscode + ◉ Enable repositories + ◉ Enable services ``` ### Starting the project diff --git a/docs/site/soap-calculator-tutorial-scaffolding.md b/docs/site/soap-calculator-tutorial-scaffolding.md index 011dad75c97f..2a07b6defca9 100644 --- a/docs/site/soap-calculator-tutorial-scaffolding.md +++ b/docs/site/soap-calculator-tutorial-scaffolding.md @@ -12,13 +12,13 @@ Let's start by creating the initial application by running the following command: ```sh -lb4 app soap-calculator --enableRepository --enableServices +lb4 app soap-calculator --repositories --services ``` -**Note:** The option **--enableRepository** instructs the **CLI** to include a +**Note:** The option **--repositories** instructs the **CLI** to include a `RepositoryMixin` class in the application constructor which will be needed when -we create the datasource. The option **--enableServices** instructs the **CLI** -to include a `ServiceMixin` class in the application constructor which will be +we create the datasource. The option **--services** instructs the **CLI** to +include a `ServiceMixin` class in the application constructor which will be needed to register our SOAP service client. **LB4** will ask you a few questions _(you can leave the default options)_. The @@ -33,20 +33,22 @@ application.ts file. ``` Next you will see a list of options for the build settings, if you did not -specify `--enableRepository` and `--enableServices` in the last command, then -you will see them in this list, make sure you enable both the repository and the -services for the application. +specify `--repositories` and `--services` in the last command, then you will see +them in this list, make sure you enable both the repository and the services for +the application. **Note:** Enable all options, unless you know what you are doing, see [The Getting Started guide](Getting-started.md) for more information. ```sh -? Select project build settings: (Press to select, to toggle all, to invert selection) +? Select features to enable in the project: ❯◉ Enable tslint ◉ Enable prettier ◉ Enable mocha ◉ Enable loopbackBuild ◉ Enable vscode + ◉ Enable repositories + ◉ Enable services ``` #### Run the Application diff --git a/docs/site/todo-tutorial-scaffolding.md b/docs/site/todo-tutorial-scaffolding.md index b7cda4ad76aa..15c1868bfc92 100644 --- a/docs/site/todo-tutorial-scaffolding.md +++ b/docs/site/todo-tutorial-scaffolding.md @@ -22,12 +22,14 @@ $ lb4 app ? Project description: A todo list API made with LoopBack 4. ? Project root directory: (todo-list) ? Application class name: (TodoListApplication) -? Select project build settings: (Press to select, to toggle all, to inverse selection) +? Select features to enable in the project: ❯◉ Enable tslint ◉ Enable prettier ◉ Enable mocha ◉ Enable loopbackBuild ◉ Enable vscode + ◉ Enable repositories + ◉ Enable services # npm will install dependencies now Application todo-list was created in todo-list. ``` diff --git a/examples/log-extension/README.md b/examples/log-extension/README.md index 193dfc2e78bc..86342b910ace 100644 --- a/examples/log-extension/README.md +++ b/examples/log-extension/README.md @@ -76,8 +76,8 @@ Initialize your new extension project as follows: `lb4 extension` - Project description: `An example extension project for LoopBack 4` - Project root directory: `(loopback4-example-log-extension)` - Component class name: `LogComponent` -- Select project build settings: - `Enable tslint, Enable prettier, Enable mocha, Enable loopbackBuild` +- Select features to enable in the project': `tslint`, `prettier`, `mocha`, + `loopbackBuild` Now you can write the extension as follows: diff --git a/packages/cli/generators/app/index.js b/packages/cli/generators/app/index.js index 81e0bac3e683..b70bec7913ed 100644 --- a/packages/cli/generators/app/index.js +++ b/packages/cli/generators/app/index.js @@ -11,8 +11,8 @@ module.exports = class AppGenerator extends ProjectGenerator { // Note: arguments and options should be defined in the constructor. constructor(args, opts) { super(args, opts); - this.buildOptions.push('enableRepository'); - this.buildOptions.push('enableServices'); + this.buildOptions.push('repositories'); + this.buildOptions.push('services'); } _setupGenerator() { @@ -23,12 +23,12 @@ module.exports = class AppGenerator extends ProjectGenerator { description: 'Application class name', }); - this.option('enableRepository', { + this.option('repositories', { type: Boolean, description: 'Include repository imports and RepositoryMixin', }); - this.option('enableServices', { + this.option('services', { type: Boolean, description: 'Include service-proxy imports and ServiceMixin', }); @@ -88,14 +88,14 @@ module.exports = class AppGenerator extends ProjectGenerator { buildAppClassMixins() { if (this.shouldExit()) return false; - const {enableRepository, enableServices} = this.projectInfo || {}; - if (!enableRepository && !enableServices) return; + const {repositories, services} = this.projectInfo || {}; + if (!repositories && !services) return; let appClassWithMixins = 'RestApplication'; - if (enableRepository) { + if (repositories) { appClassWithMixins = `RepositoryMixin(${appClassWithMixins})`; } - if (enableServices) { + if (services) { appClassWithMixins = `ServiceMixin(${appClassWithMixins})`; } diff --git a/packages/cli/generators/app/templates/src/application.ts.ejs b/packages/cli/generators/app/templates/src/application.ts.ejs index 4f48132b77fe..5dd1b5c7d8a6 100644 --- a/packages/cli/generators/app/templates/src/application.ts.ejs +++ b/packages/cli/generators/app/templates/src/application.ts.ejs @@ -1,10 +1,10 @@ import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; -<% if (project.enableRepository) { -%> +<% if (project.repositories) { -%> import {RepositoryMixin} from '@loopback/repository'; <% } -%> import {RestApplication} from '@loopback/rest'; -<% if (project.enableServices) { -%> +<% if (project.services) { -%> import {ServiceMixin} from '@loopback/service-proxy'; <% } -%> import {MySequence} from './sequence'; diff --git a/packages/cli/generators/project/templates/package.json.ejs b/packages/cli/generators/project/templates/package.json.ejs index bd13fcf193ea..898f1700a5a8 100644 --- a/packages/cli/generators/project/templates/package.json.ejs +++ b/packages/cli/generators/project/templates/package.json.ejs @@ -78,10 +78,10 @@ "@loopback/core": "<%= project.dependencies['@loopback/core'] -%>", "@loopback/dist-util": "<%= project.dependencies['@loopback/dist-util'] -%>", "@loopback/openapi-v3": "<%= project.dependencies['@loopback/openapi-v3'] -%>", -<% if (project.enableRepository) { -%> +<% if (project.repositories) { -%> "@loopback/repository": "<%= project.dependencies['@loopback/repository'] -%>", <% } -%> -<% if (project.enableServices) { -%> +<% if (project.services) { -%> "@loopback/rest": "<%= project.dependencies['@loopback/rest'] -%>", "@loopback/service-proxy": "<%= project.dependencies['@loopback/service-proxy'] -%>" <% } else { -%> diff --git a/packages/cli/lib/project-generator.js b/packages/cli/lib/project-generator.js index 72a0973da8c8..3c5cf7024e32 100644 --- a/packages/cli/lib/project-generator.js +++ b/packages/cli/lib/project-generator.js @@ -169,7 +169,7 @@ module.exports = class ProjectGenerator extends BaseGenerator { const prompts = [ { name: 'settings', - message: 'Select project build settings: ', + message: 'Select features to enable in the project', type: 'checkbox', choices: choices, // Skip if all features are enabled by cli options From 5c3396294fb15fed4e2ed22dc801f19b2fadb2d7 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 26 Aug 2018 11:02:44 -0700 Subject: [PATCH 19/30] fix(docs): fix typo --- docs/site/Defining-the-API-using-code-first-approach.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/Defining-the-API-using-code-first-approach.md b/docs/site/Defining-the-API-using-code-first-approach.md index c79df960e2b6..400b79a70b9f 100644 --- a/docs/site/Defining-the-API-using-code-first-approach.md +++ b/docs/site/Defining-the-API-using-code-first-approach.md @@ -21,7 +21,7 @@ There are various tools available to LoopBack which allows this bottom-up approach of building your application to be simple through the usages of metadata and decorators. -### Start with LoopBack artfiacts +### Start with LoopBack artifacts With TypeScript's [experimental decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) From b18e95f7fb2f01993da6e9093def70cd01e454c1 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 30 Aug 2018 11:05:25 -0700 Subject: [PATCH 20/30] fix(docs): fix typo in Repositories.md --- docs/site/Repositories.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index c8d2c38e203e..0558cafe8b2b 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -299,7 +299,7 @@ We can now access key-value stores such as [Redis](https://redis.io/) using the ### Define a KeyValue Datasource We first need to define a datasource to configure the key-value store. For -better flexibility, we spilt the datasource definition into two files. The json +better flexibility, we split the datasource definition into two files. The json file captures the configuration properties and it can be possibly overridden by dependency injection. From a1cefccea1583aa1889d60b15088b85cb5e5d190 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 15 Aug 2018 09:09:22 -0700 Subject: [PATCH 21/30] feat(rest): allow static assets to be served by a rest server --- docs/site/Application.md | 31 ++++- packages/rest/src/rest.application.ts | 15 +++ packages/rest/src/rest.server.ts | 37 ++++++ .../rest/test/integration/fixtures/index.html | 8 ++ .../integration/rest.server.integration.ts | 107 +++++++++++++++++- 5 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 packages/rest/test/integration/fixtures/index.html diff --git a/docs/site/Application.md b/docs/site/Application.md index 8a8e83178de7..de164929f50d 100644 --- a/docs/site/Application.md +++ b/docs/site/Application.md @@ -206,10 +206,39 @@ This means you can call these `RestServer` functions to do all of your server-level setups in the app constructor without having to explicitly retrieve an instance of your server. +### Serve static files + +The `RestServer` allows static files to be served. It can be set up by calling +the `static()` API. + +```ts +app.static('/html', rootDirForHtml); +``` + +or + +```ts +server.static(['/html', '/public'], rootDirForHtml); +``` + +Static assets are not allowed to be mounted on `/` to avoid performance penalty +as `/` matches all paths and incurs file system access for each HTTP request. + +The static() API delegates to +[serve-static](https://expressjs.com/en/resources/middleware/serve-static.html) +to serve static files. Please see +https://expressjs.com/en/starter/static-files.html and +https://expressjs.com/en/4x/api.html#express.static for details. + +**WARNING**: + +> The static assets are served before LoopBack sequence of actions. If an error +> is thrown, the `reject` action will NOT be triggered. + ### Use unique bindings Use binding names that are prefixed with a unique string that does not overlap -with loopback's bindings. As an example, if your application is built for your +with LoopBack's bindings. As an example, if your application is built for your employer FooCorp, you can prefix your bindings with `fooCorp`. ```ts diff --git a/packages/rest/src/rest.application.ts b/packages/rest/src/rest.application.ts index a74c53928ea8..8b979a5a3708 100644 --- a/packages/rest/src/rest.application.ts +++ b/packages/rest/src/rest.application.ts @@ -16,6 +16,8 @@ import { ControllerFactory, } from './router/routing-table'; import {OperationObject, OpenApiSpec} from '@loopback/openapi-v3-types'; +import {ServeStaticOptions} from 'serve-static'; +import {PathParams} from 'express-serve-static-core'; export const ERR_NO_MULTI_SERVER = format( 'RestApplication does not support multiple servers!', @@ -83,6 +85,19 @@ export class RestApplication extends Application implements HttpServerLike { this.restServer.handler(handlerFn); } + /** + * Mount static assets to the REST server. + * See https://expressjs.com/en/4x/api.html#express.static + * @param path The path(s) to serve the asset. + * See examples at https://expressjs.com/en/4x/api.html#path-examples + * To avoid performance penalty, `/` is not allowed for now. + * @param rootDir The root directory from which to serve static assets + * @param options Options for serve-static + */ + static(path: PathParams, rootDir: string, options?: ServeStaticOptions) { + this.restServer.static(path, rootDir, options); + } + /** * Register a new Controller-based route. * diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 9b48012a38ba..64258e2b30d7 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -35,6 +35,9 @@ import { import {RestBindings} from './keys'; import {RequestContext} from './request-context'; import * as express from 'express'; +import {ServeStaticOptions} from 'serve-static'; +import {PathParams} from 'express-serve-static-core'; +import * as pathToRegExp from 'path-to-regexp'; const debug = require('debug')('loopback:rest:server'); @@ -131,6 +134,7 @@ export class RestServer extends Context implements Server, HttpServerLike { protected _httpServer: HttpServer | undefined; protected _expressApp: express.Application; + protected _routerForStaticAssets: express.Router; get listening(): boolean { return this._httpServer ? this._httpServer.listening : false; @@ -196,6 +200,9 @@ export class RestServer extends Context implements Server, HttpServerLike { }; this._expressApp.use(cors(corsOptions)); + // Place the assets router here before controllers + this._setupRouterForStaticAssets(); + // Mount our router & request handler this._expressApp.use((req, res, next) => { this._handleHttpRequest(req, res, options!).catch(next); @@ -209,6 +216,17 @@ export class RestServer extends Context implements Server, HttpServerLike { ); } + /** + * Set up an express router for all static assets so that middleware for + * all directories are invoked at the same phase + */ + protected _setupRouterForStaticAssets() { + if (!this._routerForStaticAssets) { + this._routerForStaticAssets = express.Router(); + this._expressApp.use(this._routerForStaticAssets); + } + } + protected _handleHttpRequest( request: Request, response: Response, @@ -513,6 +531,25 @@ export class RestServer extends Context implements Server, HttpServerLike { ); } + /** + * Mount static assets to the REST server. + * See https://expressjs.com/en/4x/api.html#express.static + * @param path The path(s) to serve the asset. + * See examples at https://expressjs.com/en/4x/api.html#path-examples + * To avoid performance penalty, `/` is not allowed for now. + * @param rootDir The root directory from which to serve static assets + * @param options Options for serve-static + */ + static(path: PathParams, rootDir: string, options?: ServeStaticOptions) { + const re = pathToRegExp(path, [], {end: false}); + if (re.test('/')) { + throw new Error( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + } + this._routerForStaticAssets.use(path, express.static(rootDir, options)); + } + /** * Set the OpenAPI specification that defines the REST API schema for this * server. All routes, parameter definitions and return types will be defined diff --git a/packages/rest/test/integration/fixtures/index.html b/packages/rest/test/integration/fixtures/index.html new file mode 100644 index 000000000000..75283a67d708 --- /dev/null +++ b/packages/rest/test/integration/fixtures/index.html @@ -0,0 +1,8 @@ + +
+ Test Page +
+ +

Hello, World!

+ + diff --git a/packages/rest/test/integration/rest.server.integration.ts b/packages/rest/test/integration/rest.server.integration.ts index 4b78e22d9f22..fe596f18e7da 100644 --- a/packages/rest/test/integration/rest.server.integration.ts +++ b/packages/rest/test/integration/rest.server.integration.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Application, ApplicationConfig} from '@loopback/core'; +import {Application} from '@loopback/core'; import { supertest, expect, @@ -17,6 +17,7 @@ import {IncomingMessage, ServerResponse} from 'http'; import * as yaml from 'js-yaml'; import * as path from 'path'; import * as fs from 'fs'; +import {RestServerConfig} from '../../src'; describe('RestServer (integration)', () => { it('exports url property', async () => { @@ -77,6 +78,108 @@ describe('RestServer (integration)', () => { .expect(500); }); + it('does not allow static assets to be mounted at /', async () => { + const root = path.join(__dirname, 'fixtures'); + const server = await givenAServer({ + rest: { + port: 0, + }, + }); + + expect(() => server.static('/', root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + + expect(() => server.static('', root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + + expect(() => server.static(['/'], root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + + expect(() => server.static(['/html', ''], root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + + expect(() => server.static(/.*/, root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + + expect(() => server.static('/(.*)', root)).to.throw( + 'Static assets cannot be mount to "/" to avoid performance penalty.', + ); + }); + + it('allows static assets via api', async () => { + const root = path.join(__dirname, 'fixtures'); + const server = await givenAServer({ + rest: { + port: 0, + }, + }); + + server.static('/html', root); + const content = fs + .readFileSync(path.join(root, 'index.html')) + .toString('utf-8'); + await createClientForHandler(server.requestHandler) + .get('/html/index.html') + .expect('Content-Type', /text\/html/) + .expect(200, content); + }); + + it('allows static assets via api after start', async () => { + const root = path.join(__dirname, 'fixtures'); + const server = await givenAServer({ + rest: { + port: 0, + }, + }); + await createClientForHandler(server.requestHandler) + .get('/html/index.html') + .expect(404); + + server.static('/html', root); + + await createClientForHandler(server.requestHandler) + .get('/html/index.html') + .expect(200); + }); + + it('allows non-static routes after assets', async () => { + const root = path.join(__dirname, 'fixtures'); + const server = await givenAServer({ + rest: { + port: 0, + }, + }); + server.static('/html', root); + server.handler(dummyRequestHandler); + + await createClientForHandler(server.requestHandler) + .get('/html/does-not-exist.html') + .expect(200, 'Hello'); + }); + + it('serve static assets if matches before other routes', async () => { + const root = path.join(__dirname, 'fixtures'); + const server = await givenAServer({ + rest: { + port: 0, + }, + }); + server.static('/html', root); + server.handler(dummyRequestHandler); + + const content = fs + .readFileSync(path.join(root, 'index.html')) + .toString('utf-8'); + await createClientForHandler(server.requestHandler) + .get('/html/index.html') + .expect(200, content); + }); + it('allows cors', async () => { const server = await givenAServer({rest: {port: 0}}); server.handler(dummyRequestHandler); @@ -369,7 +472,7 @@ servers: await server.stop(); }); - async function givenAServer(options?: ApplicationConfig) { + async function givenAServer(options?: {rest: RestServerConfig}) { const app = new Application(options); app.component(RestComponent); return await app.getServer(RestServer); From 8e5638dcc82e2af3fb70bc63948399add1330c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Fri, 31 Aug 2018 10:25:15 +0200 Subject: [PATCH 22/30] docs(rest): describe HTTP error responses & handler options --- docs/site/Sequence.md | 87 +++++++++++++++++++++++++++++++++++++-- packages/rest/src/keys.ts | 6 ++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/docs/site/Sequence.md b/docs/site/Sequence.md index 11b053404fba..61e0a775220c 100644 --- a/docs/site/Sequence.md +++ b/docs/site/Sequence.md @@ -246,11 +246,92 @@ page [Parsing requests](Parsing-requests.md) - Must call `sendResponse()` exactly once - Streams? -### Sending errors +### Handling errors + +There are many reasons why the application may not be able to handle an incoming +request: + +- The requested endpoint (method + URL path) was not found. +- Parameters provided by the client were not valid. +- A backend database or a service cannot be reached. +- The response object cannot be converted to JSON because of cyclic + dependencies. +- A programmer made a mistake and a `TypeError` is thrown by the runtime. +- And so on. + +In the Sequence implementation described above, all errors are handled by a +single `catch` block at the end of the sequence, using the Sequence Action +called `reject`. + +The default implementation of `reject` does the following steps: + +- Call + [strong-error-handler](https://github.com/strongloop/strong-error-handler) to + send back an HTTP response describing the error. +- Log the error to `stderr` if the status code was 5xx (an internal server + error, not a bad request). + +To prevent the application from leaking sensitive information like filesystem +paths and server addresses, the error handler is configured to hide error +details. + +- For 5xx errors, the output contains only the status code and the status name + from the HTTP specification. For example: + + ```json + { + "error": { + "statusCode": 500, + "message": "Internal Server Error" + } + } + ``` + +- For 4xx errors, the output contains the full error message (`error.message`) + and the contents of the `details` property (`error.details`) that + `ValidationError` typically uses to provide machine-readable details about + validation problems. It also includes `error.code` to allow a machine-readable + error code to be passed through which could be used, for example, for + translation. + + ```json + { + "error": { + "statusCode": 422, + "name": "Unprocessable Entity", + "message": "Missing required fields", + "code": "MISSING_REQUIRED_FIELDS" + } + } + ``` -{% include content/tbd.html %} +During development and testing, it may be useful to see all error details in the +HTTP responsed returned by the server. This behavior can be enabled by enabling +the `debug` flag in error-handler configuration as shown in the code example +below. See strong-error-handler +[docs](https://github.com/strongloop/strong-error-handler#options) for a list of +all available options. -- try/catch details +```ts +app.bind(RestBindings.ERROR_WRITER_OPTIONS).to({debug: true}); +``` + +An example error message when the debug mode is enabled: + +```json +{ + "error": { + "statusCode": 500, + "name": "Error", + "message": "ENOENT: no such file or directory, open '/etc/passwords'", + "errno": -2, + "syscall": "open", + "code": "ENOENT", + "path": "/etc/passwords", + "stack": "Error: a test error message\n at Object.openSync (fs.js:434:3)\n at Object.readFileSync (fs.js:339:35)" + } +} +``` ### Keeping your Sequences diff --git a/packages/rest/src/keys.ts b/packages/rest/src/keys.ts index dd6867ec447b..647cedaa7de9 100644 --- a/packages/rest/src/keys.ts +++ b/packages/rest/src/keys.ts @@ -62,7 +62,11 @@ export namespace RestBindings { export const HANDLER = BindingKey.create('rest.handler'); /** * Binding key for setting and injecting Reject action's error handling - * options + * options. + * + * See https://github.com/strongloop/strong-error-handler#options for + * the list of available options. Please note that the flag `log` is not used + * by `@loopback/rest`. */ export const ERROR_WRITER_OPTIONS = BindingKey.create( 'rest.errorWriterOptions', From 2d98873f5070b0664db8522b3c101e40bc31ae13 Mon Sep 17 00:00:00 2001 From: bschrammIBM Date: Thu, 30 Aug 2018 13:21:42 -0700 Subject: [PATCH 23/30] docs: add oneliners and links to tutorials per 1444 --- docs/site/Model.md | 39 +++++++++++++-------------- docs/site/Repositories.md | 6 ++--- docs/site/todo-tutorial-controller.md | 7 +++++ docs/site/todo-tutorial-datasource.md | 15 ++++++++--- docs/site/todo-tutorial-model.md | 11 +++++++- docs/site/todo-tutorial-repository.md | 7 +++++ 6 files changed, 57 insertions(+), 28 deletions(-) diff --git a/docs/site/Model.md b/docs/site/Model.md index 172cf82933aa..8ac8ea060d2a 100644 --- a/docs/site/Model.md +++ b/docs/site/Model.md @@ -18,15 +18,15 @@ Models can be used for data exchange on the wire or between different systems. For example, a JSON object conforming to the `Customer` model definition can be passed in REST/HTTP payload to create a new customer or stored in a document database such as MongoDB. Model definitions can also be mapped to other forms, -such as relational database schema, XML schema, JSON schema, OpenAPI schema, or -gRPC message definition, and vice versa. +such as relational database schemas, XML schemas, JSON schemas, OpenAPI schemas, +or gRPC message definitions, and vice versa. There are two subtly different types of models for domain objects: - Value Object: A domain object that does not have an identity (ID). Its equality is based on the structural value. For example, `Address` can be - modeled as `Value Object` as two US addresses are equal if they have the same - street number, street name, city, and zip code values. For example: + modeled as a `Value Object` because two US addresses are equal if they have + the same street number, street name, city, and zip code values. For example: ```json { @@ -41,10 +41,9 @@ There are two subtly different types of models for domain objects: ``` - Entity: A domain object that has an identity (ID). Its equality is based on - the identity. For example, `Customer` can be modeled as `Entity` as each - customer should have a unique customer id. Two instances of `Customer` with - the same customer id are equal since they refer to the same customer. For - example: + the identity. For example, `Customer` can be modeled as an `Entity` because + each customer has a unique customer id. Two instances of `Customer` with the + same customer id are equal since they refer to the same customer. For example: ```json { @@ -77,7 +76,7 @@ export class Customer { ``` Extensibility is a core feature of LoopBack. There are external packages that -add additional features, for example, integration with the legacy juggler or +add additional features, for example, integration with the juggler bridge or JSON Schema generation. These features become available to a LoopBack model through the `@model` and `@property` decorators from the `@loopback/repository` module. @@ -96,9 +95,9 @@ export class Customer { } ``` -## Using Legacy Juggler +## Using the Juggler Bridge -To define a model for use with the legacy juggler, extend your classes from +To define a model for use with the juggler bridge, extend your classes from `Entity` and decorate them with the `@model` and `@property` decorators. ```ts @@ -124,10 +123,10 @@ export class Product extends Entity { } ``` -As you can see, models are defined primarily by their TypeScript class. By -default, classes forbid additional properties not specified in the type -definition. The persistence layer is respecting this constraint and configures -underlying PersistedModel classes to enforce `strict` mode. +Models are defined primarily by their TypeScript class. By default, classes +forbid additional properties that are not specified in the type definition. The +persistence layer respects this constraint and configures underlying +PersistedModel classes to enforce `strict` mode. To create a model that allows both well-defined but also arbitrary extra properties, you need to disable `strict` mode in model settings and tell @@ -193,7 +192,7 @@ class Product extends Entity { Additionally, the model decorator is able to build the properties object through the information passed in or inferred by the property decorators, so the -properties key value pair can be omitted as well by using property decorators. +properties key value pair can also be omitted. ### Property Decorator @@ -238,7 +237,7 @@ LoopBack, due to the nature of arrays in JavaScript. In JavaScript, arrays do not possess any information about the types of their members. By traversing an array, you can inspect the members of an array to determine if they are of a primitive type (string, number, array, boolean), object or function, but this -would not tell us anything about what the value would be if it were an object or +does not tell you anything about what the value would be if it were an object or function. For consistency, we require the use of the `@property.array` decorator, which @@ -259,7 +258,7 @@ class Thread extends Entity { } ``` -Additionally, the `@property.array` decorator can still take an optional 2nd +Additionally, the `@property.array` decorator can still take an optional second parameter to define or override metadata in the same fashion as the `@property` decorator. @@ -280,7 +279,7 @@ Use the `@loopback/repository-json-schema module` to build a JSON schema from a decorated model. Type information is inferred from the `@model` and `@property` decorators. The `@loopback/repository-json-schema` module contains the `getJsonSchema` function to access the metadata stored by the decorators to -build a matching JSON Schema of your model. +build a matching JSON schema of your model. ```ts import {model, property} from '@loopback/repository'; @@ -346,7 +345,7 @@ This feature is still a work in progress and is incomplete. " %} Following are the supported keywords that can be explicitly passed into the -decorators to better tailor towards the JSON Schema being produced. +decorators to better tailor towards the JSON Schema being produced: | Keywords | Decorator | Type | Default | Description | | ----------- | ----------- | ------- | ------------ | ------------------------------------------------------- | diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index 0558cafe8b2b..5e9db032e5b3 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -6,12 +6,12 @@ sidebar: lb4_sidebar permalink: /doc/en/lb4/Repositories.html --- -`Repository` represents a specialized `Service` interface that provides +A `Repository` represents a specialized `Service` interface that provides strong-typed data access (for example, CRUD) operations of a domain model against the underlying database or service. -`Repository` can be defined and implemented by application developers. LoopBack -ships a few predefined `Repository` interfaces for typical CRUD and KV +A `Repository` can be defined and implemented by application developers. +LoopBack ships a few predefined `Repository` interfaces for typical CRUD and KV operations. These `Repository` implementations leverage `Model` definition and `DataSource` configuration to fulfill the logic for data access. diff --git a/docs/site/todo-tutorial-controller.md b/docs/site/todo-tutorial-controller.md index e5e6759777e8..2cc7626481c3 100644 --- a/docs/site/todo-tutorial-controller.md +++ b/docs/site/todo-tutorial-controller.md @@ -14,9 +14,16 @@ Each function on a controller can be addressed individually to handle an incoming request (like a POST request to `/todos`), to perform business logic, and to return a response. +`Controller` is a class that implements operations defined by application's API. +It implements an application's business logic and acts as a bridge between the +HTTP/REST API and domain/database models. + In this respect, controllers are the regions _in which most of your business logic will live_! +For more information about Controllers, see +[Controllers](https://loopback.io/doc/en/lb4/Controllers.html). + ### Create your controller So, let's create a controller to handle our Todo routes. You can create an empty diff --git a/docs/site/todo-tutorial-datasource.md b/docs/site/todo-tutorial-datasource.md index 15690fae9151..2ac891aeee17 100644 --- a/docs/site/todo-tutorial-datasource.md +++ b/docs/site/todo-tutorial-datasource.md @@ -10,12 +10,19 @@ summary: LoopBack 4 Todo Application Tutorial - Add a Datasource ### Datasources Datasources are LoopBack's way of connecting to various sources of data, such as -databases, APIs, message queues and more. In LoopBack 4, datasources can be -represented as strongly-typed objects and freely made available for -[injection](Dependency-injection.md) throughout the application. Typically, in -LoopBack 4, datasources are used in conjunction with +databases, APIs, message queues and more. A `DataSource` in LoopBack 4 is a +named configuration for a Connector instance that represents data in an external +system. The Connector is used by `legacy-juggler-bridge` to power LoopBack 4 +Repositories for Data operations. + +In LoopBack 4, datasources can be represented as strongly-typed objects and +freely made available for [injection](Dependency-injection.md) throughout the +application. Typically, in LoopBack 4, datasources are used in conjunction with [Repositories](Repositories.md) to provide access to data. +For more information about datasources in LoopBack, see +[DataSources](https://loopback.io/doc/en/lb4/DataSources.html). + Since our Todo API will need to persist instances of Todo items, we'll need to create a datasource definition to make this possible. diff --git a/docs/site/todo-tutorial-model.md b/docs/site/todo-tutorial-model.md index 6b9b5cfaab5f..c37e3cfa100f 100644 --- a/docs/site/todo-tutorial-model.md +++ b/docs/site/todo-tutorial-model.md @@ -16,6 +16,14 @@ instances of a task for our Todo list. The Todo model will serve both as a known as a DTO) for representing incoming Todo instances on requests, as well as our data structure for use with loopback-datasource-juggler. +A model describes business domain objects and defines a list of properties with +name, type, and other constraints. + +Models are used for data exchange on the wire or between different systems. + +For more information about Models and how they are used in LoopBack, see +[Models](https://loopback.io/doc/en/lb4/todo-tutorial-model.html). + > **NOTE:** LoopBack 3 treated models as the "center" of operations; in LoopBack > 4, that is no longer the case. While LoopBack 4 provides many of the helper > methods and decorators that allow you to utilize models in a similar way, you @@ -28,7 +36,8 @@ let you label tasks so that you can distinguish between them, add extra information to describe those tasks, and finally, provide a way of tracking whether or not they're complete. -For our Todo model to represent our Todo instances, it will need: +For our Todo model to represent our Todo instances for our business domain, it +will need: - a unique id - a title diff --git a/docs/site/todo-tutorial-repository.md b/docs/site/todo-tutorial-repository.md index 2297ba48badd..42c8be7c4ae7 100644 --- a/docs/site/todo-tutorial-repository.md +++ b/docs/site/todo-tutorial-repository.md @@ -15,6 +15,13 @@ themselves to perform CRUD operations. In LoopBack 4, the layer responsible for this has been separated from the definition of the model itself, into the repository layer. +A `Repository` represents a specialized `Service` interface that provides +strong-typed data access (for example, CRUD) operations of a domain model +against the underlying database or service. + +For more information about Repositories, see +[Repositories](https://loopback.io/doc/en/lb4/Repositories.html). + ### Create your repository In the `src/repositories` directory, create two files: From d0956931eceee89ec669e8665ecc91ea1897cec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 4 Sep 2018 13:57:33 +0200 Subject: [PATCH 24/30] feat: coerce object arguments from query strings Introduce a new decorator `@param.query.object` allowing controller methods to describe a parameter as an object and optionally provide the schema for the accepted values. Improve `parseParams` action to correctly parse and coerce object values coming from query strings, supporting the following two flavours: "deepObject" encoding as described by OpenAPI Spec v3: GET /api/products?filter[where][name]=Pen&filter[limit]=10 JSON-based encoding for compatibility with LoopBack 3.x: GET /api/products?filter={"where":{"name":"Pen"},"limit":10} --- docs/site/Parsing-requests.md | 31 ++++ packages/openapi-v3-types/package.json | 2 +- .../src/decorators/parameter.decorator.ts | 23 +++ .../param/param-query.decorator.unit.ts | 49 ++++++ packages/rest/package.json | 4 + .../rest/src/coercion/coerce-parameter.ts | 52 +++++- packages/rest/src/coercion/utils.ts | 13 +- packages/rest/src/coercion/validator.ts | 9 +- packages/rest/src/parser.ts | 46 ++++-- packages/rest/src/rest-http-error.ts | 12 +- packages/rest/src/rest.server.ts | 52 +++--- .../coercion/coercion.acceptance.ts | 47 +++++- .../test/unit/coercion/paramObject.unit.ts | 149 ++++++++++++++++++ packages/rest/test/unit/coercion/utils.ts | 66 ++++++-- packages/rest/test/unit/parser.unit.ts | 88 +++++++++++ 15 files changed, 581 insertions(+), 62 deletions(-) create mode 100644 packages/rest/test/unit/coercion/paramObject.unit.ts diff --git a/docs/site/Parsing-requests.md b/docs/site/Parsing-requests.md index bfbc0972329f..7c71f1861e4c 100644 --- a/docs/site/Parsing-requests.md +++ b/docs/site/Parsing-requests.md @@ -78,6 +78,36 @@ async replaceTodo( } ``` +#### Object values + +OpenAPI specification describes several ways how to encode object values into a +string, see +[Style Values](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-values) +and +[Style Examples](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-examples). + +At the moment, LoopBack supports object values for parameters in query strings +with `style: "deepObject"` only. Please note that this style does not preserve +encoding of primitive types, numbers and booleans are always parsed as strings. + +For example: + +``` +GET /todos?filter[where][completed]=false +// filter={where: {completed: 'false'}} +``` + +As an extension to the deep-object encoding described by OpenAPI, when the +parameter is specified with `style: "deepObject"`, we allow clients to provide +the object value as a JSON-encoded string too. + +For example: + +``` +GET /todos?filter={"where":{"completed":false}} +// filter={where: {completed: false}} +``` + ### Validation Validations are applied on the parameters and the request body data. They also @@ -107,6 +137,7 @@ Here are our default validation rules for each type: [RFC3339](https://xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14). - boolean: after converted to all upper case, should be one of the following values: `TRUE`, `1`, `FALSE` or `0`. +- object: should be a plain data object, not an array. #### Request Body diff --git a/packages/openapi-v3-types/package.json b/packages/openapi-v3-types/package.json index 4ba30e0430d8..907ecc6f7e34 100644 --- a/packages/openapi-v3-types/package.json +++ b/packages/openapi-v3-types/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@loopback/dist-util": "^0.3.6", - "openapi3-ts": "^0.11.0" + "openapi3-ts": "^1.0.0" }, "devDependencies": { "@loopback/build": "^0.7.1", diff --git a/packages/openapi-v3/src/decorators/parameter.decorator.ts b/packages/openapi-v3/src/decorators/parameter.decorator.ts index 59368de088bc..74478ee9dd29 100644 --- a/packages/openapi-v3/src/decorators/parameter.decorator.ts +++ b/packages/openapi-v3/src/decorators/parameter.decorator.ts @@ -204,6 +204,29 @@ export namespace param { * @param name Parameter name. */ password: createParamShortcut('query', builtinTypes.password), + + /** + * Define a parameter accepting an object value encoded + * - as a JSON string, e.g. `filter={"where":{"id":1}}`); or + * - in multiple nested keys, e.g. `filter[where][id]=1` + * + * @param name Parameter name + * @param schema Optional OpenAPI Schema describing the object value. + */ + object: function( + name: string, + schema: SchemaObject | ReferenceObject = { + type: 'object', + additionalProperties: true, + }, + ) { + return param({ + name, + in: 'query', + style: 'deepObject', + schema, + }); + }, }; export const header = { diff --git a/packages/openapi-v3/test/unit/decorators/param/param-query.decorator.unit.ts b/packages/openapi-v3/test/unit/decorators/param/param-query.decorator.unit.ts index 0b93ba7aa807..8ccf836e08bd 100644 --- a/packages/openapi-v3/test/unit/decorators/param/param-query.decorator.unit.ts +++ b/packages/openapi-v3/test/unit/decorators/param/param-query.decorator.unit.ts @@ -5,6 +5,7 @@ import {get, param, getControllerSpec} from '../../../..'; import {expect} from '@loopback/testlab'; +import {ParameterObject} from '@loopback/openapi-v3-types'; describe('Routing metadata for parameters', () => { describe('@param.query.string', () => { @@ -219,6 +220,54 @@ describe('Routing metadata for parameters', () => { expectSpecToBeEqual(MyController, expectedParamSpec); }); }); + + describe('@param.query.object', () => { + it('sets in:query style:deepObject and a default schema', () => { + class MyController { + @get('/greet') + greet(@param.query.object('filter') filter: Object) {} + } + const expectedParamSpec = { + name: 'filter', + in: 'query', + style: 'deepObject', + schema: { + type: 'object', + additionalProperties: true, + }, + }; + expectSpecToBeEqual(MyController, expectedParamSpec); + }); + + it('supports user-defined schema', () => { + class MyController { + @get('/greet') + greet( + @param.query.object('filter', { + type: 'object', + properties: { + where: {type: 'object', additionalProperties: true}, + limit: {type: 'number'}, + }, + }) + filter: Object, + ) {} + } + const expectedParamSpec: ParameterObject = { + name: 'filter', + in: 'query', + style: 'deepObject', + schema: { + type: 'object', + properties: { + where: {type: 'object', additionalProperties: true}, + limit: {type: 'number'}, + }, + }, + }; + expectSpecToBeEqual(MyController, expectedParamSpec); + }); + }); }); function expectSpecToBeEqual(controller: Function, paramSpec: object) { diff --git a/packages/rest/package.json b/packages/rest/package.json index 95531a46d2c1..04415fba0766 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -31,6 +31,8 @@ "@types/cors": "^2.8.3", "@types/express": "^4.11.1", "@types/http-errors": "^1.6.1", + "@types/parseurl": "^1.3.1", + "@types/qs": "^6.5.1", "ajv": "^6.5.1", "body": "^5.1.0", "cors": "^2.8.4", @@ -40,7 +42,9 @@ "js-yaml": "^3.11.0", "lodash": "^4.17.5", "openapi-schema-to-json-schema": "^2.1.0", + "parseurl": "^1.3.2", "path-to-regexp": "^2.2.0", + "qs": "^6.5.2", "strong-error-handler": "^3.2.0", "validator": "^10.4.0" }, diff --git a/packages/rest/src/coercion/coerce-parameter.ts b/packages/rest/src/coercion/coerce-parameter.ts index f7d8ea55a84b..5ebe8ff911a9 100644 --- a/packages/rest/src/coercion/coerce-parameter.ts +++ b/packages/rest/src/coercion/coerce-parameter.ts @@ -63,10 +63,10 @@ export function coerceParameter( return coerceInteger(data, spec); case 'boolean': return coerceBoolean(data, spec); + case 'object': + return coerceObject(data, spec); case 'string': case 'password': - // serialize will be supported in next PR - case 'serialize': default: return data; } @@ -140,3 +140,51 @@ function coerceBoolean(data: string | object, spec: ParameterObject) { if (isFalse(data)) return false; throw RestHttpErrors.invalidData(data, spec.name); } + +function coerceObject(input: string | object, spec: ParameterObject) { + const data = parseJsonIfNeeded(input, spec); + + if (data === undefined) { + // Skip any further checks and coercions, nothing we can do with `undefined` + return undefined; + } + + if (typeof data !== 'object' || Array.isArray(data)) + throw RestHttpErrors.invalidData(input, spec.name); + + // TODO(bajtos) apply coercion based on properties defined by spec.schema + return data; +} + +function parseJsonIfNeeded( + data: string | object, + spec: ParameterObject, +): string | object | undefined { + if (typeof data !== 'string') return data; + + if (spec.in !== 'query' || spec.style !== 'deepObject') { + debug( + 'Skipping JSON.parse, argument %s is not in:query style:deepObject', + spec.name, + ); + return data; + } + + if (data === '') { + debug('Converted empty string to object value `undefined`'); + return undefined; + } + + try { + const result = JSON.parse(data); + debug('Parsed parameter %s as %j', spec.name, result); + return result; + } catch (err) { + debug('Cannot parse %s value %j as JSON: %s', spec.name, data, err.message); + throw RestHttpErrors.invalidData(data, spec.name, { + details: { + syntaxError: err.message, + }, + }); + } +} diff --git a/packages/rest/src/coercion/utils.ts b/packages/rest/src/coercion/utils.ts index 96a4d03b3f90..69c8f280d061 100644 --- a/packages/rest/src/coercion/utils.ts +++ b/packages/rest/src/coercion/utils.ts @@ -21,8 +21,9 @@ export type IntegerCoercionOptions = { }; export function isEmpty(data: string) { - debug('isEmpty %s', data); - return data === ''; + const result = data === ''; + debug('isEmpty(%j) -> %s', data, result); + return result; } /** * A set of truthy values. A data in this set will be coerced to `true`. @@ -59,8 +60,9 @@ const REGEX_RFC3339_DATE = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01] */ export function matchDateFormat(date: string) { const pattern = new RegExp(REGEX_RFC3339_DATE); - debug('matchDateFormat: %s', pattern.test(date)); - return pattern.test(date); + const result = pattern.test(date); + debug('matchDateFormat(%j) -> %s', date, result); + return result; } /** @@ -70,8 +72,7 @@ export function matchDateFormat(date: string) { * @param format The format in an OpenAPI schema specification */ export function getOAIPrimitiveType(type?: string, format?: string) { - // serizlize will be supported in next PR - if (type === 'object' || type === 'array') return 'serialize'; + if (type === 'object' || type === 'array') return type; if (type === 'string') { switch (format) { case 'byte': diff --git a/packages/rest/src/coercion/validator.ts b/packages/rest/src/coercion/validator.ts index 1e72497c2bd9..4f526693cb1d 100644 --- a/packages/rest/src/coercion/validator.ts +++ b/packages/rest/src/coercion/validator.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {ParameterObject} from '@loopback/openapi-v3-types'; +import {ParameterObject, SchemaObject} from '@loopback/openapi-v3-types'; import {RestHttpErrors} from '../'; /** @@ -63,6 +63,11 @@ export class Validator { */ // tslint:disable-next-line:no-any isAbsent(value: any) { - return value === '' || value === undefined; + if (value === '' || value === undefined) return true; + + const schema: SchemaObject = this.ctx.parameterSpec.schema || {}; + if (schema.type === 'object' && value === 'null') return true; + + return false; } } diff --git a/packages/rest/src/parser.ts b/packages/rest/src/parser.ts index cd67febdf028..c299e0242ad7 100644 --- a/packages/rest/src/parser.ts +++ b/packages/rest/src/parser.ts @@ -3,25 +3,32 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {ServerRequest} from 'http'; -import * as HttpErrors from 'http-errors'; +import {REQUEST_BODY_INDEX} from '@loopback/openapi-v3'; import { + isReferenceObject, OperationObject, ParameterObject, - isReferenceObject, SchemasObject, } from '@loopback/openapi-v3-types'; -import {REQUEST_BODY_INDEX} from '@loopback/openapi-v3'; +import * as debugModule from 'debug'; +import {ServerRequest} from 'http'; +import * as HttpErrors from 'http-errors'; +import * as parseUrl from 'parseurl'; +import {parse as parseQuery} from 'qs'; import {promisify} from 'util'; -import {OperationArgs, Request, PathParameterValues} from './types'; -import {ResolvedRoute} from './router/routing-table'; import {coerceParameter} from './coercion/coerce-parameter'; -import {validateRequestBody} from './validation/request-body.validator'; import {RestHttpErrors} from './index'; +import {ResolvedRoute} from './router/routing-table'; +import {OperationArgs, PathParameterValues, Request} from './types'; +import {validateRequestBody} from './validation/request-body.validator'; + type HttpError = HttpErrors.HttpError; -import * as debugModule from 'debug'; + const debug = debugModule('loopback:rest:parser'); +export const QUERY_NOT_PARSED = {}; +Object.freeze(QUERY_NOT_PARSED); + // tslint:disable-next-line:no-any type MaybeBody = any | undefined; @@ -134,22 +141,31 @@ function getParamFromRequest( request: Request, pathParams: PathParameterValues, ) { - let result; switch (spec.in) { case 'query': - result = request.query[spec.name]; - break; + ensureRequestQueryWasParsed(request); + return request.query[spec.name]; case 'path': - result = pathParams[spec.name]; - break; + return pathParams[spec.name]; case 'header': // @jannyhou TBD: check edge cases - result = request.headers[spec.name.toLowerCase()]; + return request.headers[spec.name.toLowerCase()]; break; // TODO(jannyhou) to support `cookie`, // see issue https://github.com/strongloop/loopback-next/issues/997 default: throw RestHttpErrors.invalidParamLocation(spec.in); } - return result; +} + +function ensureRequestQueryWasParsed(request: Request) { + if (request.query && request.query !== QUERY_NOT_PARSED) return; + + const input = parseUrl(request)!.query; + if (input && typeof input === 'string') { + request.query = parseQuery(input); + } else { + request.query = {}; + } + debug('Parsed request query: ', request.query); } diff --git a/packages/rest/src/rest-http-error.ts b/packages/rest/src/rest-http-error.ts index b9ab2fb1332a..76776067220d 100644 --- a/packages/rest/src/rest-http-error.ts +++ b/packages/rest/src/rest-http-error.ts @@ -1,23 +1,31 @@ import * as HttpErrors from 'http-errors'; export namespace RestHttpErrors { - export function invalidData(data: T, name: string) { + export function invalidData( + data: T, + name: string, + extraProperties?: Props, + ): HttpErrors.HttpError & Props { const msg = `Invalid data ${JSON.stringify(data)} for parameter ${name}!`; - return new HttpErrors.BadRequest(msg); + return Object.assign(new HttpErrors.BadRequest(msg), extraProperties); } + export function missingRequired(name: string): HttpErrors.HttpError { const msg = `Required parameter ${name} is missing!`; return new HttpErrors.BadRequest(msg); } + export function invalidParamLocation(location: string): HttpErrors.HttpError { const msg = `Parameters with "in: ${location}" are not supported yet.`; return new HttpErrors.NotImplemented(msg); } + export const INVALID_REQUEST_BODY_MESSAGE = 'The request body is invalid. See error object `details` property for more info.'; export function invalidRequestBody(): HttpErrors.HttpError { return new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE); } + /** * An invalid request body error contains a `details` property as the machine-readable error. * Each entry in `error.details` contains 4 attributes: `path`, `code`, `info` and `message`. diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 64258e2b30d7..eac3522eee44 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -3,41 +3,42 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Binding, Constructor, Context, inject} from '@loopback/context'; +import {Application, CoreBindings, Server} from '@loopback/core'; +import {HttpServer, HttpServerOptions} from '@loopback/http-server'; +import {getControllerSpec} from '@loopback/openapi-v3'; +import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3-types'; import {AssertionError} from 'assert'; +import * as cors from 'cors'; +import * as express from 'express'; +import {PathParams} from 'express-serve-static-core'; +import {ServerRequest, ServerResponse} from 'http'; import {safeDump} from 'js-yaml'; -import {Binding, Context, Constructor, inject} from '@loopback/context'; +import * as pathToRegExp from 'path-to-regexp'; +import {ServeStaticOptions} from 'serve-static'; +import {HttpHandler} from './http-handler'; +import {RestBindings} from './keys'; +import {QUERY_NOT_PARSED} from './parser'; +import {RequestContext} from './request-context'; import { - Route, - ControllerRoute, - RouteEntry, - ControllerFactory, ControllerClass, + ControllerFactory, ControllerInstance, + ControllerRoute, createControllerFactoryForBinding, + Route, + RouteEntry, } from './router/routing-table'; -import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3-types'; -import {ServerRequest, ServerResponse} from 'http'; -import {HttpServer, HttpServerOptions} from '@loopback/http-server'; -import * as cors from 'cors'; -import {Application, CoreBindings, Server} from '@loopback/core'; -import {getControllerSpec} from '@loopback/openapi-v3'; -import {HttpHandler} from './http-handler'; -import {DefaultSequence, SequenceHandler, SequenceFunction} from './sequence'; +import {DefaultSequence, SequenceFunction, SequenceHandler} from './sequence'; import { FindRoute, InvokeMethod, - Send, - Reject, ParseParams, + Reject, Request, Response, + Send, } from './types'; -import {RestBindings} from './keys'; -import {RequestContext} from './request-context'; -import * as express from 'express'; -import {ServeStaticOptions} from 'serve-static'; -import {PathParams} from 'express-serve-static-core'; -import * as pathToRegExp from 'path-to-regexp'; const debug = require('debug')('loopback:rest:server'); @@ -186,6 +187,15 @@ export class RestServer extends Context implements Server, HttpServerLike { protected _setupRequestHandler(options: RestServerConfig) { this._expressApp = express(); + + // Disable express' built-in query parser, we parse queries ourselves + // Note that when disabled, express sets query to an empty object, + // which makes it difficult for us to detect whether the query + // has been parsed or not. At the same time, we want `request.query` + // to remain as an object, because everybody in express ecosystem expects + // that property to be defined. A static singleton object to the rescue! + this._expressApp.set('query parser fn', (str: string) => QUERY_NOT_PARSED); + this.requestHandler = this._expressApp; // Allow CORS support for all endpoints so that users diff --git a/packages/rest/test/acceptance/coercion/coercion.acceptance.ts b/packages/rest/test/acceptance/coercion/coercion.acceptance.ts index 8631ec1d91eb..87bdfef8abbd 100644 --- a/packages/rest/test/acceptance/coercion/coercion.acceptance.ts +++ b/packages/rest/test/acceptance/coercion/coercion.acceptance.ts @@ -4,6 +4,7 @@ import {RestApplication, get, param} from '../../..'; describe('Coercion', () => { let app: RestApplication; let client: supertest.SuperTest; + let spy: sinon.SinonSpy; before(givenAClient); @@ -11,6 +12,8 @@ describe('Coercion', () => { await app.stop(); }); + afterEach(() => spy.restore()); + class MyController { @get('/create-number-from-path/{num}') createNumberFromPath(@param.path.number('num') num: number) { @@ -26,22 +29,27 @@ describe('Coercion', () => { createNumberFromHeader(@param.header.number('num') num: number) { return num; } + + @get('/object-from-query') + getObjectFromQuery(@param.query.object('filter') filter: Object) { + return filter; + } } it('coerces parameter in path from string to number', async () => { - const spy = sinon.spy(MyController.prototype, 'createNumberFromPath'); + spy = sinon.spy(MyController.prototype, 'createNumberFromPath'); await client.get('/create-number-from-path/100').expect(200); sinon.assert.calledWithExactly(spy, 100); }); it('coerces parameter in header from string to number', async () => { - const spy = sinon.spy(MyController.prototype, 'createNumberFromHeader'); + spy = sinon.spy(MyController.prototype, 'createNumberFromHeader'); await client.get('/create-number-from-header').set({num: 100}); sinon.assert.calledWithExactly(spy, 100); }); it('coerces parameter in query from string to number', async () => { - const spy = sinon.spy(MyController.prototype, 'createNumberFromQuery'); + spy = sinon.spy(MyController.prototype, 'createNumberFromQuery'); await client .get('/create-number-from-query') .query({num: 100}) @@ -49,6 +57,39 @@ describe('Coercion', () => { sinon.assert.calledWithExactly(spy, 100); }); + it('coerces parameter in query from JSON to object', async () => { + spy = sinon.spy(MyController.prototype, 'getObjectFromQuery'); + await client + .get('/object-from-query') + .query({filter: '{"where":{"id":1,"name":"Pen", "active": true}}'}) + .expect(200); + sinon.assert.calledWithExactly(spy, { + where: {id: 1, name: 'Pen', active: true}, + }); + }); + + it('coerces parameter in query from nested keys to object', async () => { + spy = sinon.spy(MyController.prototype, 'getObjectFromQuery'); + await client + .get('/object-from-query') + .query({ + 'filter[where][id]': 1, + 'filter[where][name]': 'Pen', + 'filter[where][active]': true, + }) + .expect(200); + sinon.assert.calledWithExactly(spy, { + // Notice that numeric and boolean values are converted to strings. + // This is because all values are encoded as strings on URL queries + // and we did not specify any schema in @param.query.object() decorator. + where: { + id: '1', + name: 'Pen', + active: 'true', + }, + }); + }); + async function givenAClient() { app = new RestApplication(); app.controller(MyController); diff --git a/packages/rest/test/unit/coercion/paramObject.unit.ts b/packages/rest/test/unit/coercion/paramObject.unit.ts new file mode 100644 index 000000000000..d4e65fdf7759 --- /dev/null +++ b/packages/rest/test/unit/coercion/paramObject.unit.ts @@ -0,0 +1,149 @@ +// Copyright IBM Corp. 2018. 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 {ParameterObject} from '@loopback/openapi-v3-types'; +import * as qs from 'qs'; +import {RestHttpErrors} from '../../..'; +import {test} from './utils'; + +const OPTIONAL_ANY_OBJECT: ParameterObject = { + in: 'query', + name: 'aparameter', + schema: { + type: 'object', + additionalProperties: true, + }, + style: 'deepObject', +}; + +const REQUIRED_ANY_OBJECT = { + ...OPTIONAL_ANY_OBJECT, + required: true, +}; + +describe('coerce object param - required', function() { + context('valid values', () => { + // Use JSON-encoded style, qs.stringify() omits empty objects + test(REQUIRED_ANY_OBJECT, '{}', {}); + test(REQUIRED_ANY_OBJECT, {key: 'value'}, {key: 'value'}); + + test(REQUIRED_ANY_OBJECT, {key: 'undefined'}, {key: 'undefined'}); + test(REQUIRED_ANY_OBJECT, {key: 'null'}, {key: 'null'}); + test(REQUIRED_ANY_OBJECT, {key: 'text'}, {key: 'text'}); + }); + + context('empty values trigger ERROR_BAD_REQUEST', () => { + test( + REQUIRED_ANY_OBJECT, + undefined, // the parameter is missing + RestHttpErrors.missingRequired(REQUIRED_ANY_OBJECT.name), + ); + + test( + REQUIRED_ANY_OBJECT, + '', // ?param= + RestHttpErrors.missingRequired(REQUIRED_ANY_OBJECT.name), + ); + + test( + REQUIRED_ANY_OBJECT, + 'null', // ?param=null + RestHttpErrors.missingRequired(REQUIRED_ANY_OBJECT.name), + ); + }); + + context('array values are not allowed', () => { + // JSON encoding + testInvalidDataError('[]'); + testInvalidDataError('[1,2]'); + + // deepObject style + testInvalidDataError([1, 2]); + }); + + function testInvalidDataError( + input: string | object, + extraErrorProps?: Props, + ) { + test( + REQUIRED_ANY_OBJECT, + input, + RestHttpErrors.invalidData( + createInvalidDataInput(input), + REQUIRED_ANY_OBJECT.name, + extraErrorProps, + ), + ); + } +}); + +describe('coerce object param - optional', function() { + context('valid values', () => { + // Use JSON-encoded style, qs.stringify() omits empty objects + test(OPTIONAL_ANY_OBJECT, '{}', {}); + test(OPTIONAL_ANY_OBJECT, {key: 'value'}, {key: 'value'}); + test(OPTIONAL_ANY_OBJECT, undefined, undefined); + test(OPTIONAL_ANY_OBJECT, '', undefined); + test(OPTIONAL_ANY_OBJECT, 'null', null); + }); + + context('nested values are not coerced', () => { + test(OPTIONAL_ANY_OBJECT, {key: 'undefined'}, {key: 'undefined'}); + test(OPTIONAL_ANY_OBJECT, {key: 'null'}, {key: 'null'}); + test(OPTIONAL_ANY_OBJECT, {key: 'text'}, {key: 'text'}); + test(OPTIONAL_ANY_OBJECT, {key: '0'}, {key: '0'}); + test(OPTIONAL_ANY_OBJECT, {key: '1'}, {key: '1'}); + test(OPTIONAL_ANY_OBJECT, {key: '-1'}, {key: '-1'}); + test(OPTIONAL_ANY_OBJECT, {key: '1.2'}, {key: '1.2'}); + test(OPTIONAL_ANY_OBJECT, {key: '-1.2'}, {key: '-1.2'}); + test(OPTIONAL_ANY_OBJECT, {key: 'true'}, {key: 'true'}); + test(OPTIONAL_ANY_OBJECT, {key: 'false'}, {key: 'false'}); + test( + OPTIONAL_ANY_OBJECT, + {key: '2016-05-19T13:28:51.299Z'}, + {key: '2016-05-19T13:28:51.299Z'}, + ); + }); + + context('invalid values should trigger ERROR_BAD_REQUEST', () => { + testInvalidDataError('text', { + details: { + syntaxError: 'Unexpected token e in JSON at position 1', + }, + }); + + testInvalidDataError('0'); + testInvalidDataError('1'); + }); + + context('array values are not allowed', () => { + testInvalidDataError('[]'); + testInvalidDataError('[1,2]'); + testInvalidDataError([1, 2]); + }); + + function testInvalidDataError( + input: string | object, + extraErrorProps?: Props, + ) { + test( + OPTIONAL_ANY_OBJECT, + input, + RestHttpErrors.invalidData( + createInvalidDataInput(input), + OPTIONAL_ANY_OBJECT.name, + extraErrorProps, + ), + ); + } +}); + +function createInvalidDataInput(input: string | object) { + if (typeof input === 'string') return input; + + // convert deep property values to strings, that's what our parser + // is going to receive on input and show in the error message + return qs.parse(qs.stringify({value: input})).value; +} diff --git a/packages/rest/test/unit/coercion/utils.ts b/packages/rest/test/unit/coercion/utils.ts index 6a762aa6574a..dcabfebed4aa 100644 --- a/packages/rest/test/unit/coercion/utils.ts +++ b/packages/rest/test/unit/coercion/utils.ts @@ -1,20 +1,20 @@ import {OperationObject, ParameterObject} from '@loopback/openapi-v3-types'; - import { - ShotRequestOptions, expect, + ShotRequestOptions, stubExpressContext, } from '@loopback/testlab'; - +import * as HttpErrors from 'http-errors'; +import * as qs from 'qs'; +import {format} from 'util'; import { - PathParameterValues, - Request, - Route, createResolvedRoute, parseOperationArgs, + PathParameterValues, + Request, ResolvedRoute, + Route, } from '../../..'; -import * as HttpErrors from 'http-errors'; function givenOperationWithParameters(params?: ParameterObject[]) { return { @@ -52,9 +52,33 @@ export type TestOptions = { export async function testCoercion(config: TestArgs) { /* istanbul ignore next */ try { + const pathParams: PathParameterValues = {}; + const req = givenRequest(); const spec = givenOperationWithParameters([config.paramSpec]); - const route = givenResolvedRoute(spec, {aparameter: config.rawValue}); + const route = givenResolvedRoute(spec, pathParams); + + switch (config.paramSpec.in) { + case 'path': + pathParams.aparameter = config.rawValue; + break; + case 'query': + const q = qs.stringify( + {aparameter: config.rawValue}, + {encodeValuesOnly: true}, + ); + req.url += `?${q}`; + break; + case 'header': + case 'cookie': + throw new Error( + `testCoercion does not yet support in:${config.paramSpec.in}`, + ); + default: + // An invalid param spec. Pass it through as an empty request + // to allow the tests to verify how invalid param spec is handled + break; + } if (config.expectError) { await expect(parseOperationArgs(req, route)).to.be.rejectedWith( @@ -77,8 +101,7 @@ export function test( opts?: TestOptions, ) { const caller: string = new Error().stack!; - const DEFAULT_TEST_NAME = `convert request raw value ${rawValue} to ${expectedResult}`; - const testName = (opts && opts.testName) || DEFAULT_TEST_NAME; + const testName = buildTestName(rawValue, expectedResult, opts); it(testName, async () => { await testCoercion({ @@ -91,3 +114,26 @@ export function test( }); }); } + +function buildTestName( + rawValue: string | undefined | object, + expectedResult: T, + opts?: TestOptions, +): string { + if (opts && opts.testName) return opts.testName; + + const inputString = getPrettyString(rawValue); + if (expectedResult instanceof HttpErrors.HttpError) + return `rejects request raw value ${inputString}`; + const expectedString = getPrettyString(expectedResult); + return `converts request raw value ${inputString} to ${expectedString}`; +} + +function getPrettyString(value: T) { + switch (typeof value) { + case 'string': + return JSON.stringify(value); + default: + return format(value); + } +} diff --git a/packages/rest/test/unit/parser.unit.ts b/packages/rest/test/unit/parser.unit.ts index 06d0f61c8358..c45b8816f819 100644 --- a/packages/rest/test/unit/parser.unit.ts +++ b/packages/rest/test/unit/parser.unit.ts @@ -7,6 +7,7 @@ import { OperationObject, ParameterObject, RequestBodyObject, + SchemaObject, } from '@loopback/openapi-v3-types'; import { ShotRequestOptions, @@ -20,6 +21,7 @@ import { createResolvedRoute, parseOperationArgs, } from '../..'; +import {RestHttpErrors} from '../../src'; describe('operationArgsParser', () => { it('parses path parameters', async () => { @@ -56,6 +58,92 @@ describe('operationArgsParser', () => { expect(args).to.eql([{key: 'value'}]); }); + context('in:query style:deepObject', () => { + it('parses JSON-encoded string value', async () => { + const req = givenRequest({ + url: '/?value={"key":"value"}', + }); + + const spec = givenOperationWithObjectParameter('value'); + const route = givenResolvedRoute(spec); + + const args = await parseOperationArgs(req, route); + + expect(args).to.eql([{key: 'value'}]); + }); + + it('parses object value provided via nested keys', async () => { + const req = givenRequest({ + url: '/?value[key]=value', + }); + + const spec = givenOperationWithObjectParameter('value'); + const route = givenResolvedRoute(spec); + + const args = await parseOperationArgs(req, route); + + expect(args).to.eql([{key: 'value'}]); + }); + + it('rejects malformed JSON string', async () => { + const req = givenRequest({ + url: '/?value={"malformed-JSON"}', + }); + + const spec = givenOperationWithObjectParameter('value'); + const route = givenResolvedRoute(spec); + + await expect(parseOperationArgs(req, route)).to.be.rejectedWith( + RestHttpErrors.invalidData('{"malformed-JSON"}', 'value', { + details: { + syntaxError: 'Unexpected token } in JSON at position 17', + }, + }), + ); + }); + + it('rejects array values encoded as JSON', async () => { + const req = givenRequest({ + url: '/?value=[1,2]', + }); + + const spec = givenOperationWithObjectParameter('value'); + const route = givenResolvedRoute(spec); + + await expect(parseOperationArgs(req, route)).to.be.rejectedWith( + RestHttpErrors.invalidData('[1,2]', 'value'), + ); + }); + + it('rejects array values provided via nested keys', async () => { + const req = givenRequest({ + url: '/?value=1&value=2', + }); + + const spec = givenOperationWithObjectParameter('value'); + const route = givenResolvedRoute(spec); + + await expect(parseOperationArgs(req, route)).to.be.rejectedWith( + RestHttpErrors.invalidData(['1', '2'], 'value'), + ); + }); + + function givenOperationWithObjectParameter( + name: string, + schema: SchemaObject = {type: 'object', additionalProperties: true}, + ) { + expect(schema).to.have.property('type', 'object'); + return givenOperationWithParameters([ + { + name, + in: 'query', + style: 'deepObject', + schema, + }, + ]); + } + }); + function givenOperationWithParameters(params?: ParameterObject[]) { return { 'x-operation-name': 'testOp', From dbd409457de86d39b4ab57c73951acd87eafeea4 Mon Sep 17 00:00:00 2001 From: Jesse Degger Date: Fri, 7 Sep 2018 10:50:55 +0200 Subject: [PATCH 25/30] docs: fix broken link --- docs/site/DataSource-generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/site/DataSource-generator.md b/docs/site/DataSource-generator.md index f1c69e068dba..8f06ec99e3bd 100644 --- a/docs/site/DataSource-generator.md +++ b/docs/site/DataSource-generator.md @@ -10,7 +10,7 @@ permalink: /doc/en/lb4/DataSource-generator.html ### Synopsis -Adds a new [DataSource](Datasources.md) class and config files to a LoopBack +Adds a new [DataSource](DataSources.md) class and config files to a LoopBack application. ```sh From fa7b54797e7de404e65d3f60c3a3b4f84a74561a Mon Sep 17 00:00:00 2001 From: Taranveer Virk Date: Fri, 7 Sep 2018 01:15:47 -0400 Subject: [PATCH 26/30] docs: fix file path in tutorial --- docs/site/todo-tutorial-geocoding-service.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/site/todo-tutorial-geocoding-service.md b/docs/site/todo-tutorial-geocoding-service.md index 13e51fabbf49..24b9291a3f6b 100644 --- a/docs/site/todo-tutorial-geocoding-service.md +++ b/docs/site/todo-tutorial-geocoding-service.md @@ -34,6 +34,16 @@ for example IBM's [Weather Company Data](https://console.bluemix.net/catalog/ser or [Google Maps Platform](https://developers.google.com/maps/documentation/geocoding). " %} +### Install `@loopback/service-proxy` + +`@loopback/service-proxy` provides a common set of interfaces for interacting +with service oriented backends such as REST APIs, SOAP Web Services, and gRPC +microservices. Install it in your project by running the following command: + +``` +npm i @loopback/service-proxy +``` + ### Configure the backing datasource Run `lb4 datasource` to define a new datasource connecting to Geocoder REST @@ -96,8 +106,8 @@ Create a new directory `src/services` and add the following two new files: - `src/services/geocoder.service.ts` defining TypeScript interfaces for Geocoder service and implementing a service proxy provider. -- `src/index.ts` providing a conventient access to all services via a single - `import` statement. +- `src/services/index.ts` providing a conventient access to all services via a + single `import` statement. #### src/services/geocoder.service.ts From 5898849aa1a1a4cc5c1d79d69c8233d8d102e346 Mon Sep 17 00:00:00 2001 From: Mario Estrada Date: Fri, 7 Sep 2018 13:17:41 -0600 Subject: [PATCH 27/30] fix(docs): fix todo-tutorial import service and Promise wrapper Mention the need for import the services and reflec the Promise<> on value(): method fix #1681 --- docs/site/todo-tutorial-geocoding-service.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/site/todo-tutorial-geocoding-service.md b/docs/site/todo-tutorial-geocoding-service.md index 24b9291a3f6b..3b817e012619 100644 --- a/docs/site/todo-tutorial-geocoding-service.md +++ b/docs/site/todo-tutorial-geocoding-service.md @@ -138,7 +138,7 @@ export class GeocoderServiceProvider implements Provider { protected datasource: juggler.DataSource = new GeocoderDataSource(), ) {} - value(): GeocoderService { + value(): Promise { return getService(this.datasource); } } @@ -178,12 +178,14 @@ export class Todo extends Entity { Finally, modify `TodoController` to look up the address and convert it to GPS coordinates when a new Todo item is created. -Modify the Controller constructor to receive `GeocoderService` as a new -dependency. +Import `GeocodeService` interface into the `TodoController` and then modify the +Controller constructor to receive `GeocodeService` as a new dependency. #### src/controllers/todo.controller.ts ```ts +import {GeocoderService} from '../services'; + export class TodoController { constructor( @repository(TodoRepository) protected todoRepo: TodoRepository, From 9b790ed926739a98913e5b2be63dffac0d09eae8 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 7 Sep 2018 11:37:51 -0400 Subject: [PATCH 28/30] fix: soap example * If using lb4 example to clone soap example, the calculator ds json file gets created --- .vscode/settings.json | 1 - examples/soap-calculator/index.d.ts | 5 +++++ examples/soap-calculator/package.json | 8 -------- examples/soap-calculator/tsconfig.build.json | 8 ++++++++ examples/soap-calculator/tsconfig.json | 14 -------------- 5 files changed, 13 insertions(+), 23 deletions(-) create mode 100644 examples/soap-calculator/index.d.ts create mode 100644 examples/soap-calculator/tsconfig.build.json delete mode 100644 examples/soap-calculator/tsconfig.json diff --git a/.vscode/settings.json b/.vscode/settings.json index ebb9f0be3dff..650638a01a99 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,7 +23,6 @@ }, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, - "tslint.ignoreDefinitionFiles": true, "tslint.nodePath": "./packages/build/node_modules", "typescript.tsdk": "./packages/build/node_modules/typescript/lib" diff --git a/examples/soap-calculator/index.d.ts b/examples/soap-calculator/index.d.ts new file mode 100644 index 000000000000..8044e0512741 --- /dev/null +++ b/examples/soap-calculator/index.d.ts @@ -0,0 +1,5 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/example-soap-calculator +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +export * from './dist8'; diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index c08d463642cf..7dce630cf0bc 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -43,14 +43,6 @@ "access": "public" }, "license": "MIT", - "files": [ - "README.md", - "index.js", - "index.d.ts", - "dist*/src", - "dist*/index*", - "src" - ], "dependencies": { "@loopback/boot": "^0.12.6", "@loopback/context": "^0.12.5", diff --git a/examples/soap-calculator/tsconfig.build.json b/examples/soap-calculator/tsconfig.build.json new file mode 100644 index 000000000000..d9dc5d30c3df --- /dev/null +++ b/examples/soap-calculator/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "./node_modules/@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["index.ts", "src", "test"] +} diff --git a/examples/soap-calculator/tsconfig.json b/examples/soap-calculator/tsconfig.json deleted file mode 100644 index 8e3cda75d5b7..000000000000 --- a/examples/soap-calculator/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/tsconfig", - "extends": "./node_modules/@loopback/build/config/tsconfig.common.json", - "include": [ - "src", - "test", - "index.ts" - ], - "exclude": [ - "node_modules/**", - "packages/*/node_modules/**", - "**/*.d.ts" - ] -} From 3e0de36ba2ba9d09b04262fac22641347fa41466 Mon Sep 17 00:00:00 2001 From: Diana Lau Date: Fri, 7 Sep 2018 17:40:37 -0400 Subject: [PATCH 29/30] docs: update front matter in glossary page --- docs/site/Glossary.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/site/Glossary.md b/docs/site/Glossary.md index 60069e394b44..795033fde9be 100644 --- a/docs/site/Glossary.md +++ b/docs/site/Glossary.md @@ -1,3 +1,12 @@ +--- +lang: en +title: 'Glossary' +keywords: LoopBack 4.0, LoopBack 4 +toc_level: 1 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Glossary.html +--- + **Action**: JavaScript functions that only accept or return Elements. Since the input of one action (an Element) is the output of another action (Element) they are easily composed. From be1f42b866be8122761ac8dc1dcd7448e320c470 Mon Sep 17 00:00:00 2001 From: virkt25 Date: Fri, 7 Sep 2018 23:45:42 -0400 Subject: [PATCH 30/30] chore: publish release - @loopback/benchmark@0.2.4 - @loopback/docs@0.17.0 - @loopback/example-hello-world@0.9.7 - @loopback/example-log-extension@0.11.7 - @loopback/example-rpc-server@0.11.3 - @loopback/example-soap-calculator@0.3.0 - @loopback/example-todo-list@0.3.4 - @loopback/example-todo@0.17.0 - @loopback/authentication@0.11.7 - @loopback/boot@0.13.0 - @loopback/build@0.7.2 - @loopback/cli@0.22.0 - @loopback/context@0.12.6 - @loopback/core@0.11.6 - @loopback/http-caching-proxy@0.3.6 - @loopback/http-server@0.3.6 - @loopback/metadata@0.9.6 - @loopback/openapi-spec-builder@0.8.6 - @loopback/openapi-v3-types@0.9.0 - @loopback/openapi-v3@0.13.0 - @loopback/repository-json-schema@0.10.7 - @loopback/repository@0.16.0 - @loopback/rest@0.20.0 - @loopback/service-proxy@0.8.0 - @loopback/testlab@0.12.0 - @loopback/sandbox-example@0.1.1 --- benchmark/CHANGELOG.md | 9 ++++ benchmark/package.json | 8 +-- docs/CHANGELOG.md | 24 +++++++++ docs/package.json | 2 +- examples/hello-world/CHANGELOG.md | 9 ++++ examples/hello-world/package.json | 10 ++-- examples/log-extension/CHANGELOG.md | 12 +++++ examples/log-extension/package.json | 14 ++--- examples/rpc-server/CHANGELOG.md | 9 ++++ examples/rpc-server/package.json | 10 ++-- examples/soap-calculator/CHANGELOG.md | 18 +++++++ examples/soap-calculator/package.json | 20 ++++---- examples/todo-list/CHANGELOG.md | 12 +++++ examples/todo-list/package.json | 24 ++++----- examples/todo/CHANGELOG.md | 17 ++++++ examples/todo/package.json | 24 ++++----- packages/authentication/CHANGELOG.md | 9 ++++ packages/authentication/package.json | 18 +++---- packages/boot/CHANGELOG.md | 19 +++++++ packages/boot/package.json | 18 +++---- packages/build/CHANGELOG.md | 9 ++++ packages/build/package.json | 2 +- packages/cli/CHANGELOG.md | 18 +++++++ packages/cli/package.json | 54 ++++++++++---------- packages/context/CHANGELOG.md | 9 ++++ packages/context/package.json | 8 +-- packages/core/CHANGELOG.md | 9 ++++ packages/core/package.json | 8 +-- packages/http-caching-proxy/CHANGELOG.md | 9 ++++ packages/http-caching-proxy/package.json | 6 +-- packages/http-server/CHANGELOG.md | 9 ++++ packages/http-server/package.json | 8 +-- packages/metadata/CHANGELOG.md | 9 ++++ packages/metadata/package.json | 6 +-- packages/openapi-spec-builder/CHANGELOG.md | 9 ++++ packages/openapi-spec-builder/package.json | 6 +-- packages/openapi-v3-types/CHANGELOG.md | 12 +++++ packages/openapi-v3-types/package.json | 6 +-- packages/openapi-v3/CHANGELOG.md | 12 +++++ packages/openapi-v3/package.json | 16 +++--- packages/repository-json-schema/CHANGELOG.md | 9 ++++ packages/repository-json-schema/package.json | 12 ++--- packages/repository/CHANGELOG.md | 18 +++++++ packages/repository/package.json | 10 ++-- packages/rest/CHANGELOG.md | 19 +++++++ packages/rest/package.json | 20 ++++---- packages/service-proxy/CHANGELOG.md | 12 +++++ packages/service-proxy/package.json | 10 ++-- packages/testlab/CHANGELOG.md | 12 +++++ packages/testlab/package.json | 4 +- sandbox/example/CHANGELOG.md | 9 ++++ sandbox/example/package.json | 2 +- 52 files changed, 485 insertions(+), 163 deletions(-) create mode 100644 sandbox/example/CHANGELOG.md diff --git a/benchmark/CHANGELOG.md b/benchmark/CHANGELOG.md index 800b3ceeee5f..34358dbc811e 100644 --- a/benchmark/CHANGELOG.md +++ b/benchmark/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +
+## [0.2.4](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@0.2.3...@loopback/benchmark@0.2.4) (2018-09-08) + +**Note:** Version bump only for package @loopback/benchmark + + + + + ## [0.2.3](https://github.com/strongloop/loopback-next/compare/@loopback/benchmark@0.2.2...@loopback/benchmark@0.2.3) (2018-08-25) diff --git a/benchmark/package.json b/benchmark/package.json index aa3db3de777d..2cc9e523c82f 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/benchmark", - "version": "0.2.3", + "version": "0.2.4", "private": true, "description": "Benchmarks measuring performance of our framework.", "keywords": [ @@ -39,7 +39,7 @@ ], "dependencies": { "@loopback/dist-util": "^0.3.6", - "@loopback/example-todo": "^0.16.3", + "@loopback/example-todo": "^0.17.0", "@types/byline": "^4.2.31", "@types/debug": "0.0.30", "@types/p-event": "^1.3.0", @@ -51,8 +51,8 @@ "request-promise-native": "^1.0.5" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/mocha": "^5.0.0", "@types/node": "^10.1.1", "mocha": "^5.1.1", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 67fbf3ece648..edf56ecdc456 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.17.0](https://github.com/strongloop/loopback-next/compare/@loopback/docs@0.16.4...@loopback/docs@0.17.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) +* **cli:** rename repository/service feature flags ([c089299](https://github.com/strongloop/loopback-next/commit/c089299)) +* **docs:** fix todo-tutorial import service and Promise wrapper ([5898849](https://github.com/strongloop/loopback-next/commit/5898849)), closes [#1681](https://github.com/strongloop/loopback-next/issues/1681) +* **docs:** fix typo ([5c33962](https://github.com/strongloop/loopback-next/commit/5c33962)) +* **docs:** fix typo in Repositories.md ([b18e95f](https://github.com/strongloop/loopback-next/commit/b18e95f)) + + +### Features + +* **rest:** allow static assets to be served by a rest server ([a1cefcc](https://github.com/strongloop/loopback-next/commit/a1cefcc)) +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) +* coerce object arguments from query strings ([d095693](https://github.com/strongloop/loopback-next/commit/d095693)) +* default 404 for request to non-existent resource ([f68a45c](https://github.com/strongloop/loopback-next/commit/f68a45c)) + + + + + ## [0.16.4](https://github.com/strongloop/loopback-next/compare/@loopback/docs@0.16.3...@loopback/docs@0.16.4) (2018-08-25) diff --git a/docs/package.json b/docs/package.json index 7b2fc114019d..0ec0593e7687 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/docs", - "version": "0.16.4", + "version": "0.17.0", "description": "Documentation for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/docs", "author": { diff --git a/examples/hello-world/CHANGELOG.md b/examples/hello-world/CHANGELOG.md index 18d041aff2df..c20b566c9464 100644 --- a/examples/hello-world/CHANGELOG.md +++ b/examples/hello-world/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.9.7](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@0.9.6...@loopback/example-hello-world@0.9.7) (2018-09-08) + +**Note:** Version bump only for package @loopback/example-hello-world + + + + + ## [0.9.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-hello-world@0.9.5...@loopback/example-hello-world@0.9.6) (2018-08-25) diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index e0c3703a4102..c0a3c63b641a 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-hello-world", - "version": "0.9.6", + "version": "0.9.7", "description": "A simple hello-world Application using LoopBack 4", "main": "index.js", "engines": { @@ -40,13 +40,13 @@ }, "license": "MIT", "dependencies": { - "@loopback/core": "^0.11.5", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/rest": "^0.19.6" + "@loopback/rest": "^0.20.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1" }, "keywords": [ diff --git a/examples/log-extension/CHANGELOG.md b/examples/log-extension/CHANGELOG.md index f185ffcd30c4..eaa194e5dcd2 100644 --- a/examples/log-extension/CHANGELOG.md +++ b/examples/log-extension/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. + +## [0.11.7](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@0.11.6...@loopback/example-log-extension@0.11.7) (2018-09-08) + + +### Bug Fixes + +* **cli:** rename repository/service feature flags ([c089299](https://github.com/strongloop/loopback-next/commit/c089299)) + + + + + ## [0.11.6](https://github.com/strongloop/loopback-next/compare/@loopback/example-log-extension@0.11.5...@loopback/example-log-extension@0.11.6) (2018-08-25) diff --git a/examples/log-extension/package.json b/examples/log-extension/package.json index 4217f90e8b85..0b5b4b76736a 100644 --- a/examples/log-extension/package.json +++ b/examples/log-extension/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-log-extension", - "version": "0.11.6", + "version": "0.11.7", "description": "An example extension project for LoopBack 4", "main": "index.js", "engines": { @@ -44,17 +44,17 @@ }, "homepage": "https://github.com/strongloop/loopback-next/tree/master/examples/log-extension", "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/debug": "0.0.30", "@types/node": "^10.1.1" }, "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/rest": "^0.19.6", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/rest": "^0.20.0", "chalk": "^2.3.2", "debug": "^3.1.0" } diff --git a/examples/rpc-server/CHANGELOG.md b/examples/rpc-server/CHANGELOG.md index 54421668ea12..70de999e7cc5 100644 --- a/examples/rpc-server/CHANGELOG.md +++ b/examples/rpc-server/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.11.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@0.11.2...@loopback/example-rpc-server@0.11.3) (2018-09-08) + +**Note:** Version bump only for package @loopback/example-rpc-server + + + + + ## [0.11.2](https://github.com/strongloop/loopback-next/compare/@loopback/example-rpc-server@0.11.1...@loopback/example-rpc-server@0.11.2) (2018-08-24) diff --git a/examples/rpc-server/package.json b/examples/rpc-server/package.json index a50498e4a024..0dc7f0f99480 100644 --- a/examples/rpc-server/package.json +++ b/examples/rpc-server/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-rpc-server", - "version": "0.11.2", + "version": "0.11.3", "description": "A basic RPC server using a made-up protocol.", "keywords": [ "loopback-application", @@ -41,15 +41,15 @@ "author": "", "license": "MIT", "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", "express": "^4.16.3", "p-event": "^1.3.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/express": "^4.11.1", "@types/node": "^10.1.1", "@types/p-event": "^1.3.0" diff --git a/examples/soap-calculator/CHANGELOG.md b/examples/soap-calculator/CHANGELOG.md index 1bef60acc62a..f3dc80adf215 100644 --- a/examples/soap-calculator/CHANGELOG.md +++ b/examples/soap-calculator/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. + +# [0.3.0](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@0.2.3...@loopback/example-soap-calculator@0.3.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) +* soap example ([9b790ed](https://github.com/strongloop/loopback-next/commit/9b790ed)) + + +### Features + +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.2.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-soap-calculator@0.2.2...@loopback/example-soap-calculator@0.2.3) (2018-08-25) diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index 7dce630cf0bc..2c5484f7abbe 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-soap-calculator", - "version": "0.2.3", + "version": "0.3.0", "description": "Integrate a SOAP webservice with LoopBack 4", "keywords": [ "loopback", @@ -44,19 +44,19 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^0.12.6", - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/boot": "^0.13.0", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.6", - "@loopback/service-proxy": "^0.7.1", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/repository": "^0.16.0", + "@loopback/rest": "^0.20.0", + "@loopback/service-proxy": "^0.8.0", "loopback-connector-soap": "^4.2.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/mocha": "^5.0.0", "@types/node": "^10.1.1", "mocha": "^5.1.1", diff --git a/examples/todo-list/CHANGELOG.md b/examples/todo-list/CHANGELOG.md index 5a2b9dbf8b39..1ed69a7b6900 100644 --- a/examples/todo-list/CHANGELOG.md +++ b/examples/todo-list/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. + +## [0.3.4](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@0.3.3...@loopback/example-todo-list@0.3.4) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) + + + + + ## [0.3.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo-list@0.3.2...@loopback/example-todo-list@0.3.3) (2018-08-25) diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index 9b56c7744aa8..6dc735cd442f 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo-list", - "version": "0.3.3", + "version": "0.3.4", "description": "Continuation of the todo example using relations in LoopBack 4.", "main": "index.js", "engines": { @@ -37,21 +37,21 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^0.12.6", - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/boot": "^0.13.0", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.6", - "@loopback/service-proxy": "^0.7.1", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/openapi-v3-types": "^0.9.0", + "@loopback/repository": "^0.16.0", + "@loopback/rest": "^0.20.0", + "@loopback/service-proxy": "^0.8.0", "loopback-connector-rest": "^3.1.1" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/http-caching-proxy": "^0.3.5", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/http-caching-proxy": "^0.3.6", + "@loopback/testlab": "^0.12.0", "@types/lodash": "^4.14.109", "@types/node": "^10.1.1", "lodash": "^4.17.10" diff --git a/examples/todo/CHANGELOG.md b/examples/todo/CHANGELOG.md index 1ddf137150f8..fef68f7bc715 100644 --- a/examples/todo/CHANGELOG.md +++ b/examples/todo/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. + +# [0.17.0](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@0.16.3...@loopback/example-todo@0.17.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) + + +### Features + +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.16.3](https://github.com/strongloop/loopback-next/compare/@loopback/example-todo@0.16.2...@loopback/example-todo@0.16.3) (2018-08-25) diff --git a/examples/todo/package.json b/examples/todo/package.json index 6fdf55cb5e0b..876cd8e47105 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/example-todo", - "version": "0.16.3", + "version": "0.17.0", "description": "Tutorial example on how to build an application with LoopBack 4.", "main": "index.js", "engines": { @@ -37,21 +37,21 @@ }, "license": "MIT", "dependencies": { - "@loopback/boot": "^0.12.6", - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/boot": "^0.13.0", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.6", - "@loopback/service-proxy": "^0.7.1", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/openapi-v3-types": "^0.9.0", + "@loopback/repository": "^0.16.0", + "@loopback/rest": "^0.20.0", + "@loopback/service-proxy": "^0.8.0", "loopback-connector-rest": "^3.1.1" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/http-caching-proxy": "^0.3.5", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/http-caching-proxy": "^0.3.6", + "@loopback/testlab": "^0.12.0", "@types/lodash": "^4.14.109", "@types/node": "^10.1.1", "lodash": "^4.17.10" diff --git a/packages/authentication/CHANGELOG.md b/packages/authentication/CHANGELOG.md index 0c16d249c6f7..7829e42afa15 100644 --- a/packages/authentication/CHANGELOG.md +++ b/packages/authentication/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.11.7](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@0.11.6...@loopback/authentication@0.11.7) (2018-09-08) + +**Note:** Version bump only for package @loopback/authentication + + + + + ## [0.11.6](https://github.com/strongloop/loopback-next/compare/@loopback/authentication@0.11.5...@loopback/authentication@0.11.6) (2018-08-25) diff --git a/packages/authentication/package.json b/packages/authentication/package.json index 368859306d37..9e62b0023f6d 100644 --- a/packages/authentication/package.json +++ b/packages/authentication/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/authentication", - "version": "0.11.6", + "version": "0.11.7", "description": "A LoopBack component for authentication support.", "engines": { "node": ">=8.9" @@ -23,19 +23,19 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/metadata": "^0.9.5", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/rest": "^0.19.6", + "@loopback/metadata": "^0.9.6", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/rest": "^0.20.0", "passport": "^0.4.0", "passport-strategy": "^1.0.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/openapi-spec-builder": "^0.8.5", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/openapi-spec-builder": "^0.8.6", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1", "@types/passport": "^0.4.4", "@types/passport-http": "^0.3.6", diff --git a/packages/boot/CHANGELOG.md b/packages/boot/CHANGELOG.md index ae8e2632ff01..3096d907e4b5 100644 --- a/packages/boot/CHANGELOG.md +++ b/packages/boot/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.13.0](https://github.com/strongloop/loopback-next/compare/@loopback/boot@0.12.6...@loopback/boot@0.13.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) + + +### Features + +* **boot:** add debug logs for better troubleshooting ([cdb63b7](https://github.com/strongloop/loopback-next/commit/cdb63b7)) +* **boot:** implement Service booter ([bf8e9c8](https://github.com/strongloop/loopback-next/commit/bf8e9c8)) +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.12.6](https://github.com/strongloop/loopback-next/compare/@loopback/boot@0.12.5...@loopback/boot@0.12.6) (2018-08-25) diff --git a/packages/boot/package.json b/packages/boot/package.json index a30fdbeb9800..b1d7c7208e5b 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/boot", - "version": "0.12.6", + "version": "0.13.0", "description": "A collection of Booters for LoopBack 4 Applications", "engines": { "node": ">=8.9" @@ -26,21 +26,21 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", - "@loopback/repository": "^0.15.1", - "@loopback/service-proxy": "^0.7.1", + "@loopback/repository": "^0.16.0", + "@loopback/service-proxy": "^0.8.0", "@types/debug": "0.0.30", "@types/glob": "^5.0.35", "debug": "^3.1.0", "glob": "^7.1.2" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/rest": "^0.19.6", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/rest": "^0.20.0", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1" }, "files": [ diff --git a/packages/build/CHANGELOG.md b/packages/build/CHANGELOG.md index ff55d4172d5d..adb4a9c16778 100644 --- a/packages/build/CHANGELOG.md +++ b/packages/build/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.7.2](https://github.com/strongloop/loopback-next/compare/@loopback/build@0.7.1...@loopback/build@0.7.2) (2018-09-08) + +**Note:** Version bump only for package @loopback/build + + + + + ## [0.7.1](https://github.com/strongloop/loopback-next/compare/@loopback/build@0.7.0...@loopback/build@0.7.1) (2018-08-24) diff --git a/packages/build/package.json b/packages/build/package.json index 32bdbd0b8d4f..2408f7f11eae 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": "0.7.1", + "version": "0.7.2", "engines": { "node": ">=8.9" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ed3a8e967392..42c1bb1d43b6 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/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. + +# [0.22.0](https://github.com/strongloop/loopback-next/compare/@loopback/cli@0.21.4...@loopback/cli@0.22.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) +* **cli:** rename repository/service feature flags ([c089299](https://github.com/strongloop/loopback-next/commit/c089299)) + + +### Features + +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.21.4](https://github.com/strongloop/loopback-next/compare/@loopback/cli@0.21.3...@loopback/cli@0.21.4) (2018-08-25) diff --git a/packages/cli/package.json b/packages/cli/package.json index e8111bee90d6..91ffe3d083b7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/cli", - "version": "0.21.4", + "version": "0.22.0", "description": "Yeoman generator for LoopBack 4", "homepage": "https://github.com/strongloop/loopback-next/tree/master/packages/cli", "author": { @@ -24,8 +24,8 @@ "yeoman-generator" ], "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/ejs": "^2.6.0", "@types/node": "^10.1.1", "glob": "^7.1.2", @@ -86,33 +86,33 @@ "strong-docs": "^4.0.0", "tslint": "^5.9.1", "typescript": "^3.0.1", - "@loopback/authentication": "^0.11.6", - "@loopback/boot": "^0.12.6", - "@loopback/build": "^0.7.1", - "@loopback/cli": "^0.21.4", - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", - "@loopback/metadata": "^0.9.5", - "@loopback/openapi-spec-builder": "^0.8.5", - "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/repository-json-schema": "^0.10.6", - "@loopback/repository": "^0.15.1", - "@loopback/rest": "^0.19.6", - "@loopback/testlab": "^0.11.5", - "@loopback/docs": "^0.16.4", + "@loopback/authentication": "^0.11.7", + "@loopback/boot": "^0.13.0", + "@loopback/build": "^0.7.2", + "@loopback/cli": "^0.22.0", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", + "@loopback/metadata": "^0.9.6", + "@loopback/openapi-spec-builder": "^0.8.6", + "@loopback/openapi-v3-types": "^0.9.0", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/repository-json-schema": "^0.10.7", + "@loopback/repository": "^0.16.0", + "@loopback/rest": "^0.20.0", + "@loopback/testlab": "^0.12.0", + "@loopback/docs": "^0.17.0", "glob": "^7.1.2", - "@loopback/example-hello-world": "^0.9.6", - "@loopback/example-log-extension": "^0.11.6", - "@loopback/example-rpc-server": "^0.11.2", - "@loopback/example-todo": "^0.16.3", - "@loopback/example-soap-calculator": "^0.2.3", + "@loopback/example-hello-world": "^0.9.7", + "@loopback/example-log-extension": "^0.11.7", + "@loopback/example-rpc-server": "^0.11.3", + "@loopback/example-todo": "^0.17.0", + "@loopback/example-soap-calculator": "^0.3.0", "@loopback/dist-util": "^0.3.6", "@loopback/service-proxy-proxy": "^0.4.0", - "@loopback/service-proxy": "^0.7.1", - "@loopback/http-caching-proxy": "^0.3.5", - "@loopback/http-server": "^0.3.5", - "@loopback/example-todo-list": "^0.3.3" + "@loopback/service-proxy": "^0.8.0", + "@loopback/http-caching-proxy": "^0.3.6", + "@loopback/http-server": "^0.3.6", + "@loopback/example-todo-list": "^0.3.4" } }, "copyright.owner": "IBM Corp.", diff --git a/packages/context/CHANGELOG.md b/packages/context/CHANGELOG.md index b9f72f86bd2e..4a2217073576 100644 --- a/packages/context/CHANGELOG.md +++ b/packages/context/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.12.6](https://github.com/strongloop/loopback-next/compare/@loopback/context@0.12.5...@loopback/context@0.12.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/context + + + + + ## [0.12.5](https://github.com/strongloop/loopback-next/compare/@loopback/context@0.12.4...@loopback/context@0.12.5) (2018-08-24) diff --git a/packages/context/package.json b/packages/context/package.json index 40511a58a051..2791d96b2015 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/context", - "version": "0.12.5", + "version": "0.12.6", "description": "LoopBack's container for Inversion of Control", "engines": { "node": ">=8.9" @@ -23,13 +23,13 @@ "license": "MIT", "dependencies": { "@loopback/dist-util": "^0.3.6", - "@loopback/metadata": "^0.9.5", + "@loopback/metadata": "^0.9.6", "debug": "^3.1.0", "uuid": "^3.2.1" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/bluebird": "^3.5.20", "@types/debug": "^0.0.30", "@types/node": "^10.1.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e46b39eb6866..15e2718d7e3e 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.11.6](https://github.com/strongloop/loopback-next/compare/@loopback/core@0.11.5...@loopback/core@0.11.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/core + + + + + ## [0.11.5](https://github.com/strongloop/loopback-next/compare/@loopback/core@0.11.4...@loopback/core@0.11.5) (2018-08-24) diff --git a/packages/core/package.json b/packages/core/package.json index a88f2ac293ce..8a3bb210e1ed 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/core", - "version": "0.11.5", + "version": "0.11.6", "description": "", "engines": { "node": ">=8.9" @@ -23,12 +23,12 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^0.12.5", + "@loopback/context": "^0.12.6", "@loopback/dist-util": "^0.3.6" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1" }, "files": [ diff --git a/packages/http-caching-proxy/CHANGELOG.md b/packages/http-caching-proxy/CHANGELOG.md index 2ad57c8da054..c9c60e5c8d49 100644 --- a/packages/http-caching-proxy/CHANGELOG.md +++ b/packages/http-caching-proxy/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.6](https://github.com/strongloop/loopback-next/compare/@loopback/http-caching-proxy@0.3.5...@loopback/http-caching-proxy@0.3.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/http-caching-proxy + + + + + ## [0.3.5](https://github.com/strongloop/loopback-next/compare/@loopback/http-caching-proxy@0.3.4...@loopback/http-caching-proxy@0.3.5) (2018-08-24) diff --git a/packages/http-caching-proxy/package.json b/packages/http-caching-proxy/package.json index c2807164801b..e4855a4de9e5 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": "0.3.5", + "version": "0.3.6", "description": "A caching HTTP proxy for integration tests. NOT SUITABLE FOR PRODUCTION USE!", "engines": { "node": ">=8.9" @@ -29,8 +29,8 @@ "rimraf": "^2.6.2" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/debug": "^0.0.30", "@types/node": "^10.1.1", "@types/p-event": "^1.3.0", diff --git a/packages/http-server/CHANGELOG.md b/packages/http-server/CHANGELOG.md index 13d651c6af32..d4d289750311 100644 --- a/packages/http-server/CHANGELOG.md +++ b/packages/http-server/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.6](https://github.com/strongloop/loopback-next/compare/@loopback/http-server@0.3.5...@loopback/http-server@0.3.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/http-server + + + + + ## [0.3.5](https://github.com/strongloop/loopback-next/compare/@loopback/http-server@0.3.4...@loopback/http-server@0.3.5) (2018-08-24) diff --git a/packages/http-server/package.json b/packages/http-server/package.json index d9809feba427..aea122af2cbd 100644 --- a/packages/http-server/package.json +++ b/packages/http-server/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/http-server", - "version": "0.3.5", + "version": "0.3.6", "description": "A wrapper for creating HTTP/HTTPS servers", "engines": { "node": ">=8.9" @@ -24,9 +24,9 @@ "p-event": "^2.0.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/core": "^0.11.5", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/core": "^0.11.6", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.2", "@types/p-event": "^1.3.0", "@types/request-promise-native": "^1.0.15", diff --git a/packages/metadata/CHANGELOG.md b/packages/metadata/CHANGELOG.md index 53a627778db8..b7e413bdbb8d 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.9.6](https://github.com/strongloop/loopback-next/compare/@loopback/metadata@0.9.5...@loopback/metadata@0.9.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/metadata + + + + + ## [0.9.5](https://github.com/strongloop/loopback-next/compare/@loopback/metadata@0.9.4...@loopback/metadata@0.9.5) (2018-08-24) diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 81c99c1652bb..7e3c777d0adc 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/metadata", - "version": "0.9.5", + "version": "0.9.6", "description": "LoopBack's metadata utilities for reflection and decoration", "engines": { "node": ">=8.9" @@ -27,9 +27,9 @@ "reflect-metadata": "^0.1.10" }, "devDependencies": { - "@loopback/build": "^0.7.1", + "@loopback/build": "^0.7.2", "@loopback/dist-util": "^0.3.6", - "@loopback/testlab": "^0.11.5", + "@loopback/testlab": "^0.12.0", "@types/debug": "^0.0.30", "@types/lodash": "^4.14.106", "@types/node": "^10.1.1" diff --git a/packages/openapi-spec-builder/CHANGELOG.md b/packages/openapi-spec-builder/CHANGELOG.md index f2be624f953b..acd3693d2380 100644 --- a/packages/openapi-spec-builder/CHANGELOG.md +++ b/packages/openapi-spec-builder/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.8.6](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-spec-builder@0.8.5...@loopback/openapi-spec-builder@0.8.6) (2018-09-08) + +**Note:** Version bump only for package @loopback/openapi-spec-builder + + + + + ## [0.8.5](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-spec-builder@0.8.4...@loopback/openapi-spec-builder@0.8.5) (2018-08-24) diff --git a/packages/openapi-spec-builder/package.json b/packages/openapi-spec-builder/package.json index bb05cfbd7996..a150e1b60f52 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": "0.8.5", + "version": "0.8.6", "description": "Make it easy to create OpenAPI (Swagger) specification documents in your tests using the builder pattern.", "engines": { "node": ">=8.9" @@ -26,10 +26,10 @@ ], "dependencies": { "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3-types": "^0.8.5" + "@loopback/openapi-v3-types": "^0.9.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", + "@loopback/build": "^0.7.2", "@types/node": "^10.1.1" }, "files": [ diff --git a/packages/openapi-v3-types/CHANGELOG.md b/packages/openapi-v3-types/CHANGELOG.md index a7fcb449ecea..38eb93d7c2b7 100644 --- a/packages/openapi-v3-types/CHANGELOG.md +++ b/packages/openapi-v3-types/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. + +# [0.9.0](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3-types@0.8.5...@loopback/openapi-v3-types@0.9.0) (2018-09-08) + + +### Features + +* coerce object arguments from query strings ([d095693](https://github.com/strongloop/loopback-next/commit/d095693)) + + + + + ## [0.8.5](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3-types@0.8.4...@loopback/openapi-v3-types@0.8.5) (2018-08-24) diff --git a/packages/openapi-v3-types/package.json b/packages/openapi-v3-types/package.json index 907ecc6f7e34..c1dd03f39d95 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": "0.8.5", + "version": "0.9.0", "description": "TypeScript type definitions for OpenAPI Specifications.", "engines": { "node": ">=8.9" @@ -10,8 +10,8 @@ "openapi3-ts": "^1.0.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1" }, "scripts": { diff --git a/packages/openapi-v3/CHANGELOG.md b/packages/openapi-v3/CHANGELOG.md index b145e19ab91e..94c11e4d0c40 100644 --- a/packages/openapi-v3/CHANGELOG.md +++ b/packages/openapi-v3/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. + +# [0.13.0](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@0.12.6...@loopback/openapi-v3@0.13.0) (2018-09-08) + + +### Features + +* coerce object arguments from query strings ([d095693](https://github.com/strongloop/loopback-next/commit/d095693)) + + + + + ## [0.12.6](https://github.com/strongloop/loopback-next/compare/@loopback/openapi-v3@0.12.5...@loopback/openapi-v3@0.12.6) (2018-08-25) diff --git a/packages/openapi-v3/package.json b/packages/openapi-v3/package.json index 5ddeead9e225..5c725a7c3170 100644 --- a/packages/openapi-v3/package.json +++ b/packages/openapi-v3/package.json @@ -1,15 +1,15 @@ { "name": "@loopback/openapi-v3", - "version": "0.12.6", + "version": "0.13.0", "description": "Processes openapi v3 related metadata", "engines": { "node": ">=8.9" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/openapi-spec-builder": "^0.8.5", - "@loopback/repository": "^0.15.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/openapi-spec-builder": "^0.8.6", + "@loopback/repository": "^0.16.0", + "@loopback/testlab": "^0.12.0", "@types/debug": "0.0.30", "@types/lodash": "^4.14.106", "@types/node": "^10.1.1" @@ -51,10 +51,10 @@ "url": "https://github.com/strongloop/loopback-next.git" }, "dependencies": { - "@loopback/context": "^0.12.5", + "@loopback/context": "^0.12.6", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-v3-types": "^0.8.5", - "@loopback/repository-json-schema": "^0.10.6", + "@loopback/openapi-v3-types": "^0.9.0", + "@loopback/repository-json-schema": "^0.10.7", "debug": "^3.1.0", "lodash": "^4.17.5" } diff --git a/packages/repository-json-schema/CHANGELOG.md b/packages/repository-json-schema/CHANGELOG.md index f567ad8eb441..bef812b09082 100644 --- a/packages/repository-json-schema/CHANGELOG.md +++ b/packages/repository-json-schema/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.10.7](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@0.10.6...@loopback/repository-json-schema@0.10.7) (2018-09-08) + +**Note:** Version bump only for package @loopback/repository-json-schema + + + + + ## [0.10.6](https://github.com/strongloop/loopback-next/compare/@loopback/repository-json-schema@0.10.5...@loopback/repository-json-schema@0.10.6) (2018-08-25) diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index 01bb2ee73ec6..37e944dff42c 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": "0.10.6", + "version": "0.10.7", "description": "Converts TS classes into JSON Schemas using TypeScript's reflection API", "engines": { "node": ">=8.9" @@ -27,15 +27,15 @@ "access": "public" }, "dependencies": { - "@loopback/context": "^0.12.5", + "@loopback/context": "^0.12.6", "@loopback/dist-util": "^0.3.6", - "@loopback/metadata": "^0.9.5", - "@loopback/repository": "^0.15.1", + "@loopback/metadata": "^0.9.6", + "@loopback/repository": "^0.16.0", "@types/json-schema": "^6.0.1" }, "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/node": "^10.1.1", "ajv": "^6.5.0" }, diff --git a/packages/repository/CHANGELOG.md b/packages/repository/CHANGELOG.md index 993af9651a34..edfbb2d9ebda 100644 --- a/packages/repository/CHANGELOG.md +++ b/packages/repository/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. + +# [0.16.0](https://github.com/strongloop/loopback-next/compare/@loopback/repository@0.15.1...@loopback/repository@0.16.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) + + +### Features + +* default 404 for request to non-existent resource ([f68a45c](https://github.com/strongloop/loopback-next/commit/f68a45c)) +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.15.1](https://github.com/strongloop/loopback-next/compare/@loopback/repository@0.15.0...@loopback/repository@0.15.1) (2018-08-24) diff --git a/packages/repository/package.json b/packages/repository/package.json index f47d3052fed2..c89932729640 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/repository", - "version": "0.15.1", + "version": "0.16.0", "description": "Repository based persistence for LoopBack 4", "engines": { "node": ">=8.9" @@ -22,14 +22,14 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5", + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0", "@types/lodash": "^4.14.108", "@types/node": "^10.1.1" }, "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", "lodash": "^4.17.10", "loopback-datasource-juggler": "^3.23.0" diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index 145a33d102e2..9386d9d59a34 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.20.0](https://github.com/strongloop/loopback-next/compare/@loopback/rest@0.19.6...@loopback/rest@0.20.0) (2018-09-08) + + +### Bug Fixes + +* remove extra imports for mixin dependencies ([35b916b](https://github.com/strongloop/loopback-next/commit/35b916b)) +* **rest:** use strong-error-handler for writing errors to the response body ([ac011f8](https://github.com/strongloop/loopback-next/commit/ac011f8)) + + +### Features + +* **rest:** allow static assets to be served by a rest server ([a1cefcc](https://github.com/strongloop/loopback-next/commit/a1cefcc)) +* coerce object arguments from query strings ([d095693](https://github.com/strongloop/loopback-next/commit/d095693)) + + + + + ## [0.19.6](https://github.com/strongloop/loopback-next/compare/@loopback/rest@0.19.5...@loopback/rest@0.19.6) (2018-08-25) diff --git a/packages/rest/package.json b/packages/rest/package.json index 04415fba0766..726e156e5a01 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/rest", - "version": "0.19.6", + "version": "0.20.0", "description": "", "engines": { "node": ">=8.9" @@ -23,11 +23,11 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", - "@loopback/http-server": "^0.3.5", - "@loopback/openapi-v3": "^0.12.6", - "@loopback/openapi-v3-types": "^0.8.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", + "@loopback/http-server": "^0.3.6", + "@loopback/openapi-v3": "^0.13.0", + "@loopback/openapi-v3-types": "^0.9.0", "@types/cors": "^2.8.3", "@types/express": "^4.11.1", "@types/http-errors": "^1.6.1", @@ -49,11 +49,11 @@ "validator": "^10.4.0" }, "devDependencies": { - "@loopback/build": "^0.7.1", + "@loopback/build": "^0.7.2", "@loopback/dist-util": "^0.3.6", - "@loopback/openapi-spec-builder": "^0.8.5", - "@loopback/repository": "^0.15.1", - "@loopback/testlab": "^0.11.5", + "@loopback/openapi-spec-builder": "^0.8.6", + "@loopback/repository": "^0.16.0", + "@loopback/testlab": "^0.12.0", "@types/debug": "0.0.30", "@types/js-yaml": "^3.11.1", "@types/lodash": "^4.14.106", diff --git a/packages/service-proxy/CHANGELOG.md b/packages/service-proxy/CHANGELOG.md index 67f0abb1b882..241cc51bc390 100644 --- a/packages/service-proxy/CHANGELOG.md +++ b/packages/service-proxy/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. + +# [0.8.0](https://github.com/strongloop/loopback-next/compare/@loopback/service-proxy@0.7.1...@loopback/service-proxy@0.8.0) (2018-09-08) + + +### Features + +* **service-proxy:** add service mixin ([fb01931](https://github.com/strongloop/loopback-next/commit/fb01931)) + + + + + ## [0.7.1](https://github.com/strongloop/loopback-next/compare/@loopback/service-proxy@0.7.0...@loopback/service-proxy@0.7.1) (2018-08-24) diff --git a/packages/service-proxy/package.json b/packages/service-proxy/package.json index 2a7b3bf9f514..a8baf525d9f0 100644 --- a/packages/service-proxy/package.json +++ b/packages/service-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/service-proxy", - "version": "0.7.1", + "version": "0.8.0", "description": "Service integration for LoopBack 4", "engines": { "node": ">=8.9" @@ -27,12 +27,12 @@ "copyright.owner": "IBM Corp.", "license": "MIT", "devDependencies": { - "@loopback/build": "^0.7.1", - "@loopback/testlab": "^0.11.5" + "@loopback/build": "^0.7.2", + "@loopback/testlab": "^0.12.0" }, "dependencies": { - "@loopback/context": "^0.12.5", - "@loopback/core": "^0.11.5", + "@loopback/context": "^0.12.6", + "@loopback/core": "^0.11.6", "@loopback/dist-util": "^0.3.6", "loopback-datasource-juggler": "^3.23.0" }, diff --git a/packages/testlab/CHANGELOG.md b/packages/testlab/CHANGELOG.md index 7355623ee3e8..109e9472ce48 100644 --- a/packages/testlab/CHANGELOG.md +++ b/packages/testlab/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. + +# [0.12.0](https://github.com/strongloop/loopback-next/compare/@loopback/testlab@0.11.5...@loopback/testlab@0.12.0) (2018-09-08) + + +### Features + +* **testlab:** expose "sandbox.path" property ([1445ebd](https://github.com/strongloop/loopback-next/commit/1445ebd)) + + + + + ## [0.11.5](https://github.com/strongloop/loopback-next/compare/@loopback/testlab@0.11.4...@loopback/testlab@0.11.5) (2018-08-24) diff --git a/packages/testlab/package.json b/packages/testlab/package.json index 6569d4b0b2a8..02124e3c4038 100644 --- a/packages/testlab/package.json +++ b/packages/testlab/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/testlab", - "version": "0.11.5", + "version": "0.12.0", "description": "A collection of test utilities we use to write LoopBack tests.", "engines": { "node": ">=8.9" @@ -34,7 +34,7 @@ "swagger2openapi": "^2.11.16" }, "devDependencies": { - "@loopback/build": "^0.7.1", + "@loopback/build": "^0.7.2", "@types/node": "^10.1.1" }, "files": [ diff --git a/sandbox/example/CHANGELOG.md b/sandbox/example/CHANGELOG.md new file mode 100644 index 000000000000..5c3a6d30ee50 --- /dev/null +++ b/sandbox/example/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## 0.1.1 (2018-09-08) + +**Note:** Version bump only for package @loopback/sandbox-example diff --git a/sandbox/example/package.json b/sandbox/example/package.json index eb8b28712d02..448161f4fc7c 100644 --- a/sandbox/example/package.json +++ b/sandbox/example/package.json @@ -1,6 +1,6 @@ { "name": "@loopback/sandbox-example", - "version": "0.1.0", + "version": "0.1.1", "description": "Sample project for sandbox", "main": "index.js", "private": true,