From 742bf6cf0a23cf94e2c2953ecc8c6b05ab57396b Mon Sep 17 00:00:00 2001 From: Matt Schnee Date: Sat, 18 Apr 2020 19:27:18 -0700 Subject: [PATCH 1/3] feat: initial commit of a typegoose extension docs: add more example code to README.md fix: use build-extension to create @loopback/typegoose feat: adds mongoose variant docs: update README files fix: license headers fix: remove mongoose --- extensions/typegoose/.npmrc | 1 + extensions/typegoose/LICENSE | 27 + extensions/typegoose/README.md | 165 ++++ extensions/typegoose/index.d.ts | 6 + extensions/typegoose/index.js | 6 + extensions/typegoose/index.ts | 6 + extensions/typegoose/package-lock.json | 904 ++++++++++++++++++ extensions/typegoose/package.json | 63 ++ .../src/__tests__/integration/MySequence.ts | 40 + .../src/__tests__/integration/application.ts | 56 ++ .../__tests__/integration/rest-server.test.ts | 46 + .../integration/schemas/event.schema.ts | 20 + .../__tests__/integration/schemas/index.ts | 48 + .../integration/schemas/login-event.schema.ts | 13 + .../integration/schemas/user.schema.ts | 17 + .../__tests__/integration/user.controller.ts | 74 ++ extensions/typegoose/src/component.ts | 138 +++ extensions/typegoose/src/index.ts | 8 + extensions/typegoose/src/keys.ts | 16 + extensions/typegoose/src/types.ts | 59 ++ extensions/typegoose/tsconfig.build.json | 9 + 21 files changed, 1722 insertions(+) create mode 100644 extensions/typegoose/.npmrc create mode 100644 extensions/typegoose/LICENSE create mode 100644 extensions/typegoose/README.md create mode 100644 extensions/typegoose/index.d.ts create mode 100644 extensions/typegoose/index.js create mode 100644 extensions/typegoose/index.ts create mode 100644 extensions/typegoose/package-lock.json create mode 100644 extensions/typegoose/package.json create mode 100644 extensions/typegoose/src/__tests__/integration/MySequence.ts create mode 100644 extensions/typegoose/src/__tests__/integration/application.ts create mode 100644 extensions/typegoose/src/__tests__/integration/rest-server.test.ts create mode 100644 extensions/typegoose/src/__tests__/integration/schemas/event.schema.ts create mode 100644 extensions/typegoose/src/__tests__/integration/schemas/index.ts create mode 100644 extensions/typegoose/src/__tests__/integration/schemas/login-event.schema.ts create mode 100644 extensions/typegoose/src/__tests__/integration/schemas/user.schema.ts create mode 100644 extensions/typegoose/src/__tests__/integration/user.controller.ts create mode 100644 extensions/typegoose/src/component.ts create mode 100644 extensions/typegoose/src/index.ts create mode 100644 extensions/typegoose/src/keys.ts create mode 100644 extensions/typegoose/src/types.ts create mode 100644 extensions/typegoose/tsconfig.build.json diff --git a/extensions/typegoose/.npmrc b/extensions/typegoose/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/extensions/typegoose/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/extensions/typegoose/LICENSE b/extensions/typegoose/LICENSE new file mode 100644 index 000000000000..cad796dd5bc4 --- /dev/null +++ b/extensions/typegoose/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/typegoose +This project is licensed under the MIT License, full text below. + +-------- + +MIT License + +MIT License Copyright (c) IBM Corp. 2020 + +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 (including the next +paragraph) 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/typegoose/README.md b/extensions/typegoose/README.md new file mode 100644 index 000000000000..f3907c9b0046 --- /dev/null +++ b/extensions/typegoose/README.md @@ -0,0 +1,165 @@ +# @loopback/typegoose + +## 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. + +[![LoopBack]()](http://loopback.io/) + +## ⚠️Warnings⚠️ + +As of `0.0.1` of this extension, the type definitions of `@types/mongoose` are +several signifigant minor versions out of date: it supports mongoose `5.7.12`, +and the latest stable version of mongoose is `5.9.10`. These types are used by +the latest version of `@typegoose/typegoose`. + +## Using your existing Typegoose Schemas in Loopback + +Using typegoose schemas is relatively straightforward, though at this early +stage it's still a number of steps: + +- Import the component, and it's binding: + `import {LoopbackTypegooseComponent, TypegooseBindings} from '@loopback/typegoose'` + +- Register your configuration (see below) + ```ts + this.configure(TypegooseBindings.COMPONENT).to(configuration); + ``` +- Add the component to your application + ```ts + this.component(LoopbackTypegooseComponent); + ``` +- Finally, `@inject` your Entity Models for use + ```ts + public async MyService(@inject('loopback-typegoose-component.model.ModelName') ModelName) { + const result = await ModelName.findOne({}); + } + ``` + +## Configuration + +There are three pieces of information that the Mongoose Component needs in order +to create a connection to your database and set up the mongoose models: + +- the `uri` of the connection +- an array of Schema classes +- optionally, an array of Discriminator Schema classes + +The configuration looks like the following: + +```ts + this.configure(TypegooseBindings.COMPONENT).to({ + uri: 'mongodb://localhost:27017', + connectionOptions: {} // anything valid to mongoose.createConnection(), + schemas: [ + { + bindingKey: 'my-custom.binding.key', + schema: myImportedSchema, + name: 'MyModel' + } + ], + discriminators: [ + { + bindingKey: 'my-custom.discriminator.binding.key', + modelKey: 'my-custom.binding.key', + schema: myImportedDiscriminatorSchema, + value: 'MyModelDiscriminator' + } + ] + }) +``` + +For multiple connections, pass an array instead of an object: + +```ts +this.configure(MongooseBindings.COMPONENT).to([ + { + uri: 'mongodb://localhost:27017', + schemas: [...] + }, + { + uri: 'mongodb://127.0.0.12:27017', + schemas: [...] + } +]) +``` + +Remember, models are **always** scoped to a single connection. See the +[Mongoose Documentation for Multiple Connections](https://mongoosejs.com/docs/connections.html#multiple_connections) +for more information. + +### uri + +This is a valid mongodb connection uri string. See the +[MongoDB Documentation](https://docs.mongodb.com/manual/reference/connection-string/) +for more information. + +### connectionOptions + +_(optional)_ + +Optionally, these are options that can be passed to the mongoose +`createConnection` function. See the +[Mongoose Documentation](https://mongoosejs.com/docs/api.html#mongoose_Mongoose-createConnection) +for more details. + +### Schemas + +The Mongoose Component will create the schemas for you. Each item in this array +contains the information necessary to so do: + +**schema** + +The schema class itself, created from `class MySchema {}`, and passed to +`getModelForClass()` + +**schemaOptions** + +See the +[typegoose documentation](https://typegoose.github.io/typegoose/docs/functions/getModelForClass/#example) +**customOptions** + +See the +[typegoose documentation](https://typegoose.github.io/typegoose/docs/functions/getModelForClass/#example) + +**bindingKey** _optional_ + +Optionally, you can create your own binding key. The default binding key will be +`loopback-typegoose-extensions.model.${name}`. If you have multiple connections, +the binding key will include the connection index: +`loopback-typegoose-extension.connection.0.model.${name}` + +Using the same binding key for a model on multiple connections is not supported, +and doing so will throw an `Error`. + +## Discriminators + +Creating discriminators requires having already created a model to create the +discriminator from. The configuration to do so requires the bindingKey for the +original model, as well as the binding key for the new model, as well as the +name of the discriminator. + +**schema** + +The schema class itself, created from `class MySchema {}`, and passed to +`getDiscriminatorModelForClass(BaseModel, DiscriminatorClass)` + +**modelKey** + +This must be the same binding key used for the discriminator base model. + +**bindingKey** _(optional)_ + +Optionally, you can create your own binding key. The default binding key will be +`loopback-typegoose-extensions.model.${name}`. If you have multiple connections, +the binding key will include the connection index: +`loopback-typegoose-extension.connection.0.model.${name}` + +# Using the Typegoose Extension + +There is a full REST server used for +[integration tests](./src/__tests__/integration), which contains an example of +how to set up and use mongoose models and discriminators. diff --git a/extensions/typegoose/index.d.ts b/extensions/typegoose/index.d.ts new file mode 100644 index 000000000000..120404b1681f --- /dev/null +++ b/extensions/typegoose/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist'; diff --git a/extensions/typegoose/index.js b/extensions/typegoose/index.js new file mode 100644 index 000000000000..c8ab80d6065b --- /dev/null +++ b/extensions/typegoose/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('./dist'); diff --git a/extensions/typegoose/index.ts b/extensions/typegoose/index.ts new file mode 100644 index 000000000000..ff25f7ab93d7 --- /dev/null +++ b/extensions/typegoose/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './src'; diff --git a/extensions/typegoose/package-lock.json b/extensions/typegoose/package-lock.json new file mode 100644 index 000000000000..27b8ad6c4820 --- /dev/null +++ b/extensions/typegoose/package-lock.json @@ -0,0 +1,904 @@ +{ + "name": "@loopback/typegoose", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@typegoose/typegoose": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-6.4.0.tgz", + "integrity": "sha512-3aJ2MxOIcTQ0gpGuQ3d4UGAUSATfI03Jberr4K6Xp7LnIehhVjBcVzaJETH2BR/H3sDOJXLUnHH+nJP218JFHQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15", + "loglevel": "^1.6.7", + "reflect-metadata": "^0.1.13", + "semver": "^7.1.3", + "tslib": "^1.11.0" + } + }, + "@types/bson": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz", + "integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cross-spawn": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.1.tgz", + "integrity": "sha512-MtN1pDYdI6D6QFDzy39Q+6c9rl2o/xN7aWGe6oZuzqq5N6+YuwFsWiEAv3dNzvzN9YzU+itpN8lBzFpphQKLAw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", + "dev": true + }, + "@types/find-cache-dir": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.0.tgz", + "integrity": "sha512-+JeT9qb2Jwzw72WdjU+TSvD5O1QRPWCeRpDJV+guiIq+2hwR0DFGw+nZNbTFjMIVe6Bf4GgAKeB/6Ytx6+MbeQ==", + "dev": true + }, + "@types/find-package-json": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.1.1.tgz", + "integrity": "sha512-XMCocYkg6VUpkbOQMKa3M5cgc3MvU/LJKQwd3VUJrWZbLr2ARUggupsCAF8DxjEEIuSO6HlnH+vl+XV4bgVeEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-Iv2FAb5RnIk/eFO2CTu8k+0VMmIR15pKbcqRWi+s3ydW+aKXlN2yemP92SrO++ERyJx+p6Ie1ggbLBMbU1SjiQ==", + "dev": true, + "requires": { + "get-port": "*" + } + }, + "@types/lockfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/lockfile/-/lockfile-1.0.1.tgz", + "integrity": "sha512-65WZedEm4AnOsBDdsapJJG42MhROu3n4aSSiu87JXF/pSdlubxZxp3S1yz3kTfkJ2KBPud4CpjoHVAptOm9Zmw==", + "dev": true + }, + "@types/md5-file": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/md5-file/-/md5-file-4.0.1.tgz", + "integrity": "sha512-uK6vlo/LJp6iNWinpSzZwMe8Auzs0UYxesm7OGfQS3oz6PJciHtrKcqVOGk4wjYKawrl234vwNWvHyXH1ZzRyQ==", + "dev": true + }, + "@types/mkdirp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.0.tgz", + "integrity": "sha512-ONFY9//bCEr3DWKON3iDv/Q8LXnhaYYaNDeFSN0AtO5o4sLf9F0pstJKKKjQhXE0kJEeHs8eR6SAsROhhc2Csw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mongodb": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.9.tgz", + "integrity": "sha512-YEs/yBGkxpp9QxSB6gZInV7+S74pryqdfxEUJnlXDsFTtLmnDJA9iHnGEsxgrWm9gl2f4BDTwKe6lzTNB25oHg==", + "dev": true, + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "@types/mongoose": { + "version": "5.7.12", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.12.tgz", + "integrity": "sha512-yzLJk3cdSwuMXaIacUCWUb8m960YcgnID7S4ZPOOgzT39aSC46670TuunN+ajDio7OUcGG4mGg8eOGs2Z6VmrA==", + "dev": true, + "requires": { + "@types/mongodb": "*", + "@types/node": "*" + } + }, + "@types/node": { + "version": "10.17.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.20.tgz", + "integrity": "sha512-XgDgo6W10SeGEAM0k7FosJpvLCynOTYns4Xk3J5HGrA+UI/bKZ30PGMzOP5Lh2zs4259I71FSYLAtjnx3qhObw==", + "dev": true + }, + "@types/tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==", + "dev": true + }, + "@types/uuid": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.0.tgz", + "integrity": "sha512-RiX1I0lK9WFLFqy2xOxke396f0wKIzk5sAll0tL4J4XDYJXURI7JOs96XQb3nP+2gEpQ/LutBb66jgiT5oQshQ==", + "dev": true + }, + "agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "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==", + "dev": true + }, + "bl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", + "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", + "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==" + }, + "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==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "kareem": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", + "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "loglevel": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "dev": true + }, + "make-dir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "md5-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz", + "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==", + "dev": true + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mongodb": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.6.tgz", + "integrity": "sha512-sh3q3GLDLT4QmoDLamxtAECwC3RGjq+oNuK1ENV8+tnipIavss6sMYt77hpygqlMOCt0Sla5cl7H4SKCVBCGEg==", + "requires": { + "bl": "^2.2.0", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongodb-memory-server": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-6.5.2.tgz", + "integrity": "sha512-PUCiWcHGwyqQiZF3J4iCy1DXqPjoNtehV2qMFJ26rhNBFzmc5SW+9/FUQwGNLge5/Lm1dEwcraJD5PUe8m9Kdg==", + "dev": true, + "requires": { + "mongodb-memory-server-core": "6.5.2" + } + }, + "mongodb-memory-server-core": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-6.5.2.tgz", + "integrity": "sha512-Cs1wB+GrL2KconDD2emk6ptU1OqdYia11I7fw5sOqnEWc8stTQ4rtptbRcLmHyTcTgRYC/fsQQvzbbd7DBf4DQ==", + "dev": true, + "requires": { + "@types/cross-spawn": "^6.0.1", + "@types/debug": "^4.1.5", + "@types/dedent": "^0.7.0", + "@types/find-cache-dir": "^3.2.0", + "@types/find-package-json": "^1.1.1", + "@types/get-port": "^4.0.1", + "@types/lockfile": "^1.0.1", + "@types/md5-file": "^4.0.1", + "@types/mkdirp": "^1.0.0", + "@types/tmp": "0.1.0", + "@types/uuid": "7.0.0", + "camelcase": "^5.3.1", + "cross-spawn": "^7.0.1", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "find-cache-dir": "3.3.1", + "find-package-json": "^1.2.0", + "get-port": "5.1.1", + "https-proxy-agent": "5.0.0", + "lockfile": "^1.0.4", + "md5-file": "^4.0.0", + "mkdirp": "^1.0.3", + "mongodb": "^3.5.4", + "tar-stream": "^2.1.1", + "tmp": "^0.1.0", + "uuid": "^7.0.2", + "yauzl": "^2.10.0" + } + }, + "mongoose": { + "version": "5.9.10", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.10.tgz", + "integrity": "sha512-w1HNukfJzzDLfcI1f79h2Wj4ogVbf+X8hRkyFgqlcjK7OnDlAgahjDMIsT+mCS9jKojrMhjSsZIs9FiRPkLqMg==", + "requires": { + "bson": "^1.1.4", + "kareem": "2.3.1", + "mongodb": "3.5.6", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.7.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.1.2", + "sift": "7.0.1", + "sliced": "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==" + } + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "mpath": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", + "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" + }, + "mquery": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", + "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "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==" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "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==" + }, + "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==", + "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==" + } + } + }, + "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==", + "dev": true + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "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.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "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 + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.18.tgz", + "integrity": "sha512-9luZr/BZ2QeU6tO2uG8N2aZpVSli4TSAOAqFOyTO51AJcD9P99c0K1h6dD6r6qo5dyT44BR5exweOaLLeldTkQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "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==", + "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==" + } + } + }, + "tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "dev": true, + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/extensions/typegoose/package.json b/extensions/typegoose/package.json new file mode 100644 index 000000000000..a4ad25ef7cf3 --- /dev/null +++ b/extensions/typegoose/package.json @@ -0,0 +1,63 @@ +{ + "name": "@loopback/typegoose", + "version": "0.0.1", + "description": "@loopback/typegoose", + "keywords": [ + "loopback-extension", + "loopback" + ], + "main": "index.js", + "engines": { + "node": ">=10" + }, + "scripts": { + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha --allow-console-logs \"dist/__tests__\"", + "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js", + "clean": "lb-clean dist *.tsbuildinfo .eslintcache" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "extensions/typegoose" + }, + "author": "IBM Corp.", + "license": "MIT", + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist", + "src", + "!*/__tests__" + ], + "dependencies": { + "@loopback/boot": "^2.0.4", + "@loopback/context": "^3.4.0", + "@loopback/core": "^2.3.0", + "@loopback/openapi-v3": "^3.1.3", + "@loopback/repository": "^2.1.1", + "@loopback/rest": "^3.2.1", + "@loopback/service-proxy": "^2.0.4", + "mongoose": "^5.9.10", + "tslib": "^1.10.0" + }, + "devDependencies": { + "@loopback/build": "^5.0.1", + "@loopback/testlab": "^3.0.1", + "@typegoose/typegoose": "^6.4.0", + "@types/mongodb": "^3.5.9", + "@types/mongoose": "^5.7.12", + "@types/node": "^10.17.19", + "get-port": "^5.1.1", + "mongodb-memory-server": "^6.5.2", + "source-map-support": "^0.5.16", + "typescript": "~3.8.3" + }, + "copyright.owner": "IBM Corp.", + "publishConfig": { + "access": "public" + } +} diff --git a/extensions/typegoose/src/__tests__/integration/MySequence.ts b/extensions/typegoose/src/__tests__/integration/MySequence.ts new file mode 100644 index 000000000000..663aee3c17ee --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/MySequence.ts @@ -0,0 +1,40 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/core'; +import { + FindRoute, + InvokeMethod, + ParseParams, + Reject, + RequestContext, + RestBindings, + Send, + SequenceHandler, +} from '@loopback/rest'; + +const SequenceActions = RestBindings.SequenceActions; + +export class MySequence implements SequenceHandler { + constructor( + @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute, + @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams, + @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod, + @inject(SequenceActions.SEND) public send: Send, + @inject(SequenceActions.REJECT) public reject: Reject, + ) {} + + async handle(context: RequestContext) { + try { + const {request, response} = context; + const route = this.findRoute(request); + const args = await this.parseParams(request, route); + const result = await this.invoke(route, args); + this.send(response, result); + } catch (err) { + this.reject(context, err); + } + } +} diff --git a/extensions/typegoose/src/__tests__/integration/application.ts b/extensions/typegoose/src/__tests__/integration/application.ts new file mode 100644 index 000000000000..05626e522cdb --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/application.ts @@ -0,0 +1,56 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// 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 {ServiceMixin} from '@loopback/service-proxy'; +import {LoopbackTypegooseComponent} from '../../component'; +import {TypegooseBindings} from '../../keys'; +import {MySequence} from './MySequence'; +import {BindingKeys, Schemas} from './schemas'; +import UserController from './user.controller'; + +export interface TestApplicationConfig extends ApplicationConfig { + mongoUri?: string; +} + +export class TestTypegooseApplication extends BootMixin( + ServiceMixin(RestApplication), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + this.sequence(MySequence); + this.configureTypegoose(options.mongoUri); + this.component(LoopbackTypegooseComponent); + this.controller(UserController); + + this.projectRoot = __dirname; + } + + configureTypegoose(uri: string) { + const schemas = [ + {schema: Schemas.Event, bindingKey: BindingKeys.Connection1.Event}, + {schema: Schemas.User, bindingKey: BindingKeys.Connection1.User}, + ]; + const discriminators = [ + { + schema: Schemas.LoginEvent, + modelKey: BindingKeys.Connection1.Event, + bindingKey: BindingKeys.Connection1.LoginEvent, + }, + ]; + + this.configure(TypegooseBindings.COMPONENT).to([ + { + uri, + connectionOptions: {userNewUrlParser: true}, + schemas, + discriminators, + }, + ]); + } +} diff --git a/extensions/typegoose/src/__tests__/integration/rest-server.test.ts b/extensions/typegoose/src/__tests__/integration/rest-server.test.ts new file mode 100644 index 000000000000..1513e81d560c --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/rest-server.test.ts @@ -0,0 +1,46 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Client, createRestAppClient, expect} from '@loopback/testlab'; +import getPort from 'get-port'; +import {MongoMemoryServer} from 'mongodb-memory-server'; +import {TestTypegooseApplication} from './application'; +describe('Typegoose Extension (Acceptance)', () => { + let app: TestTypegooseApplication; + let client: Client; + + const userData = { + firstName: 'TestName', + lastName: 'TestLastName', + }; + afterEach(async () => { + if (app) await app.stop(); + (app as unknown) = undefined; + }); + + beforeEach(async () => { + const server = new MongoMemoryServer(); + const connectionUrl = await server.getConnectionString(); + const port = await getPort(); + app = new TestTypegooseApplication({ + mongoUri: connectionUrl, + rest: { + port, + }, + }); + await app.boot(); + await app.start(); + client = createRestAppClient(app); + }); + + it('Posting to /login should create a single event', async () => { + const loginRes = await client.post('/login').send(userData); + expect(loginRes.status).to.eql(204); + + const eventRes = await client.post('/find').send(userData); + expect(eventRes.status).to.eql(200); + expect(eventRes.body.length).to.eql(1); + }); +}); diff --git a/extensions/typegoose/src/__tests__/integration/schemas/event.schema.ts b/extensions/typegoose/src/__tests__/integration/schemas/event.schema.ts new file mode 100644 index 000000000000..051fd582fd91 --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/schemas/event.schema.ts @@ -0,0 +1,20 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {modelOptions, pre, prop} from '@typegoose/typegoose'; + +@modelOptions({ + schemaOptions: { + discriminatorKey: 'eventType', + }, +}) +@pre('save', function () { + // eslint-disable-next-line no-invalid-this + this.createdAt = new Date(); +}) +export default class Event { + @prop() + createdAt: Date; +} diff --git a/extensions/typegoose/src/__tests__/integration/schemas/index.ts b/extensions/typegoose/src/__tests__/integration/schemas/index.ts new file mode 100644 index 000000000000..a44758895f12 --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/schemas/index.ts @@ -0,0 +1,48 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {ReturnModelType} from '@typegoose/typegoose'; +import EventSchema from './event.schema'; +import LoginEventSchema from './login-event.schema'; +import UserSchema from './user.schema'; + +export namespace Schemas { + export const Event = EventSchema; + export const LoginEvent = LoginEventSchema; + export const User = UserSchema; +} + +export namespace Models { + export type Event = ReturnModelType; + export type LoginEvent = ReturnModelType; + export type User = ReturnModelType; +} + +export namespace BindingKeys { + export namespace Connection1 { + export const Event = BindingKey.create( + 'typegoose.models.conn1.Event', + ); + export const LoginEvent = BindingKey.create( + 'typegoose.models.conn1.LoginEvent', + ); + export const User = BindingKey.create( + 'typegoose.models.conn1.User', + ); + } + + export namespace Connection2 { + export const Event = BindingKey.create( + 'typegoose.models.conn2.Event', + ); + export const LoginEvent = BindingKey.create( + 'typegoose.models.conn2.LoginEvent', + ); + export const User = BindingKey.create( + 'typegoose.models.conn2.User', + ); + } +} diff --git a/extensions/typegoose/src/__tests__/integration/schemas/login-event.schema.ts b/extensions/typegoose/src/__tests__/integration/schemas/login-event.schema.ts new file mode 100644 index 000000000000..0c60fda6e0d4 --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/schemas/login-event.schema.ts @@ -0,0 +1,13 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {prop, Ref} from '@typegoose/typegoose'; +import Event from './event.schema'; +import User from './user.schema'; + +export default class LoginEvent extends Event { + @prop({ref: 'User'}) + _user: Ref; +} diff --git a/extensions/typegoose/src/__tests__/integration/schemas/user.schema.ts b/extensions/typegoose/src/__tests__/integration/schemas/user.schema.ts new file mode 100644 index 000000000000..e4d3b407c827 --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/schemas/user.schema.ts @@ -0,0 +1,17 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {prop} from '@typegoose/typegoose'; + +/** + * Define Typegoose classes first + */ +export default class User { + @prop() + firstName: string; + + @prop() + lastName: string; +} diff --git a/extensions/typegoose/src/__tests__/integration/user.controller.ts b/extensions/typegoose/src/__tests__/integration/user.controller.ts new file mode 100644 index 000000000000..9fa3826ead7f --- /dev/null +++ b/extensions/typegoose/src/__tests__/integration/user.controller.ts @@ -0,0 +1,74 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/core'; +import {oas, post, requestBody} from '@loopback/openapi-v3'; +import {Model, model, property} from '@loopback/repository'; +import {BindingKeys, Models} from './schemas'; + +@model() +class LoginBody extends Model { + @property() + firstName: string; + + @property() + lastName: string; +} + +@model() +class LoginResponse extends Model { + constructor(d: Partial) { + super(d); + } + @property() + createdAt: Date; +} + +export default class UserController { + constructor( + @inject(BindingKeys.Connection1.User) private userModel: Models.User, + @inject(BindingKeys.Connection1.Event) private eventModel: Models.Event, + @inject(BindingKeys.Connection1.LoginEvent) + private loginEventModel: Models.LoginEvent, + ) {} + + @post('/login') + public async userLogin(@requestBody() body: LoginBody) { + let user = await this.userModel.findOne({ + firstName: body.firstName, + lastName: body.lastName, + }); + if (!user) { + user = await this.userModel.create({ + firstName: body.firstName, + lastName: body.lastName, + }); + } + + await this.loginEventModel.create({_user: user}); + } + + @post('/find') + @oas.response(200, [LoginResponse]) + public async findUserLogins(@requestBody() body: LoginBody) { + const user = await this.userModel.findOne({ + firstName: body.firstName, + lastName: body.lastName, + }); + + if (!user) { + return []; + } + const events = await this.eventModel.find({ + _user: user._id, + }); + + if (events) { + return events.map(e => new LoginResponse({createdAt: e.createdAt})); + } else { + return []; + } + } +} diff --git a/extensions/typegoose/src/component.ts b/extensions/typegoose/src/component.ts new file mode 100644 index 000000000000..cacdecc78e36 --- /dev/null +++ b/extensions/typegoose/src/component.ts @@ -0,0 +1,138 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Application, + bind, + Binding, + BindingScope, + Component, + config, + ContextTags, + CoreBindings, + inject, + LifeCycleObserver, +} from '@loopback/core'; +import { + getDiscriminatorModelForClass, + getModelForClass, +} from '@typegoose/typegoose'; +import debugFactory from 'debug'; +import {Connection, createConnection} from 'mongoose'; +import {TypegooseBindings} from './keys'; +import {TypegooseConfig, TypegooseConnectionOptions} from './types'; + +const debug = debugFactory('loopback:typegoose'); + +@bind({ + tags: {[ContextTags.KEY]: TypegooseBindings.COMPONENT}, + scope: BindingScope.SINGLETON, +}) +export class LoopbackTypegooseComponent + implements Component, LifeCycleObserver { + mongooseConnections: Connection[] = []; + bindings: Binding[] = []; + + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + protected application: Application, + @config() + readonly typegooseConfig: TypegooseConfig = [], + ) {} + + async start() { + /** + * You can have multiple connections and you can use schemas on different + * connections to create connection-model instances. + */ + let connectionOptions: TypegooseConnectionOptions[]; + + if (Array.isArray(this.typegooseConfig)) { + connectionOptions = this.typegooseConfig; + } else { + connectionOptions = [this.typegooseConfig]; + } + + for (const connectionIndex in connectionOptions) { + const connectionConfig = connectionOptions[connectionIndex]; + const connection = await createConnection( + connectionConfig.uri, + connectionConfig.connectionOptions, + ); + // Add the connection to our array. We don't want to depend on + // mongoose as a singleton since we could have multiple applications + // or servers running, and the singleton would disconnect other + // services + this.mongooseConnections.push(connection); + + /** + * First, we turn the base schemas referenced by this connection into + * models, and bind them to the application context. + */ + for (const schemaConfig of connectionConfig.schemas ?? []) { + const model = getModelForClass(schemaConfig.schema, { + existingConnection: connection, + schemaOptions: schemaConfig.schemaOptions, + options: schemaConfig.options, + }); + + let binding = schemaConfig.bindingKey; + if (!binding) { + if (connectionOptions.length === 1) { + binding = `loopback-typegoose-extension.model.${schemaConfig.schema.name}`; + } else { + binding = `loopback-typegoose-extension.connection.${connectionIndex}.model.${schemaConfig.schema.name}`; + } + } + + const existing = await this.application.get(binding, {optional: true}); + if (existing) { + throw new Error( + `Binding '${binding}' is already bound to this context`, + ); + } + + debug(`binding ${binding}`); + this.application + .bind(binding) + .to(model) + .inScope(BindingScope.SINGLETON); + } + + /** + * Now that we have models created, we can creeate any discriminators. + */ + for (const discriminatorConfig of connectionConfig.discriminators ?? []) { + const fromModel = await this.application.get( + discriminatorConfig.modelKey, + ); + const model = getDiscriminatorModelForClass( + fromModel, + discriminatorConfig.schema, + ); + + let binding = discriminatorConfig.bindingKey; + if (!binding) { + if (connectionOptions.length === 1) { + binding = `loopback-typegoose-extension.model.${discriminatorConfig.schema.name}`; + } else { + binding = `loopback-typegoose-extension.connection.${connectionIndex}.model.${discriminatorConfig.schema.name}`; + } + } + debug(`binding discriminator binding`); + this.application + .bind(binding) + .to(model) + .inScope(BindingScope.SINGLETON); + } + } + } + + async stop() { + for (const connection of this.mongooseConnections) { + await connection.close(); + } + } +} diff --git a/extensions/typegoose/src/index.ts b/extensions/typegoose/src/index.ts new file mode 100644 index 000000000000..fdf6835e9fe9 --- /dev/null +++ b/extensions/typegoose/src/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './component'; +export * from './keys'; +export * from './types'; diff --git a/extensions/typegoose/src/keys.ts b/extensions/typegoose/src/keys.ts new file mode 100644 index 000000000000..1d418bf14e9c --- /dev/null +++ b/extensions/typegoose/src/keys.ts @@ -0,0 +1,16 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {LoopbackTypegooseComponent} from './component'; + +/** + * Binding keys used by this component. + */ +export namespace TypegooseBindings { + export const COMPONENT = BindingKey.create( + 'components.LoopbackTypegooseComponent', + ); +} diff --git a/extensions/typegoose/src/types.ts b/extensions/typegoose/src/types.ts new file mode 100644 index 000000000000..351f9a062a8c --- /dev/null +++ b/extensions/typegoose/src/types.ts @@ -0,0 +1,59 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/typegoose +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {ReturnModelType} from '@typegoose/typegoose'; +import { + AnyParamConstructor, + ICustomOptions, +} from '@typegoose/typegoose/lib/types'; +import {ConnectionOptions, SchemaOptions} from 'mongoose'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface TypegooseSchema, T = any> { + /** + * In the mongoose/typegoose world, you interace directly with the models + * created from mongoose.model() or typegoose.getModelForClass() + * + * These are bound to specific connections, which can be either a global + * connection or a specific connection. + * + * In either case, you need to create your own binding key for each + * connection that you have configured. + * + * Pass the Schema Class Constructor type in configuration, and when you + * @inject(YourModelBindingKey), you'll have the correct ReturnModelType + */ + bindingKey?: string | BindingKey>; + schema: U; + schemaOptions?: SchemaOptions; + options?: ICustomOptions; +} + +export interface TypegooseDiscriminator< + U extends AnyParamConstructor, + Mu extends AnyParamConstructor, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Mt = any +> { + modelKey: BindingKey>; + bindingKey?: string | BindingKey>; + schema: U; +} + +export interface TypegooseConnectionOptions { + uri: string; + connectionOptions?: ConnectionOptions; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + schemas?: TypegooseSchema[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + discriminators?: TypegooseDiscriminator[]; +} + +export type TypegooseConfig = + | TypegooseConnectionOptions + | TypegooseConnectionOptions[]; diff --git a/extensions/typegoose/tsconfig.build.json b/extensions/typegoose/tsconfig.build.json new file mode 100644 index 000000000000..c7b8e49eaca5 --- /dev/null +++ b/extensions/typegoose/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} From 4ed5dc8a2e0d0c494f01988ad1f7ce9b39fab459 Mon Sep 17 00:00:00 2001 From: Matthew Schnee Date: Fri, 24 Apr 2020 11:53:21 -0700 Subject: [PATCH 2/3] Update package.json --- extensions/typegoose/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/typegoose/package.json b/extensions/typegoose/package.json index a4ad25ef7cf3..51a628b87fa8 100644 --- a/extensions/typegoose/package.json +++ b/extensions/typegoose/package.json @@ -4,7 +4,10 @@ "description": "@loopback/typegoose", "keywords": [ "loopback-extension", - "loopback" + "loopback", + "mongodb", + "mongoose", + "typegoose" ], "main": "index.js", "engines": { From 2b6119c3fd02a34d0c507f0e24e25b70440021ec Mon Sep 17 00:00:00 2001 From: Matt Schnee Date: Fri, 24 Apr 2020 13:52:33 -0700 Subject: [PATCH 3/3] feat: adds support to configure using an existing connection --- .../src/__tests__/integration/application.ts | 35 +++---------- .../__tests__/integration/schemas/index.ts | 45 +++++++++++++--- extensions/typegoose/src/component.ts | 51 ++++++++++++++++--- extensions/typegoose/src/types.ts | 19 +++++-- 4 files changed, 104 insertions(+), 46 deletions(-) diff --git a/extensions/typegoose/src/__tests__/integration/application.ts b/extensions/typegoose/src/__tests__/integration/application.ts index 05626e522cdb..c0925afcd591 100644 --- a/extensions/typegoose/src/__tests__/integration/application.ts +++ b/extensions/typegoose/src/__tests__/integration/application.ts @@ -7,12 +7,13 @@ import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; import {RestApplication} from '@loopback/rest'; import {ServiceMixin} from '@loopback/service-proxy'; +import debugFactory from 'debug'; import {LoopbackTypegooseComponent} from '../../component'; -import {TypegooseBindings} from '../../keys'; import {MySequence} from './MySequence'; -import {BindingKeys, Schemas} from './schemas'; +import {configureTypegoose} from './schemas'; import UserController from './user.controller'; +const debug = debugFactory('loopback:typegoose:integration-test'); export interface TestApplicationConfig extends ApplicationConfig { mongoUri?: string; } @@ -22,35 +23,15 @@ export class TestTypegooseApplication extends BootMixin( ) { constructor(options: ApplicationConfig = {}) { super(options); - this.sequence(MySequence); - this.configureTypegoose(options.mongoUri); - this.component(LoopbackTypegooseComponent); this.controller(UserController); - this.projectRoot = __dirname; } - configureTypegoose(uri: string) { - const schemas = [ - {schema: Schemas.Event, bindingKey: BindingKeys.Connection1.Event}, - {schema: Schemas.User, bindingKey: BindingKeys.Connection1.User}, - ]; - const discriminators = [ - { - schema: Schemas.LoginEvent, - modelKey: BindingKeys.Connection1.Event, - bindingKey: BindingKeys.Connection1.LoginEvent, - }, - ]; - - this.configure(TypegooseBindings.COMPONENT).to([ - { - uri, - connectionOptions: {userNewUrlParser: true}, - schemas, - discriminators, - }, - ]); + async start() { + debug('TestApplicationConfig:start()'); + await configureTypegoose(this, this.options.mongoUri); + this.component(LoopbackTypegooseComponent); + await super.start(); } } diff --git a/extensions/typegoose/src/__tests__/integration/schemas/index.ts b/extensions/typegoose/src/__tests__/integration/schemas/index.ts index a44758895f12..e566a88997aa 100644 --- a/extensions/typegoose/src/__tests__/integration/schemas/index.ts +++ b/extensions/typegoose/src/__tests__/integration/schemas/index.ts @@ -2,12 +2,15 @@ // Node module: @loopback/typegoose // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT - -import {BindingKey} from '@loopback/core'; +import {BindingKey, Context} from '@loopback/core'; import {ReturnModelType} from '@typegoose/typegoose'; +import debugFactory from 'debug'; +import {createConnection} from 'mongoose'; +import {TypegooseBindings} from '../../../keys'; import EventSchema from './event.schema'; import LoginEventSchema from './login-event.schema'; import UserSchema from './user.schema'; +const debug = debugFactory('loopback:typegoose:integration-test:setup'); export namespace Schemas { export const Event = EventSchema; @@ -24,25 +27,51 @@ export namespace Models { export namespace BindingKeys { export namespace Connection1 { export const Event = BindingKey.create( - 'typegoose.models.conn1.Event', + 'typegoose-integration-text.models.conn1.Event', ); export const LoginEvent = BindingKey.create( - 'typegoose.models.conn1.LoginEvent', + 'typegoose-integration-text.models.conn1.LoginEvent', ); export const User = BindingKey.create( - 'typegoose.models.conn1.User', + 'typegoose-integration-text.models.conn1.User', ); } export namespace Connection2 { export const Event = BindingKey.create( - 'typegoose.models.conn2.Event', + 'typegoose-integration-text.models.conn2.Event', ); export const LoginEvent = BindingKey.create( - 'typegoose.models.conn2.LoginEvent', + 'typegoose-integration-text.models.conn2.LoginEvent', ); export const User = BindingKey.create( - 'typegoose.models.conn2.User', + 'typegoose-integration-text.models.conn2.User', ); } } + +export async function configureTypegoose(context: Context, uri: string) { + debug('Creating mongoose connection'); + const connection = await createConnection(uri, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + + debug('Adding configuration'); + context.configure(TypegooseBindings.COMPONENT).to([ + { + connection, + schemas: [ + {schema: Schemas.Event, bindingKey: BindingKeys.Connection1.Event}, + {schema: Schemas.User, bindingKey: BindingKeys.Connection1.User}, + ], + discriminators: [ + { + schema: Schemas.LoginEvent, + modelKey: BindingKeys.Connection1.Event, + bindingKey: BindingKeys.Connection1.LoginEvent, + }, + ], + }, + ]); +} diff --git a/extensions/typegoose/src/component.ts b/extensions/typegoose/src/component.ts index cacdecc78e36..08cceabe949e 100644 --- a/extensions/typegoose/src/component.ts +++ b/extensions/typegoose/src/component.ts @@ -22,10 +22,32 @@ import { import debugFactory from 'debug'; import {Connection, createConnection} from 'mongoose'; import {TypegooseBindings} from './keys'; -import {TypegooseConfig, TypegooseConnectionOptions} from './types'; +import { + BaseTypegooseConnectionOptions, + ExistingTypegooseConnectionOptions, + TypegooseConfig, + UriTypegooseConnectionOptions, +} from './types'; const debug = debugFactory('loopback:typegoose'); +function isExistingConnectionOptions( + opts: BaseTypegooseConnectionOptions, +): opts is ExistingTypegooseConnectionOptions { + // eslint-disable-next-line no-prototype-builtins + return opts.hasOwnProperty('connection'); +} + +function isUriConnectionOptions( + opts: BaseTypegooseConnectionOptions, +): opts is UriTypegooseConnectionOptions { + return ( + // eslint-disable-next-line no-prototype-builtins + opts.hasOwnProperty('uri') && + typeof (opts as UriTypegooseConnectionOptions).uri === 'string' + ); +} + @bind({ tags: {[ContextTags.KEY]: TypegooseBindings.COMPONENT}, scope: BindingScope.SINGLETON, @@ -40,14 +62,17 @@ export class LoopbackTypegooseComponent protected application: Application, @config() readonly typegooseConfig: TypegooseConfig = [], - ) {} + ) { + debug('LoopbackTypegooseComponent:constructed()'); + } async start() { + debug('LoopbackTypegooseComponent:start()'); /** * You can have multiple connections and you can use schemas on different * connections to create connection-model instances. */ - let connectionOptions: TypegooseConnectionOptions[]; + let connectionOptions: BaseTypegooseConnectionOptions[]; if (Array.isArray(this.typegooseConfig)) { connectionOptions = this.typegooseConfig; @@ -57,10 +82,21 @@ export class LoopbackTypegooseComponent for (const connectionIndex in connectionOptions) { const connectionConfig = connectionOptions[connectionIndex]; - const connection = await createConnection( - connectionConfig.uri, - connectionConfig.connectionOptions, - ); + let connection: Connection; + if (isExistingConnectionOptions(connectionConfig)) { + debug(`Using existing connection`); + connection = connectionConfig.connection; + } else if (isUriConnectionOptions(connectionConfig)) { + debug(`Creating a new connection given a mongodb URI`); + connection = await createConnection( + connectionConfig.uri, + connectionConfig.connectionOptions, + ); + } else { + throw new Error( + 'Connection options must either include a connection `uri`, or an existing `connection`.', + ); + } // Add the connection to our array. We don't want to depend on // mongoose as a singleton since we could have multiple applications // or servers running, and the singleton would disconnect other @@ -131,6 +167,7 @@ export class LoopbackTypegooseComponent } async stop() { + debug('LoopbackTypegooseComponent:stop()'); for (const connection of this.mongooseConnections) { await connection.close(); } diff --git a/extensions/typegoose/src/types.ts b/extensions/typegoose/src/types.ts index 351f9a062a8c..7d44c6776330 100644 --- a/extensions/typegoose/src/types.ts +++ b/extensions/typegoose/src/types.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {BindingKey} from '@loopback/core'; -import {ReturnModelType} from '@typegoose/typegoose'; +import {mongoose, ReturnModelType} from '@typegoose/typegoose'; import { AnyParamConstructor, ICustomOptions, @@ -45,7 +45,7 @@ export interface TypegooseDiscriminator< schema: U; } -export interface TypegooseConnectionOptions { +export interface BaseTypegooseConnectionOptions { uri: string; connectionOptions?: ConnectionOptions; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -54,6 +54,17 @@ export interface TypegooseConnectionOptions { discriminators?: TypegooseDiscriminator[]; } +export interface UriTypegooseConnectionOptions + extends BaseTypegooseConnectionOptions { + uri: string; +} + +export interface ExistingTypegooseConnectionOptions + extends BaseTypegooseConnectionOptions { + connection: mongoose.Connection; +} + export type TypegooseConfig = - | TypegooseConnectionOptions - | TypegooseConnectionOptions[]; + | ExistingTypegooseConnectionOptions + | UriTypegooseConnectionOptions + | Array;