diff --git a/CODEOWNERS b/CODEOWNERS index 275ac65f9f77..9dbc3c22874d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -307,6 +307,14 @@ # - Standby owner(s): @deepakrkris /extensions/apiconnect @raymondfeng @deepakrkris +# +# TypeORM +# +# - Issue label: TypeORM +# - Primary owner(s): @hacksparrow +# - Standby owner(s): n/a +/extensions/typeorm @hacksparrow + # # Build & internal tooling # diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index 820c21ed18d8..be982551694b 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -44,7 +44,8 @@ one in the monorepo: `npm run update-monorepo-file` | [extensions/health](https://github.com/strongloop/loopback-next/tree/master/extensions/health) | @loopback/extension-health | An extension exposes health check related endpoints with LoopBack 4 | | [extensions/logging](https://github.com/strongloop/loopback-next/tree/master/extensions/logging) | @loopback/extension-logging | An extension exposes logging for Winston and Fluentd with LoopBack 4 | | [extensions/metrics](https://github.com/strongloop/loopback-next/tree/master/extensions/metrics) | @loopback/extension-metrics | An extension exposes metrics for Prometheus with LoopBack 4 | -| [fixtures/mock-oauth2-provider](https://github.com/strongloop/loopback-next/tree/master/fixtures/mock-oauth2-provider) | _(private)_ | An internal application to mock the OAuth2 authorization flow login with a social app like facebook, google etc | +| [extensions/typeorm](https://github.com/strongloop/loopback-next/tree/master/extensions/typeorm) | @loopback/typeorm | Adds support for TypeORM in LoopBack | +| [fixtures/mock-oauth2-provider](https://github.com/strongloop/loopback-next/tree/master/fixtures/mock-oauth2-provider) | @loopback/mock-oauth2-provider | An internal application to mock the OAuth2 authorization flow login with a social app like facebook, google etc | | [fixtures/tsdocs-monorepo](https://github.com/strongloop/loopback-next/tree/master/fixtures/tsdocs-monorepo) | _(private)_ | A monorepo for tsdocs testing | | [packages/authentication](https://github.com/strongloop/loopback-next/tree/master/packages/authentication) | @loopback/authentication | A LoopBack component for authentication support. | | [packages/authorization](https://github.com/strongloop/loopback-next/tree/master/packages/authorization) | @loopback/authorization | A LoopBack component for authorization support. | diff --git a/docs/site/Using-TypeORM-with-LoopBack.md b/docs/site/Using-TypeORM-with-LoopBack.md new file mode 100644 index 000000000000..0d8397ef84e4 --- /dev/null +++ b/docs/site/Using-TypeORM-with-LoopBack.md @@ -0,0 +1,10 @@ +--- +lang: en +title: 'How to use TypeORM with LoopBack' +keywords: LoopBack 4.0, LoopBack 4, TypeORM +sidebar: lb4_sidebar +layout: readme +source: loopback-next +file: extensions/typeorm/README.md +permalink: /doc/en/lb4/Using-typeorm-with-loopback.html +--- diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index 9237d98f0259..e96fe4d76d03 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -242,6 +242,13 @@ children: url: Database-migrations.html output: 'web, pdf' + - title: 'Using TypeORM' + output: 'web, pdf' + children: + - title: 'How to use TypeORM with LoopBack' + url: Using-typeorm-with-loopback.html + output: 'web, pdf' + - title: 'Running and debugging apps' url: Running-and-debugging-apps.html output: 'web, pdf' diff --git a/extensions/typeorm/CHANGELOG.md b/extensions/typeorm/CHANGELOG.md new file mode 100644 index 000000000000..767840ada8e0 --- /dev/null +++ b/extensions/typeorm/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + diff --git a/extensions/typeorm/LICENSE b/extensions/typeorm/LICENSE new file mode 100644 index 000000000000..54ccddfe6b9e --- /dev/null +++ b/extensions/typeorm/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/typeorm +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extensions/typeorm/README.md b/extensions/typeorm/README.md new file mode 100644 index 000000000000..546a70a0cde2 --- /dev/null +++ b/extensions/typeorm/README.md @@ -0,0 +1,163 @@ +# @loopback/typeorm + +This module enables TypeORM support in LoopBack. For pending features, refer to +the [Limitations](#limitations) section below. + +## Overview + +[TypeORM](https://typeorm.io/) is a TypeScript ORM for Node.js. It supports many +databases and can be used an as alternative to LoopBack's Juggler ORM. + +## Stability: ⚠️Experimental⚠️ + +> Experimental packages provide early access to advanced or experimental +> functionality to get community feedback. Such modules are published to npm +> using `0.x.y` versions. Their APIs and functionality may be subject to +> breaking changes in future releases. + +## Installation + +```sh +npm install --save @loopback/typeorm +``` + +## Basic Use + +### Enabling TypeORM support + +To enable TypeORM support, import `TypeOrmMixin` from `@loopback/typeorm` and +apply it to your application class as shown below. + +```ts +import {BootMixin} from '@loopback/boot'; +import {RestApplication} from '@loopback/rest'; +import {TypeOrmMixin} from '@loopback/typeorm'; +export class MyApplication extends BootMixin(TypeOrmMixin(RestApplication)) { + ... +} +``` + +### Creating Connections + +[Connections](https://typeorm.io/#/connection) are equivalent to LoopBack's +datasources. They contain the connectivity and other details about the +databases. Define the connection in files with a `.connection.ts` extension and +keep them in the `typeorm-connections` directory of your project. + +```ts +// src/connections/sqlite.connection.ts +import path from 'path'; +import {ConnectionOptions} from '@loopback/typeorm'; +import {Book} from '../entities/'; + +export const SqliteConnection: ConnectionOptions = { + name: 'SQLite', + type: 'sqlite', + database: './mydb.sql', + entities: [Book], + synchronize: true, +}; +``` + +Make sure to install the underlying database driver. For example, if you are +using SQlite, you'll need to install `sqlite3`. + +```sh +npm install sqlite3 +``` + +Refer to the +[TypeORM documentation](https://github.com/typeorm/typeorm#installation) for the +supported databases and the underlying drivers. + +### Creating Entities + +[Entities](https://typeorm.io/#/entities) are equivalent to LoopBack's Models. +Define the entities as usual and keep them in a directory named +`typeorm-entities`. + +```ts +// src/typeorm-entities/book.entity.ts +import {Entity, Column, PrimaryColumn} from 'typeorm'; +@Entity() +export class Photo { + @PrimaryColumn() + id: number; + + @Column() + title: string; + + @Column() + isPublished: boolean; +} +``` + +### Creating Controllers + +Controllers continue to work as usual. And you don't have to create repositories +since TypeORM creates them for you; just inject them in the controllers. The +repository API is 100% TypeORM +[repository API](https://typeorm.io/#/repository-api). + +```ts +// src/controllers/book.controller.ts +import {get, post, Request, requestBody} from '@loopback/rest'; +import {getModelSchema, Repository, typeorm} from '@loopback/typeorm'; +import {Book} from '../typeorm-entities'; + +export class BookController { + @typeorm.repository(Book) private bookRepo: Repository; + + constructor() {} + + // Create a new book + @post('/book') + async create(@requestBody() data: Book) { + const book = new Book(); + book.title = data.title; + book.published = false; + return await this.bookRepo.save(book); + } + + // Find book by title + @get('/note/{title}') + async findByTitle(@param.path.string('title') title: string) { + return await this.bookRepo.find({title}); + } +} +``` + +## Limitations + +Please note, the current implementation does not support the following: + +1. [Complete TypeORM to OpenAPI data type conversion](https://github.com/strongloop/loopback-next/issues/5893) + (currently only `number`, `string`, and `boolean` are supported) +2. [Full JSON/OpenAPI schema for entities](https://github.com/strongloop/loopback-next/issues/5894), + including variants like with/without id, with/without relations, partial, + etc. +3. [Support for LoopBack-style filters](https://github.com/strongloop/loopback-next/issues/5895) +4. [JSON/OpenAPI schema to describe the supported filter format](https://github.com/strongloop/loopback-next/issues/5896) +5. [Custom repository classes](https://github.com/strongloop/loopback-next/issues/5897) + (e.g. to implement bookRepo.findByTitle(title)). +6. [Database migration](https://github.com/strongloop/loopback-next/issues/5898) + +Community contribution is welcome. + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/extensions/typeorm/package-lock.json b/extensions/typeorm/package-lock.json new file mode 100644 index 000000000000..ef9f0e764fe2 --- /dev/null +++ b/extensions/typeorm/package-lock.json @@ -0,0 +1,1323 @@ +{ + "name": "@loopback/typeorm", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz", + "integrity": "sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==", + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, + "@types/node": { + "version": "10.17.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.26.tgz", + "integrity": "sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "cli-highlight": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.4.tgz", + "integrity": "sha512-s7Zofobm20qriqDoU9sXptQx0t2R9PEgac92mENNm7xaEe1hn71IIMsXMK+6encA6WRCWWxIGQbipr3q998tlQ==", + "requires": { + "chalk": "^3.0.0", + "highlight.js": "^9.6.0", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^5.1.1", + "yargs": "^15.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.0.tgz", + "integrity": "sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^3.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + } + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz", + "integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==", + "requires": { + "xregexp": "^4.2.4" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true + }, + "dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "figlet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.4.0.tgz", + "integrity": "sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "highlight.js": { + "version": "9.18.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", + "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true + }, + "needle": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", + "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "dev": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "parse5-htmlparser2-tree-adapter": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-5.1.1.tgz", + "integrity": "sha512-CF+TKjXqoqyDwHqBhFQ+3l5t83xYi6fVT1tQNg+Ye0JRLnTxWvIroCjEp1A0k4lneHNBGnICUf0cfYVYGEazqw==", + "requires": { + "parse5": "^5.1.1" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sqlite3": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", + "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", + "dev": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.11.0" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "tslib": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", + "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + }, + "typeorm": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.25.tgz", + "integrity": "sha512-yzQ995fyDy5wolSLK9cmjUNcmQdixaeEm2TnXB5HN++uKbs9TiR6Y7eYAHpDlAE8s9J1uniDBgytecCZVFergQ==", + "requires": { + "app-root-path": "^3.0.0", + "buffer": "^5.1.0", + "chalk": "^2.4.2", + "cli-highlight": "^2.0.0", + "debug": "^4.1.1", + "dotenv": "^6.2.0", + "glob": "^7.1.2", + "js-yaml": "^3.13.1", + "mkdirp": "^1.0.3", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^1.9.0", + "xml2js": "^0.4.17", + "yargonaut": "^1.1.2", + "yargs": "^13.2.1" + }, + "dependencies": { + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xregexp": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", + "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "requires": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + } + } + } + } +} diff --git a/extensions/typeorm/package.json b/extensions/typeorm/package.json new file mode 100644 index 000000000000..b7fcae335d0a --- /dev/null +++ b/extensions/typeorm/package.json @@ -0,0 +1,52 @@ +{ + "name": "@loopback/typeorm", + "version": "0.0.1", + "description": "Adds support for TypeORM in LoopBack", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=10.6" + }, + "scripts": { + "acceptance": "lb-mocha \"dist/__tests__/acceptance/**/*.js\"", + "build": "lb-tsc", + "clean": "lb-clean loopback-typeorm*.tgz dist *.tsbuildinfo package", + "pretest": "npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "verify": "npm pack && tar xf loopback-typeorm*.tgz && tree package && npm run clean" + }, + "author": "IBM Corp.", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@loopback/build": "^5.4.3", + "@loopback/eslint-config": "^8.0.1", + "@loopback/repository": "^2.7.0", + "@loopback/rest": "^5.1.1", + "@loopback/testlab": "^3.1.7", + "@types/json-schema": "^7.0.5", + "@types/node": "^10.17.26", + "sqlite3": "^4.2.0" + }, + "dependencies": { + "@loopback/boot": "^2.3.3", + "@loopback/core": "^2.8.0", + "@loopback/openapi-v3": "^3.4.3", + "tslib": "^2.0.0", + "typeorm": "^0.2.25" + }, + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "extensions/typeorm" + } +} diff --git a/extensions/typeorm/src/__tests__/fixtures/application.ts b/extensions/typeorm/src/__tests__/fixtures/application.ts new file mode 100644 index 000000000000..d4d4725dafa0 --- /dev/null +++ b/extensions/typeorm/src/__tests__/fixtures/application.ts @@ -0,0 +1,16 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// 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 {RestApplication} from '@loopback/rest'; +import {TypeOrmMixin} from '../../'; + +export class TypeOrmApp extends BootMixin(TypeOrmMixin(RestApplication)) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + } +} diff --git a/extensions/typeorm/src/__tests__/fixtures/controllers/book.controller.ts b/extensions/typeorm/src/__tests__/fixtures/controllers/book.controller.ts new file mode 100644 index 000000000000..c82f071660cf --- /dev/null +++ b/extensions/typeorm/src/__tests__/fixtures/controllers/book.controller.ts @@ -0,0 +1,59 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {get, param, post, requestBody} from '@loopback/rest'; +import {getModelSchema, Repository, typeorm} from '../../../'; +import {Book} from '../typeorm-entities/book.entity'; + +export class BookController { + constructor() {} + + @typeorm.repository(Book, 'my-db') private bookRepo: Repository; + + @post('/books', { + responses: { + '200': { + description: 'User model instance', + content: {'application/json': {schema: getModelSchema(Book)}}, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchema(Book, { + title: 'NewUser', + exclude: ['id'], + }), + }, + }, + }) + data: Omit, + ): Promise { + const bookEntity = new Book(); + bookEntity.title = data.title; + bookEntity.published = data.published; + return this.bookRepo.save(bookEntity); + } + + @get('/books/{id}', { + responses: { + '200': { + description: 'User model instance', + content: { + 'application/json': { + schema: getModelSchema(Book, {includeRelations: true}), + }, + }, + }, + }, + }) + async findById( + @param.path.string('id') id: string, + ): Promise { + return this.bookRepo.findOne(id); + } +} diff --git a/extensions/typeorm/src/__tests__/fixtures/typeorm-connections/sqlite.connection.ts b/extensions/typeorm/src/__tests__/fixtures/typeorm-connections/sqlite.connection.ts new file mode 100644 index 000000000000..480966c0208e --- /dev/null +++ b/extensions/typeorm/src/__tests__/fixtures/typeorm-connections/sqlite.connection.ts @@ -0,0 +1,16 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import path from 'path'; +import {ConnectionOptions} from '../../../'; +import {Book} from '../typeorm-entities/book.entity'; + +export const SqliteConnection: ConnectionOptions = { + name: 'my-db', + type: 'sqlite', + database: path.join(__dirname, './mydb.sql'), + entities: [Book], + synchronize: true, +}; diff --git a/extensions/typeorm/src/__tests__/fixtures/typeorm-entities/book.entity.ts b/extensions/typeorm/src/__tests__/fixtures/typeorm-entities/book.entity.ts new file mode 100644 index 000000000000..fbe426224b40 --- /dev/null +++ b/extensions/typeorm/src/__tests__/fixtures/typeorm-entities/book.entity.ts @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Column, Entity, PrimaryGeneratedColumn} from '../../../'; + +@Entity() +export class Book { + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @Column() + published: boolean; +} diff --git a/extensions/typeorm/src/__tests__/integration/booter.integration.ts b/extensions/typeorm/src/__tests__/integration/booter.integration.ts new file mode 100644 index 000000000000..b44661606580 --- /dev/null +++ b/extensions/typeorm/src/__tests__/integration/booter.integration.ts @@ -0,0 +1,42 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// 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 {TypeOrmBindings} from '../../'; +import {TypeOrmApp} from '../fixtures/application'; + +describe('TypeORM connection booter integration tests', () => { + const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + + let app: TypeOrmApp; + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getApp); + + it('boots connections when app.boot() is called', async () => { + const expectedBindings = [`${TypeOrmBindings.PREFIX}.my-db`]; + await app.boot(); + const bindings = app.findByTag(TypeOrmBindings.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/typeorm-connections/sqlite.connection.js', + ), + 'typeorm-connections/sqlite.connection.js', + ); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/typeorm-entities/book.entity.js'), + 'typeorm-entities/book.entity.js', + ); + const MyApp = require(resolve(sandbox.path, 'application.js')).TypeOrmApp; + app = new MyApp(); + } +}); diff --git a/extensions/typeorm/src/__tests__/integration/rest.integration.ts b/extensions/typeorm/src/__tests__/integration/rest.integration.ts new file mode 100644 index 000000000000..b01b093038c6 --- /dev/null +++ b/extensions/typeorm/src/__tests__/integration/rest.integration.ts @@ -0,0 +1,79 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {RestApplication} from '@loopback/rest'; +import { + Client, + createRestAppClient, + expect, + givenHttpServerConfig, + TestSandbox, +} from '@loopback/testlab'; +import {resolve} from 'path'; +import {TypeOrmApp} from '../fixtures/application'; + +describe('REST with TypeORM (integration)', () => { + const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + + let app: TypeOrmApp; + let client: Client; + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getAppAndClient); + afterEach(async () => { + if (app) await app.stop(); + (app as unknown) = undefined; + }); + + it('creates an entity', async () => { + const book = { + title: 'The Jungle', + published: false, + }; + const res = await client.post('/books').send(book); + expect(res.body).to.have.property('title', book.title); + expect(res.body).to.have.property('published', book.published); + }); + + it('fetches an entity', async () => { + const book = { + title: 'The Book', + published: true, + }; + const create = await client.post('/books').send(book); + const res = await client.get(`/books/${create.body.id}`); + expect(res.body).to.have.property('title', book.title); + expect(res.body).to.have.property('published', book.published); + }); + + async function getAppAndClient() { + await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); + await sandbox.copyFile( + resolve( + __dirname, + '../fixtures/typeorm-connections/sqlite.connection.js', + ), + 'typeorm-connections/sqlite.connection.js', + ); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/typeorm-entities/book.entity.js'), + 'typeorm-entities/book.entity.js', + ); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/controllers/book.controller.js'), + 'controllers/book.controller.js', + ); + + const MyApp = require(resolve(sandbox.path, 'application.js')).TypeOrmApp; + app = new MyApp({ + rest: givenHttpServerConfig(), + }); + + await app.boot(); + await app.start(); + + client = createRestAppClient(app as RestApplication); + } +}); diff --git a/extensions/typeorm/src/__tests__/unit/booter.unit.ts b/extensions/typeorm/src/__tests__/unit/booter.unit.ts new file mode 100644 index 000000000000..c8a7a7d3660b --- /dev/null +++ b/extensions/typeorm/src/__tests__/unit/booter.unit.ts @@ -0,0 +1,116 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application} from '@loopback/core'; +import {expect, sinon, TestSandbox} from '@loopback/testlab'; +import {resolve} from 'path'; +import { + ApplicationUsingTypeOrm, + ConnectionDefaults, + TypeOrmBindings, + TypeOrmConnectionBooter, + TypeOrmMixin, +} from '../../'; + +describe('TypeORM connection booter unit tests', () => { + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); + + class AppUsingTypeOrm extends TypeOrmMixin(Application) {} + + let app: AppUsingTypeOrm; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let stub: sinon.SinonStub<[any?, ...any[]], void>; + + beforeEach('reset sandbox', () => sandbox.reset()); + beforeEach(getApp); + beforeEach(createStub); + afterEach(restoreStub); + + it('gives a warning if called on an app without TypeOrmMixin', async () => { + const normalApp = new Application(); + await sandbox.copyFile( + resolve( + __dirname, + '../fixtures/typeorm-connections/sqlite.connection.js', + ), + 'typeorm-connections/sqlite.connection.js', + ); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/typeorm-entities/book.entity.js'), + 'typeorm-entities/book.entity.js', + ); + const booterInst = new TypeOrmConnectionBooter( + normalApp as ApplicationUsingTypeOrm, + sandbox.path, + ); + + booterInst.discovered = [resolve(sandbox.path, 'sqlite.connection.js')]; + await booterInst.load(); + + sinon.assert.calledOnce(stub); + sinon.assert.calledWith( + stub, + 'app.connection() function is needed for TypeOrmConnectionBooter. You can add ' + + 'it to your Application using TypeOrmMixin from @loopback/typeorm.', + ); + }); + + it(`uses ConnectionDefaults for 'options' if none are given`, () => { + const booterInst = new TypeOrmConnectionBooter(app, sandbox.path); + expect(booterInst.options).to.deepEqual(ConnectionDefaults); + }); + + it('overrides defaults with provided options and uses defaults for the rest', () => { + const options = { + dirs: ['test'], + extensions: ['.ext1'], + }; + const expected = Object.assign({}, options, { + nested: ConnectionDefaults.nested, + }); + + const booterInst = new TypeOrmConnectionBooter(app, sandbox.path, options); + expect(booterInst.options).to.deepEqual(expected); + }); + + it('binds connections during the load phase', async () => { + const expected = [`${TypeOrmBindings.PREFIX}.my-db`]; + await sandbox.copyFile( + resolve( + __dirname, + '../fixtures/typeorm-connections/sqlite.connection.js', + ), + 'typeorm-connections/sqlite.connection.js', + ); + await sandbox.copyFile( + resolve(__dirname, '../fixtures/typeorm-entities/book.entity.js'), + 'typeorm-entities/book.entity.js', + ); + const booterInst = new TypeOrmConnectionBooter(app, sandbox.path); + const NUM_CONNECTIONS = 1; // 1 connection in above file. + + booterInst.discovered = [ + resolve(sandbox.path, 'typeorm-connections/sqlite.connection.js'), + ]; + await booterInst.load(); + + const connections = app.findByTag(TypeOrmBindings.TAG); + const keys = connections.map(binding => binding.key); + expect(keys).to.have.lengthOf(NUM_CONNECTIONS); + expect(keys.sort()).to.eql(expected.sort()); + }); + + function getApp() { + app = new AppUsingTypeOrm(); + } + + function restoreStub() { + stub.restore(); + } + + function createStub() { + stub = sinon.stub(console, 'warn'); + } +}); diff --git a/extensions/typeorm/src/__tests__/unit/mixin.unit.ts b/extensions/typeorm/src/__tests__/unit/mixin.unit.ts new file mode 100644 index 000000000000..ebc4e6b7d97e --- /dev/null +++ b/extensions/typeorm/src/__tests__/unit/mixin.unit.ts @@ -0,0 +1,25 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Application} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import {TypeOrmMixin} from '../../'; + +describe('TypeOrmMixin unit tests', () => { + class AppUsingTypeOrm extends TypeOrmMixin(Application) {} + let app: AppUsingTypeOrm; + + beforeEach(getApp); + + it('adds essential members', async () => { + expect(app).to.have.property('connectionManager'); + expect(app).to.have.property('connection'); + expect(app).to.have.property('migrateSchema'); + }); + + async function getApp() { + app = new AppUsingTypeOrm(); + } +}); diff --git a/extensions/typeorm/src/index.ts b/extensions/typeorm/src/index.ts new file mode 100644 index 000000000000..d8d49c59bd20 --- /dev/null +++ b/extensions/typeorm/src/index.ts @@ -0,0 +1,11 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './typeorm-connection.booter'; +export * from './typeorm.decorators'; +export * from './typeorm.keys'; +export * from './typeorm.mixin'; +export * from './typeorm.types'; +export * from './typeorm.utils'; diff --git a/extensions/typeorm/src/typeorm-connection.booter.ts b/extensions/typeorm/src/typeorm-connection.booter.ts new file mode 100644 index 000000000000..cb6f8d270e89 --- /dev/null +++ b/extensions/typeorm/src/typeorm-connection.booter.ts @@ -0,0 +1,73 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/graphql +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + ArtifactOptions, + BaseArtifactBooter, + BootBindings, + booter, +} from '@loopback/boot'; +import {config, CoreBindings, inject} from '@loopback/core'; +import debugFactory from 'debug'; +import {ApplicationUsingTypeOrm, ConnectionOptions} from './'; +const debug = debugFactory('loopback:typeorm:mixin'); + +/** + * A class that extends BaseArtifactBooter to boot the TypeORM connection artifact type. + * + * Supported phases: configure, discover, load + * + * @param app - Application instance + * @param projectRoot - Root of user's project relative to which all paths are resolved + * @param bootConfig - Connection artifact options object + */ +@booter('connections') +export class TypeOrmConnectionBooter extends BaseArtifactBooter { + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + public app: ApplicationUsingTypeOrm, + @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @config() + public entityConfig: ArtifactOptions = {}, + ) { + super( + projectRoot, + // Set TypeORM connection options if passed in via bootConfig + Object.assign({}, ConnectionDefaults, entityConfig), + ); + } + + async load() { + for (const file of this.discovered) { + if (!this.app.connection) { + console.warn( + 'app.connection() function is needed for TypeOrmConnectionBooter. You can add ' + + 'it to your Application using TypeOrmMixin from @loopback/typeorm.', + ); + } else { + const connections = require(file); + for (const k in connections) { + const connection: ConnectionOptions = connections[k]; + debug('Bind class: %s', connection.name); + const binding = this.app.connection(connection); + debug( + 'Binding created for connection %s: %j', + connection.name, + binding, + ); + } + } + } + } +} + +/** + * Default ArtifactOptions for TypeORM connection. + */ +export const ConnectionDefaults: ArtifactOptions = { + dirs: ['typeorm-connections'], + extensions: ['.connection.js'], + nested: true, +}; diff --git a/extensions/typeorm/src/typeorm.decorators.ts b/extensions/typeorm/src/typeorm.decorators.ts new file mode 100644 index 000000000000..8f6201a8dba6 --- /dev/null +++ b/extensions/typeorm/src/typeorm.decorators.ts @@ -0,0 +1,34 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Context, inject, Injection, ResolutionSession} from '@loopback/core'; +import {TypeOrmBindings} from './typeorm.keys'; + +export namespace typeorm { + export function repository(entity: Function, connectionName?: string) { + return inject( + '', + {decorator: '@typeorm.repository'}, + async ( + ctx: Context, + injection: Readonly, + session: ResolutionSession, + ) => { + const conn = await getConnection(ctx, connectionName); + return conn.getRepository(entity); + }, + ); + } +} + +/** + * Get a connection by name + * @param ctx - Context object + * @param connectionName - Optional connection name + */ +async function getConnection(ctx: Context, connectionName?: string) { + const manager = await ctx.get(TypeOrmBindings.MANAGER); + return manager.get(connectionName); +} diff --git a/extensions/typeorm/src/typeorm.keys.ts b/extensions/typeorm/src/typeorm.keys.ts new file mode 100644 index 000000000000..16e64cd60f8a --- /dev/null +++ b/extensions/typeorm/src/typeorm.keys.ts @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {ConnectionManager} from 'typeorm'; + +/** + * Binding keys used by this component. + */ +export namespace TypeOrmBindings { + export const MANAGER = BindingKey.create( + 'services.TypeOrmConnectionManager', + ); + export const PREFIX = 'connection'; + export const TAG = 'typeOrmConnection'; +} diff --git a/extensions/typeorm/src/typeorm.mixin.ts b/extensions/typeorm/src/typeorm.mixin.ts new file mode 100644 index 000000000000..93ec6337ff5f --- /dev/null +++ b/extensions/typeorm/src/typeorm.mixin.ts @@ -0,0 +1,98 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// FIXME(rfeng): Workaround for https://github.com/microsoft/rushstack/pull/1867 +/* eslint-disable @typescript-eslint/no-unused-vars */ +import * as loopbackContext from '@loopback/core'; + +import { + Application, + Binding, + BindingScope, + Component, + config, + createBindingFromClass, + inject, + lifeCycleObserver, + LifeCycleObserver, + MixinTarget, +} from '@loopback/core'; +import debugFactory from 'debug'; +import {Connection, ConnectionManager, ConnectionOptions} from 'typeorm'; +import {TypeOrmConnectionBooter} from './'; +import {TypeOrmBindings} from './typeorm.keys'; + +const debug = debugFactory('loopback:typeorm:mixin'); + +export function TypeOrmMixin>( + superClass: T, +) { + return class extends superClass { + connectionManager: ConnectionManager; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + super(...args); + this.component(TypeOrmComponent); + this.connectionManager = new ConnectionManager(); + const binding: Binding = this.bind( + TypeOrmBindings.MANAGER, + ).to(this.connectionManager); + debug('Binding created for connection manager', binding); + } + + connection(connectionConfig: ConnectionOptions) { + const connection: Connection = this.connectionManager.create( + connectionConfig, + ); + const name = connection.name; + const binding: Binding = this.bind( + `${TypeOrmBindings.PREFIX}.${name}`, + ) + .toDynamicValue(() => this.connectionManager.get(name)) + .tag(TypeOrmBindings.TAG); + this.add(binding); + return binding; + } + + async migrateSchema(): Promise { + // TODO: implement using TypeORM + throw new Error('TypeORM migration not implemented.'); + } + }; +} + +export interface ApplicationUsingTypeOrm extends Application { + connection(options: ConnectionOptions): void; + migrateSchema(): Promise; +} + +export type TypeOrmComponentOptions = { + [prop: string]: string; +}; + +export class TypeOrmComponent implements Component { + bindings: Binding[] = [createBindingFromClass(TypeOrmConnectionBooter)]; + lifeCycleObservers = [TypeOrmLifeCycleManager]; + constructor(@config() private options: TypeOrmComponentOptions = {}) {} +} + +@lifeCycleObserver('datasource', { + scope: BindingScope.SINGLETON, +}) +export class TypeOrmLifeCycleManager implements LifeCycleObserver { + constructor( + @inject(TypeOrmBindings.MANAGER) + private manager: ConnectionManager, + ) {} + + async start(): Promise { + await Promise.all(this.manager.connections.map(c => c.connect())); + } + + async stop(): Promise { + await Promise.all(this.manager.connections.map(c => c.close())); + } +} diff --git a/extensions/typeorm/src/typeorm.types.ts b/extensions/typeorm/src/typeorm.types.ts new file mode 100644 index 000000000000..3ff361335469 --- /dev/null +++ b/extensions/typeorm/src/typeorm.types.ts @@ -0,0 +1,7 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Re-export everything from 'typeorm' +export * from 'typeorm'; diff --git a/extensions/typeorm/src/typeorm.utils.ts b/extensions/typeorm/src/typeorm.utils.ts new file mode 100644 index 000000000000..aaa5d6c9ccd8 --- /dev/null +++ b/extensions/typeorm/src/typeorm.utils.ts @@ -0,0 +1,87 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typeorm +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + JsonSchemaOptions, + ReferenceObject, + SchemaObject, +} from '@loopback/openapi-v3'; +import debugFactory from 'debug'; +import {getMetadataArgsStorage} from 'typeorm'; +import {ColumnType} from 'typeorm/driver/types/ColumnTypes'; +import {ColumnMetadataArgs} from 'typeorm/metadata-args/ColumnMetadataArgs'; +const debug = debugFactory('loopback:typeorm:mixin'); + +const modelSchemaCache = new WeakMap(); + +/** + * Describe the provided Entity as a reference to a definition shared by multiple + * endpoints. The definition is included in the returned schema. + * + * @param modelCtor - The entity constructor (e.g. `Product`) + * @param options - Additional options + */ +export function getModelSchema( + modelCtor: Function & {prototype: T}, + options?: JsonSchemaOptions, +): SchemaObject { + const cached = modelSchemaCache.get(modelCtor); + if (cached) { + return cached; + } + const allColumns: ColumnMetadataArgs[] = getMetadataArgsStorage().columns; + const modelColumns = allColumns.filter(col => col.target === modelCtor); + + const properties: PropertyType = {}; + for (const col of modelColumns) { + // Skip @PrimaryGeneratedColumn + if (!col.options.primary) { + properties[col.propertyName] = { + type: getStringifiedType({ + func: col.options.type as ColumnType, + entity: modelCtor.name, + property: col.propertyName, + }), + }; + } + } + + const schema: SchemaObject = { + title: modelCtor.name, + properties, + }; + + modelSchemaCache.set(modelCtor, schema); + return schema; +} + +export type PropertyType = { + [propertyName: string]: SchemaObject | ReferenceObject; +}; + +export type StringifiedTypeOptions = { + func: ColumnType; + entity: string; + property: string; +}; + +// TODO: identify other data types +function getStringifiedType( + options: StringifiedTypeOptions, +): string | undefined { + const {func, entity, property} = options; + if (func === Number) { + return 'number'; + } else if (func === String) { + return 'string'; + } else if (func === Boolean) { + return 'boolean'; + } else { + debug( + `${entity}.${property}: Type conversion of ${func} to OpenAPI format not implemented.`, + ); + return undefined; + } +} diff --git a/extensions/typeorm/tsconfig.json b/extensions/typeorm/tsconfig.json new file mode 100644 index 000000000000..5d891f4be892 --- /dev/null +++ b/extensions/typeorm/tsconfig.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../packages/boot/tsconfig.json" + }, + { + "path": "../../packages/core/tsconfig.json" + }, + { + "path": "../../packages/openapi-v3/tsconfig.json" + }, + { + "path": "../../packages/repository/tsconfig.json" + }, + { + "path": "../../packages/rest/tsconfig.json" + }, + { + "path": "../../packages/testlab/tsconfig.json" + } + ] +} diff --git a/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts b/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts index fb1771c32d50..1840056876b8 100644 --- a/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts +++ b/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts @@ -7,7 +7,7 @@ import {Application, bind, BindingKey, ContextTags} from '@loopback/core'; import {expect} from '@loopback/testlab'; import {BootBindings, Booter, BootMixin} from '../../..'; -describe('BootMxiin unit tests', () => { +describe('BootMixin unit tests', () => { let app: AppWithBootMixin; beforeEach(getApp); diff --git a/packages/boot/src/boot.component.ts b/packages/boot/src/boot.component.ts index 19b4b1b92387..0338e7b8da96 100644 --- a/packages/boot/src/boot.component.ts +++ b/packages/boot/src/boot.component.ts @@ -4,11 +4,11 @@ // License text available at https://opensource.org/licenses/MIT import { - BindingScope, - inject, Application, + BindingScope, Component, CoreBindings, + inject, } from '@loopback/core'; import { ApplicationMetadataBooter, diff --git a/tsconfig.json b/tsconfig.json index 4098b54da5f2..e3536a017c36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -103,6 +103,9 @@ { "path": "extensions/metrics/tsconfig.json" }, + { + "path": "extensions/typeorm/tsconfig.json" + }, { "path": "fixtures/mock-oauth2-provider/tsconfig.json" },