diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f9f16b0 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +examples/ +test/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..cb47910 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends": "airbnb", + "rules": { + "comma-dangle": "off", + "global-require":"off", + "no-param-reassign": "off" + }, + "parserOptions": { + "ecmaVersion": 8 + } +} \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 757d261..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - 'extends': 'eslint:recommended' - ,'rules': { - 'indent': ['error', 2] - } - ,'env': { - 'mocha': true - ,'node': true - }, - 'globals': { - 'expect': true, - 'getDataSource': true, - 'should': true, - 'assert': true, - 'getSchema': true, - 'getSettings': true - } -}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index f13251c..b4b2f37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea node_modules examples/db.json -test/**/*.log \ No newline at end of file +test/**/*.log +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 643ec5a..5e793ac 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,9 @@ [![Join the chat at https://gitter.im/strongloop-community/loopback-connector-elastic-search](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/strongloop-community/loopback-connector-elastic-search?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Basic Elasticsearch datasource connector for [Loopback](https://loopback.io/). +Elasticsearch(versions 6.x and 7.x) datasource connector for [Loopback 3.x](https://loopback.io/). - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* +# Table of Contents - [Overview](#overview) - [Install this connector in your loopback app](#install-this-connector-in-your-loopback-app) @@ -16,27 +14,19 @@ Basic Elasticsearch datasource connector for [Loopback](https://loopback.io/). - [Recommended properties](#recommended) - [Optional properties](#optional) - [Sample for copy paste](#sample) -- [About the example app](#about-the-example-app) - - [Run both example and ES in docker](#run-both-example-and-es-in-docker) - - [Run example locally and ES in docker](#run-example-locally-and-es-in-docker) - - [Run example locally](#run-example-locally) - [How to achieve Instant search](#how-to-achieve-instant-search) - [Troubleshooting](#troubleshooting) -- [Testing](#testing) - [Contributing](#contributing) - [Frequently Asked Questions](#faqs) -- [Release notes](#release-notes) - - ## Overview 1. `lib` directory has the entire source code for this connector - 1. this is what gets downloaded to your `node_modules` folder when you run `npm install loopback-connector-es --save --save-exact` + 1. this is what gets downloaded to your `node_modules` folder when you run `npm install loopback-connector-esv6 --save --save-exact` 1. `examples` directory has a loopback app which uses this connector 1. this is not published to NPM, it is only here for demo purposes 1. it will not be downloaded to your `node_modules` folder! - 1. similarly the `examples/server/datasources.json` and `examples/server/datasources..js` files are there for this demo app to use + 1. similarly the `examples/server/datasources.json` file is there for this demo app to use 1. you can copy their content over to `/server/datasources.json` or `/server/datasources..js` if you want and edit it there but don't start editing the files inside `examples/server` itself and expect changes to take place in your app! 1. `test` directory has unit tests 1. it does not reuse the loopback app from the `examples` folder @@ -52,305 +42,138 @@ Basic Elasticsearch datasource connector for [Loopback](https://loopback.io/). ## Install this connector in your loopback app -``` +```bash cd npm install loopback-connector-esv6 --save --save-exact ``` ## Configuring connector -### Required: -- **host:** Elasticsearch engine host address. -- **port:** Elasticsearch engine port. -- **name:** Connector name. -- **connector:** Elasticsearch driver. -- **index:** Search engine specific index. defaults to `shakespeare`. (mandatory) -- **apiVersion:** specify the major version of the Elasticsearch nodes you will be connecting to. - -### Recommended: -- **mappingType:** mapping type for provided index. defaults to `basedata` -- **mappingProperties:** An object with properties for above mentioned **mappingType** +### Important Note -#### Important Note: -- This package is created to support ElasticSearch v6.x only. +- **This connector will only connect to one index per datasource.** +- This package is created to support ElasticSearch v6.x and 7.x only. - `docType` property is automatically added in mapping properties which is required to differentiate documents stored in index with loopback model data. It stores loopback modelName value. `docType: { type: "keyword", index: true }` -### Optional: -- **log:** sets elasticsearch client's logging, you can refer to the docs [here](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html#config-log) -- **defaultSize:** total number of results to return per page. -- **refreshOn** optional array with method names you want to set refresh option as true -- **requestTimeout:** this value is in milliseconds -- **ssl:** useful for setting up a secure channel -- **protocol:** can be `http` or `https` (`http` is the default if none specified) ... *must* be `https` if you're using `ssl` -- **auth**: useful if you have access control setup via services like `es-jetty` or `found` or `shield` -- **amazonES**: configuration for `http-aws-es` NOTE: The package needs to be installed in your project. Its not part of this Connector. Version 1.x.x (currently 1.1.3) will have to be used, later versions will not pass through aws configuration. - -### Sample: -1. Edit **datasources.json** and set: - - ``` - "db": { - "connector": "es", - "name": "", - "index": "", - "hosts": [ - { - "protocol": "http", - "host": "127.0.0.1", - "port": 9200, - "auth": "username:password" - } - ], - "apiVersion": "6.0", - "refreshOn": ["save","create", "updateOrCreate"], - "log": "trace", - "defaultSize": , - "requestTimeout": 30000, - "ssl": { - "ca": "./../cacert.pem", - "rejectUnauthorized": true - }, - "amazonES": { - "region": "us-east-1", - "accessKey": "AKID", - "secretKey": "secret" - }, - "mappingType": "basedata", - "mappingProperties": { - "id": { - "type": "keyword", - "index": true - }, - "docType": { - "type": "keyword", - "index": true - }, - "name": { - "type": "text", - "index": true - }, - "realm": { - "type": "keyword", - "index": true - }, - "username": { - "type": "keyword", - "index": true - }, - "description": { - "type": "text", - "index": true - }, - "roleId": { - "type": "keyword", - "index": true - } - }, - "settings": {} +### Required + +- **name:** name of the connector. +- **connector:** Elasticsearch driver **'esv6'**. +- **configuration:** Elasticsearch client configuraiton object which includes nodes, authetication and ssl coonfiguration. Please refer this [official link](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-configuration.html) for more information on configuraiton. +- **index:** Name of the ElasticSearch index `eg: shakespeare`. +- **version:** specify the major version of the Elasticsearch nodes you will be connecting to. Supported versions: [6, 7] `eg: version: 7` +- **mappingType:** mapping type for provided index. defaults to `basedata`. Required only for version: 6 +- **mappingProperties:** An object with properties for above mentioned `mappingType` + +### Optional + +- **indexSettings:** optional settings object for creating index. +- **defaultSize:** Search size limit. Default is 50. + +### Sample + +1.Edit **datasources.json** and set: + +```javascript + + "elastic-search-ssl": { + "name": "elasticsearch-example-index-datasource", + "connector": "esv6", + "version": 7, + "index": "example-index", + "configuration": { // Elastic client configuration + "node": "http://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000, + "auth": { + "username": "test", + "password": "test" + }, + "ssl": { + "rejectUnauthorized": true + } + }, + "defaultSize": 50, + "indexSettings": { // Elastic index settings + "number_of_shards": 2, + "number_of_replicas": 1 + }, + "mappingType": "basedata", // not required for verison: 7, will be ignored + "mappingProperties": { + "docType": { + "type": "keyword", + "index": true + }, + "id": { + "type": "keyword", + "index": true + }, + "seq": { + "type": "integer", + "index": true + }, + "name": { + "type": "keyword", + "index": true + }, + "email": { + "type": "keyword", + "index": true + }, + "birthday": { + "type": "date", + "index": true + }, + "role": { + "type": "keyword", + "index": true + }, + "order": { + "type": "integer", + "index": true + }, + "vip": { + "type": "boolean", + "index": true + }, + "objectId": { + "type": "keyword", + "index": true + }, + "ttl": { + "type": "integer", + "index": true + }, + "created": { + "type": "date", + "index": true } - ``` -2. You can peek at `/examples/server/datasources.sample-es-6.json` for more hints. + } +} +``` + +2.You can peek at `/examples/server/datasources.json` for more hints. ## About the example app 1. The `examples` directory contains a loopback app which uses this connector. 1. You can point this example at your own elasticsearch instance or use the quick instances provided via docker. -### Run both example and ES in docker - -As a developer, you may want a short lived ES instance that is easy to tear down when you're finished dev testing. We recommend docker to facilitate this. - -**Pre-requisites** -You will need [docker-engine](https://docs.docker.com/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/) installed on your system. - -**Step-1** -- Set desired versions for **node** and **Elasticsearch** - - here are the [valid values](https://hub.docker.com/r/library/node/tags/) to use for **Node** - - here are the [valid values](https://hub.docker.com/r/library/elasticsearch/tags/) to use for **Elasticsearch** -``` -# combination of node v0.10.46 with elasticsearch v1 -export NODE_VERSION=0.10.46 -export ES_VERSION=1 -echo 'NODE_VERSION' $NODE_VERSION && echo 'ES_VERSION' $ES_VERSION - -# similarly feel free to try relevant combinations: -## of node v0.10.46 with elasticsearch v2 -## of node v0.12 with elasticsearch v2 -## of node v0.4 with elasticsearch v2 -## of node v5 with elasticsearch v2 -## elasticsearch v5 will probably not work as there isn't an `elasticsearch` client for it, as of this writing -## etc. -``` -**Step-2** -- Run the setup with `docker-compose` commands. - -``` -git clone https://github.com/strongloop-community/loopback-connector-elastic-search.git myEsConnector -cd myEsConnector/examples -npm install -docker-compose up -``` - -**Step-3** -- Visit `localhost:3000/explorer` and you will find our example loopback app running there. - -### Run example locally and ES in docker - -1. Empty out `examples/server/datasources.json` so that it only has the following content remaining: `{}` -1. Set the `NODE_ENV` environment variable on your local/host machine - 1. Set the environment variable `NODE_ENV=sample-es-plain-1` if you want to use `examples/server/datasources.sample-es-plain-1.js` - 1. Set the environment variable `NODE_ENV=sample-es-plain-2` if you want to use `examples/server/datasources.sample-es-plain-2.js` - 1. Set the environment variable `NODE_ENV=sample-es-ssl-1` if you want to use `examples/server/datasources.sample-es-ssl-1.js` - 1. a sample docker instance for this hasn't been configured yet, so it doesn't work out-of-the-box, use it only as readable (not runnable) reference material for now - 1. You can configure your own `datasources.json` or `datasources..js` based on what you learn from these sample files. - 1. Technically, to run the example, you don't need to set `NODE_ENV` **if you won't be configuring via the `..js` files** ... configuring everything within `datasources.json` is perfectly fine too. Just remember that you will lose the ability to have inline comments and will have to use double-quotes if you stick with `.json` -1. Start elasticsearch version 1.x and 2.x using: - - ``` - git clone https://github.com/strongloop-community/loopback-connector-elastic-search.git myEsConnector - cd myEsConnector - docker-compose -f docker-compose-for-tests.yml up - - # in another terminal window or tab - cd myEsConnector/examples - npm install - DEBUG=boot:test:* node server/server.js - ``` -1. Visit `localhost:3000/explorer` and you will find our example loopback app running there. - -### Run example locally - -1. Install dependencies and start the example server - - ``` - git clone https://github.com/strongloop-community/loopback-connector-elastic-search.git myEsConnector - cd myEsConnector/examples - npm install - ``` -2. [Configure the connector](#configuring-connector) - * Don't forget to create an index in your ES instance: `curl -X POST https://username:password@my.es.cluster.com/shakespeare` - * If you mess up and want to delete, you can use: `curl -X DELETE https://username:password@my.es.cluster.com/shakespeare` - * Don't forget to set a [valid value](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html#config-api-version) for `apiVersion` field in `examples/server/datasources.json` that matches the version of ES you are running. -3. Set up a `cacert.pem` file for communicating securely (https) with your ES instance. Download the certificate chain for your ES server using this **sample** (will need to be edited to *use* your provider) command: - - ``` - cd myEsConnector - openssl s_client -connect my.es.cluster.com:9243 -showcerts | tee cacert.pem - ``` - 1. The command may not self terminate so you may need to use `ctrl+c` - 2. It will be saved at the base of your cloned project - 3. Sometimes extra data is added to the file, you should delete everything after the following lines: - - ``` - --- - No client certificate CA names sent - --- - ``` -4. Run: - - ``` - cd myEsConnector/examples - DEBUG=boot:test:* node server/server.js - ``` - * The `examples/server/boot/boot.js` file will automatically populate data for UserModels on your behalf when the server starts. -5. Open this URL in your browser: [http://localhost:3000/explorer](http://localhost:3000/explorer) - * Try fetching all the users via the rest api console - * You can dump all the data from your ES index, via cmd-line too: `curl -X POST username:password@my.es.cluster.com/shakespeare/_search -d '{"query": {"match_all": {}}}'` -6. To test a specific filter via GET method, use for example: `{"q" : "friends, romans, countrymen"}` - -## How to achieve Instant search - -From version 1.3.4, `refresh` option is added which support's instant search after `create` and `update`. This option is configurable and one can activate or deactivate it according to their need. `By default refresh is true` which makes response to come only after documents are indexed(searchable). -To know more about `refresh` go through this [article](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-refresh.html) - -* [Related Issue](https://github.com/strongloop-community/loopback-connector-elastic-search/issues/72) -* [Related PR](https://github.com/strongloop-community/loopback-connector-elastic-search/pull/81) - -### Ways to configure refresh -**Datasource File:** Pass `refreshOn` array from datasource file including methods name in which you want this to be `true` -``` - "es": { - "name": "es", - "refreshOn": ["save","create", "updateOrCreate"], - ..... -``` -**Model.json file:** Configurable on per model and operation level (`true`, `false`, `wait_for`) -``` - "elasticsearch": { - "create": { - "refresh": false - }, - "destroy": { - "refresh": false - }, - "destroyAll": { - "refresh": "wait_for" - } - } -``` -###### NOTE:- *While a refresh is useful, it still has a performance cost. A manual refresh can be useful, but avoid manual refresh every time you index a document in production; it will hurt your performance. Instead, your application needs to be aware of the near real-time nature of Elasticsearch and make allowances for it.* - ## Troubleshooting 1. Do you have both `elasticsearch-ssl` and `elasticsearch-plain` in your `datasources.json` file? You just need one of them (not both), based on how you've setup your ES instance. 1. Did you forget to set `model-config.json` to point at the datasource you configured? Maybe you are using a different or misspelled name than what you thought you had! -1. Did you forget to set a [valid value](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html#config-api-version) for `apiVersion` field in `datasources.json` that matches the version of ES you are running? -1. Maybe the version of ES you are using isn't supported by the client that this project uses. Try removing the `elasticsearch` sub-dependency from `/node_modules/loopback-connector-es/node_modules` folder and then install the latest client: - 1. `cd /node_modules/loopback-connector-es/node_modules` - 1. then remove the `elasticsearch` folder - 1. unix/mac quickie: `rm -rf elasticsearch` - 1. `npm install --save --save-exact https://github.com/elastic/elasticsearch-js.git` - 1. to "academically" prove to yourself that this will work with the new install: - 1. on unix/mac you can quickly dump the supported versions to your terminal with: `cat elasticsearch/package.json | grep -A 5 supported_es_branches` - 2. on other platforms, look into the `elasticsearch/package.json` and search for the `supported_es_branches` json block. +1. Make sure to configure major version of Elastic in `version` +1. Maybe the version of ES you are using isn't supported by the client that this project uses. Try removing the `elasticsearch` sub-dependency from `/node_modules/loopback-connector-esv6/node_modules` folder and then install the latest client: + 1. `cd /node_modules/loopback-connector-esv6/node_modules` + 1. then remove `es6` && `es7` folder + 1. unix/mac quickie: `rm -rf es6 es7` + 1. `npm install` 1. go back to yourApp's root directory 1. unix/mac quickie: `cd ` 1. And test that you can now use the connector without any issues! 1. These changes can easily get washed away for several reasons. So for a more permanent fix that adds the version you want to work on into a release of this connector, please look into [Contributing](#contributing). -## Testing - -1. You can edit `test/resource/datasource-test.json` to point at your ES instance and then run `npm test` -1. If you don't have an ES instance and want to leverage docker based ES instances then: - 1. If you want to run all tests across all versions in one go, then: - 1. Run `docker-compose -f docker-compose-for-testing-all.yml up` - 1. Then run `npm test` - 1. When you're finished and want to tear down the docker instances, run: `docker-compose -f docker-compose-for-testing-all.yml down` - 1. You can test a specific version of elasticsearch if you want - 1. elasticsearch version 1.x - 1. Run `docker-compose -f docker-compose-for-testing-v1.yml up` - 1. Then run `npm run testv1` - 1. To run tests with additional logging, use: - 1. `DEBUG=test:es-v1:* npm run testv1` - 1. `DEBUG=test:es-v1:*,loopback:connector:elasticsearch npm run testv1` - 1. [Troubleshoot test with node-inspector](http://blog.andrewray.me/how-to-debug-mocha-tests-with-chrome/) if the level of details is still not enough: - 1. `npm run testv1 -- --debug-brk` - 1. `DEBUG=test:es-v1:* npm run testv1 -- --debug-brk` - 1. `DEBUG=test:es-v1:*,loopback:connector:elasticsearch npm run testv1 -- --debug-brk` - 1. When you're finished and want to tear down the docker instances, run: `docker-compose -f docker-compose-for-testing-v1.yml down` - 1. elasticsearch version 2.x - 1. Run `docker-compose -f docker-compose-for-testing-v2.yml up` - 1. Then run `npm run testv2` - 1. To run tests with additional logging, use: - 1. `DEBUG=test:es-v2:* npm run testv2` - 1. `DEBUG=test:es-v2:*,loopback:connector:elasticsearch npm run testv2` - 1. [Troubleshoot test with node-inspector](http://blog.andrewray.me/how-to-debug-mocha-tests-with-chrome/) if the level of details is still not enough: - 1. `npm run testv2 -- --debug-brk` - 1. `DEBUG=test:es-v2:* npm run testv2 -- --debug-brk` - 1. `DEBUG=test:es-v2:*,loopback:connector:elasticsearch npm run testv2 -- --debug-brk` - 1. When you're finished and want to tear down the docker instances, run: `docker-compose -f docker-compose-for-testing-v2.yml down` - 1. elasticsearch version 5.x - 1. Run `docker-compose -f docker-compose-for-testing-v5.yml up` - 1. Then run `npm run testv5` - 1. To run tests with additional logging, use: - 1. `DEBUG=test:es-v5:* npm run testv5` - 1. `DEBUG=test:es-v5:*,loopback:connector:elasticsearch npm run testv5` - 1. [Troubleshoot test with node-inspector](http://blog.andrewray.me/how-to-debug-mocha-tests-with-chrome/) if the level of details is still not enough: - 1. `npm run testv5 -- --debug-brk` - 1. `DEBUG=test:es-v5:* npm run testv5 -- --debug-brk` - 1. `DEBUG=test:es-v5:*,loopback:connector:elasticsearch npm run testv5 -- --debug-brk` - 1. When you're finished and want to tear down the docker instances, run: `docker-compose -f docker-compose-for-testing-v5.yml down` - ## Contributing 1. Feel free to [contribute via PR](https://github.com/strongloop-community/loopback-connector-elastic-search/pulls) or [open an issue](https://github.com/strongloop-community/loopback-connector-elastic-search/issues) for discussion or jump into the [gitter chat room](https://gitter.im/strongloop-community/loopback-connector-elastic-search) if you have ideas. @@ -373,12 +196,9 @@ To know more about `refresh` go through this [article](https://www.elastic.co/gu ## FAQs 1. How do we enable or disable the logs coming from the underlying elasticsearch client? There may be a need to debug/troubleshoot at times. - 1. Use the `"log": "trace"` field in your datasources file or omit it. You can refer to the detailed docs [here](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html#config-log) and [here](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/logging.html) + 1. Use the env variable `DEBUG=elasticsearch` for elastic client logs. 1. How do we enable or disable the logs coming from this connector? 1. By default if you do not set the following env variable, they are disabled: `DEBUG=loopback:connector:elasticsearch` - 1. For example, try running tests with and without it, to see the difference: - 1. with: `DEBUG=loopback:connector:elasticsearch npm test` - 1. without: `npm test` 1. What are the tests about? Can you provide a brief overview? 1. Tests are prefixed with `01` or `02` etc. in order to run them in that order by leveraging default alphabetical sorting. 1. The `02.basic-querying.test.js` file uses two models to test various CRUD operations that any connector must provide, like `find(), findById(), findByIds(), updateAttributes()` etc. @@ -390,7 +210,3 @@ To know more about `refresh` go through this [article](https://www.elastic.co/gu 1. An automatically generated id-like field that is maintained by ES is `_id`. Without some sort of es-field-level-scripting-on-index (if that is possible at all) ... I am not sure how we could ask elasticsearch to take over auto-generating an id-like value for any arbitrary field! So the connector is setup such that adding `id: {type: String, generated: true, id: true}` will tell it to use `_id` as the actual field backing the `id` ... you can keep using the doing `model.id` abstraction and in the background `_id` values are mapped to it. 1. Will this work for any field marked as with `generated: true` and `id: true`? 1. No! The connector isn't coded that way right now ... while it is an interesting idea to couple any such field with ES's `_id` field inside this connector ... I am not sure if this is the right thing to do. If you had `objectId: {type: String, generated: true, id: true}` then you won't find a real `objectId` field in your ES documents. Would that be ok? Wouldn't that confuse developers who want to write custom queries and run 3rd party app against their ES instance? Don't use `objectId`, use `_id` would have to be common knowledge. Is that ok? - -## Release notes - - * TBD diff --git a/cacert.pem b/cacert.pem deleted file mode 100644 index 4b33615..0000000 --- a/cacert.pem +++ /dev/null @@ -1,141 +0,0 @@ -CONNECTED(00000003) ---- -Certificate chain - 0 s:/OU=Domain Control Validated/OU=EssentialSSL Wildcard/CN=*.shoppinpal.com - i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA ------BEGIN CERTIFICATE----- -MIIFWTCCBEGgAwIBAgIRAK2QR2YGpGTbDQBJm0GMF04wDQYJKoZIhvcNAQELBQAw -gZAxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTYwNAYD -VQQDEy1DT01PRE8gUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIg -Q0EwHhcNMTUwNDA1MDAwMDAwWhcNMTYwNzAzMjM1OTU5WjBeMSEwHwYDVQQLExhE -b21haW4gQ29udHJvbCBWYWxpZGF0ZWQxHjAcBgNVBAsTFUVzc2VudGlhbFNTTCBX -aWxkY2FyZDEZMBcGA1UEAxQQKi5zaG9wcGlucGFsLmNvbTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBANobBwcN03si2pO2rVw6VnC6FGeWQhW+h7OnKP5x -IOayEFqSgYpM+ExpOHpX+0FsvrhqP4UyXMA4RutXryWxsvrQ/KPBsr+283rQEB/F -ICMg07pggwtftuRtOj4CMjTsBl8xRrXWO1epAImUrlJSqUOUvDhmvnRsRQtyFnT7 -libYsk7/oKIAQVNC2CiPVQUzS+yF3f5fQ/0V+loBof9AD6MsHUW1e1c3SoFQWlTy -8rBnQsJsM4/2fMTtgujFSizKKkYDbJM1euHvExTDej16x8pCKa/ca9da2/bfHwJ0 -btSFLXMxKsjw+5DOrlSZefvhO1BWG/JM0IgqjpYwlg3RbA0CAwEAAaOCAd0wggHZ -MB8GA1UdIwQYMBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBQsZYWb -MFaio699b72uEZmLwzjvhzAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAd -BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEE -AbIxAQICBzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29t -L0NQUzAIBgZngQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21v -ZG9jYS5jb20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNB -LmNybDCBhQYIKwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNv -bW9kb2NhLmNvbS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVy -Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wKwYD -VR0RBCQwIoIQKi5zaG9wcGlucGFsLmNvbYIOc2hvcHBpbnBhbC5jb20wDQYJKoZI -hvcNAQELBQADggEBAClNtFgZ0I1f0XFFZnQwJ69z9wICqer7lOAidc9Ur2pAxmuW -2YA7sdR7pWDTJI5RZXZxNbxdBXwrjozgT182RmdM53ak5OCRxUOrG8KRpEKeO6oV -Z6HaV8354tv0uQ4tKCW9+ap5R3q5B0pq2ZN4PEsUzuym7b4ukuVyBvuwvYR6F9bT -TE3OhWXVFzNyJevrGZhqEFDSh7Pq1hz5hf+msr8lXsRgbkYU2t1Rbzbv3oNYRWph -jLPTfVI5CjGB7McpMaL3xSiAqwEHtG77IP0IEkE7oWoeblaX0rQP0PbuorXVtpvR -eHGgWFxAmvGiiu6+b09rkIx18LvwxeGla7rVKM0= ------END CERTIFICATE----- - 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA - i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority ------BEGIN CERTIFICATE----- -MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy -MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh -bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh -bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0 -Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6 -ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51 -UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n -c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY -MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz -30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV -HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG -BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv -bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB -AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E -T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v -ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p -mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/ -e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps -P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY -dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc -2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG -V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4 -HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX -j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII -0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap -lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf -+AZxAeKCINT+b72x ------END CERTIFICATE----- - 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority - i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root ------BEGIN CERTIFICATE----- -MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk -ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF -eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow -gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD -VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw -AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6 -2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr -ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt -4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq -m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/ -vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT -8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE -IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO -KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO -GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/ -s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g -JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD -AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9 -MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy -bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6 -Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ -zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj -Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY -Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5 -B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx -PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR -pu/xO28QOG8= ------END CERTIFICATE----- - 3 s:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root - i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- ---- -Server certificate -subject=/OU=Domain Control Validated/OU=EssentialSSL Wildcard/CN=*.shoppinpal.com -issuer=/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA ---- -No client certificate CA names sent ---- diff --git a/docker-compose-for-testing-all.yml b/docker-compose-for-testing-all.yml deleted file mode 100644 index 17b8a79..0000000 --- a/docker-compose-for-testing-all.yml +++ /dev/null @@ -1,8 +0,0 @@ -## Version Selection for compose file -# https://docs.docker.com/compose/compose-file/#/versioning -version: '2' -services: - es_v5: - image: elasticsearch:5 - ports: - - "9203:9200" diff --git a/docker-compose-for-testing-v5.yml b/docker-compose-for-testing-v5.yml deleted file mode 100644 index 17b8a79..0000000 --- a/docker-compose-for-testing-v5.yml +++ /dev/null @@ -1,8 +0,0 @@ -## Version Selection for compose file -# https://docs.docker.com/compose/compose-file/#/versioning -version: '2' -services: - es_v5: - image: elasticsearch:5 - ports: - - "9203:9200" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ceb5c34..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -## Version Selection for compose file -# https://docs.docker.com/compose/compose-file/#/versioning -version: '2' -services: - web: - image: node:${NODE_VERSION} - working_dir: /apps/examples - entrypoint: /apps/docker-entrypoint.sh - command: node server/server.js - environment: - DEBUG: boot:test:* - ports: - - "80:3000" - volumes: - - .:/apps - depends_on: - - es - restart: always - es: - image: elasticsearch:${ES_VERSION} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index 0508d99..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# setting up prerequisites - -if [ -f server/datasources.bak ]; then - cp server/datasources.bak server/datasources.json -else - cp server/datasources.json server/datasources.bak -fi -if [ -f server/model-config.bak ]; then - cp server/model-config.bak server/model-config.json -else - cp server/model-config.json server/model-config.bak -fi -sed -i 's/hosted\.foundcluster\.com/es/g; s/9243/9200/g' /apps/examples/server/datasources.json -sed -i 's/db/elasticsearch-plain/g' /apps/examples/server/model-config.json -sed -i '21,41d; s/"requestTimeout": 30000/"requestTimeout": 30000,/' /apps/examples/server/datasources.json -if [ -d node_modules ]; then - rm -rf node_modules -fi -npm install -curl -X POST http://es:9200/shakespeare -exec "$@" diff --git a/examples/client/client.js b/examples/client/client.js new file mode 100644 index 0000000..c98f06e --- /dev/null +++ b/examples/client/client.js @@ -0,0 +1,138 @@ +const dataSource = { + "name": "elasticsearch-example-index-datasource", + "connector": "esv6", + "version": 6, + "index": "example-index", + "configuration": { + "node": "http://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000 + }, + "defaultSize": 50, + "indexSettings": {}, + "mappingType": "basedata", + "mappingProperties": { + "id": { + "type": "keyword" + }, + "seq": { + "type": "integer" + }, + "name": { + "type": "keyword", + "fields": { + "native": { + "type": "keyword" + } + } + }, + "email": { + "type": "keyword" + }, + "birthday": { + "type": "date" + }, + "role": { + "type": "keyword" + }, + "order": { + "type": "integer" + }, + "vip": { + "type": "boolean" + }, + "objectId": { + "type": "keyword" + }, + "ttl": { + "type": "integer" + }, + "created": { + "type": "date" + } + } +}; + +const SupportedVersions = [6, 7]; // Supported elasticsearch versions +// 'Client' will be assigned either Client6 or Client7 from below definitions based on version +let Client = null; +const { Client: Client6 } = require('es6'); +const { Client: Client7 } = require('es7'); +const version = 6; +Client = version === 6 ? Client6 : Client7; +const db = new Client(dataSource.configuration); + +db.ping().then(({ body }) => { + console.log(body); +}).catch((e) => { + console.log(e); +}); + +db.indices.create({ + index: 'helloworld', + body: { + settings: {}, + mappings: { + basedata: { + properties: { + name: { + type: 'keyword' + } + } + } + } + } +}).then((response) => { + console.log(response); +}).catch((e) => { + console.log(e); +}); + +/* db.indices.putMapping({ + index: 'hello', + type: 'basedata', + body: { + properties: { + name: { + type: 'keyword' + } + } + } +}).then(({ body }) => { + console.log(body); +}).catch((e) => { + console.log(e); +}); */ + +db.count({ + index: 'hello' +}).then(({ body }) => { + console.log(body); +}).catch((e) => { + console.log(e); +}); + +/* db.create({ + index: 'hello', + type: 'basedata', + id: 'aasddd', + body: { + name: 'hello' + } +}).then(({ body }) => { + console.log(body); +}).catch((e) => { + console.log(e); +}); */ + +/* db.index({ + index: 'hello', + type: 'basedata', + body: { + name: 'hello2s' + } +}).then(({ body }) => { + console.log(body, body._id); +}).catch((e) => { + console.log(e); +}); */ diff --git a/examples/package.json b/examples/package.json index e597c33..2c4ef13 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,21 +1,22 @@ { "name": "example", - "version": "0.0.0", + "version": "2.0.0", "main": "server/server.js", "scripts": { "pretest": "jshint ." }, "dependencies": { - "bluebird": "^2.9.27", - "compression": "^1.0.3", - "debug": "^2.2.0", - "errorhandler": "^1.1.1", - "lodash": "^3.9.3", - "loopback": "^2.0.0", - "loopback-boot": "^2.0.0", - "loopback-connector-es": "^1.0.6", - "loopback-datasource-juggler": "^2.7.0", - "serve-favicon": "^2.0.1" + "compression": "1.7.3", + "cors": "2.8.5", + "helmet": "3.16.0", + "loopback": "3.25.0", + "loopback-boot": "3.2.0", + "loopback-component-explorer": "6.3.1", + "loopback-component-storage": "3.5.0", + "loopback-connector-esv6": "1.3.2", + "ramda": "0.26.1", + "serve-favicon": "2.5.0", + "strong-error-handler": "3.2.0" }, "optionalDependencies": { "loopback-explorer": "^1.1.0" diff --git a/examples/server/component-config.json b/examples/server/component-config.json new file mode 100644 index 0000000..2000020 --- /dev/null +++ b/examples/server/component-config.json @@ -0,0 +1,10 @@ +{ + "loopback-component-explorer": { + "apiInfo": { + "title": "Elastic App APIs", + "description": "Explore Elastic App APIs" + }, + "mountPath": "/api/explorer", + "generateOperationScopedModels": true + } +} diff --git a/examples/server/config.json b/examples/server/config.json index b275c97..3b7c877 100644 --- a/examples/server/config.json +++ b/examples/server/config.json @@ -1,7 +1,26 @@ { - "restApiRoot": "/api", - "host": "0.0.0.0", - "port": 3000, - "url": "http://localhost:3000/", - "legacyExplorer": false + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "types": { + "warnOnUnknownType": false + }, + "context": false, + "rest": { + "handleErrors": false, + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false + } } + \ No newline at end of file diff --git a/examples/server/datasources.json b/examples/server/datasources.json index a661c12..1f2696f 100644 --- a/examples/server/datasources.json +++ b/examples/server/datasources.json @@ -1,55 +1,146 @@ { "db": { "name": "db", - "connector": "memory", - "file": "db.json" + "connector": "memory" }, "elasticsearch-plain": { "name": "elasticsearch-plain", - "connector": "es", - "index": "shakespeare", - "hosts": [ - { - "host": "hosted.foundcluster.com", - "port": 9243 - } - ], - "apiVersion": "1.1", - "log": "trace", + "connector": "esv6", + "version": 6, + "index": "example-index", + "configuration": { + "node": "http://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000 + }, "defaultSize": 50, - "requestTimeout": 30000 + "indexSettings": { + "number_of_shards": 2, + "number_of_replicas": 1 + }, + "mappingType": "basedata", + "mappingProperties": { + "docType": { + "type": "keyword", + "index": true + }, + "id": { + "type": "keyword", + "index": true + }, + "seq": { + "type": "integer", + "index": true + }, + "name": { + "type": "keyword", + "index": true + }, + "email": { + "type": "keyword", + "index": true + }, + "birthday": { + "type": "date", + "index": true + }, + "role": { + "type": "keyword", + "index": true + }, + "order": { + "type": "integer", + "index": true + }, + "vip": { + "type": "boolean", + "index": true + }, + "objectId": { + "type": "keyword", + "index": true + }, + "ttl": { + "type": "integer", + "index": true + }, + "created": { + "type": "date", + "index": true + } + } }, "elasticsearch-ssl": { "name": "elasticsearch-ssl", - "connector": "es", - "index": "shakespeare", - "hosts": [ - { - "protocol": "https", - "host": "hosted.foundcluster.com", - "port": 9243, - "auth": "username:password" + "connector": "esv6", + "version": 7, + "index": "example-index", + "configuration": { + "node": "https://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000, + "auth": { + "username": "test", + "password": "test" + }, + "ssl": { + "rejectUnauthorized": true } - ], - "apiVersion": "1.1", - "log": "trace", + }, "defaultSize": 50, - "requestTimeout": 30000, - "ssl": { - "ca": "./../cacert.pem", - "rejectUnauthorized": true + "indexSettings": { + "number_of_shards": 2, + "number_of_replicas": 1 }, - "mappings": [ - { - "name": "UserModel", - "properties": { - "id": {"type": "string", "index" : "not_analyzed" }, - "realm": {"type": "string", "index" : "not_analyzed" }, - "username": {"type": "string", "index" : "not_analyzed" }, - "password": {"type": "string", "index" : "not_analyzed" }, - "email": {"type": "string", "index" : "not_analyzed" } - } + "mappingProperties": { + "docType": { + "type": "keyword", + "index": true + }, + "id": { + "type": "keyword", + "index": true + }, + "seq": { + "type": "integer", + "index": true + }, + "name": { + "type": "keyword", + "index": true + }, + "email": { + "type": "keyword", + "index": true + }, + "birthday": { + "type": "date", + "index": true + }, + "role": { + "type": "keyword", + "index": true + }, + "order": { + "type": "integer", + "index": true + }, + "vip": { + "type": "boolean", + "index": true + }, + "objectId": { + "type": "keyword", + "index": true + }, + "ttl": { + "type": "integer", + "index": true + }, + "created": { + "type": "date", + "index": true } - ] + } } } diff --git a/examples/server/datasources.sample-es-6.js b/examples/server/datasources.sample-es-6.js deleted file mode 100644 index d00cb18..0000000 --- a/examples/server/datasources.sample-es-6.js +++ /dev/null @@ -1,103 +0,0 @@ -module.exports = { - 'db': { - 'name': 'elasticsearch-6', - 'connector': 'esv6', - 'index': 'shakespeare', - 'hosts': [{ - 'host': 'localhost', - 'port': 9202 - }], - 'apiVersion': '6.0', - 'log': 'trace', - 'defaultSize': 50, - 'requestTimeout': 30000, - 'mappings': [], - "mappingType": "basedata", - "mappingProperties": { - "id": { - "type": "keyword", - "index": true - }, - "docType": { - "type": "keyword", - "index": true - }, - "name": { - "type": "text", - "index": true - }, - 'realm': { - 'type': 'keyword', - 'index': true - }, - 'username': { - 'type': 'keyword', - 'index': true - }, - "description": { - "type": "text", - "index": true - }, - "roleId": { - "type": "keyword", - "index": true - }, - "principalId": { - "type": "keyword", - "index": true - }, - "principalType": { - "type": "keyword", - "index": true - }, - "model": { - "type": "keyword", - "index": true - }, - "property": { - "type": "keyword", - "index": true - }, - "accessType": { - "type": "keyword", - "index": true - }, - "permission": { - "type": "keyword", - "index": true - }, - "userId": { - "type": "keyword", - "index": true - }, - "ttl": { - "type": "long", - "index": true - }, - "email": { - "type": "keyword", - "index": true - }, - "password": { - "type": "keyword", - "index": false - }, - "status": { - "type": "keyword", - "index": true - }, - "created": { - "type": "date", - "index": true - }, - "modified": { - "type": "date", - "index": true - }, - "lastUpdated": { - "type": "date", - "index": true - } - } - } -}; diff --git a/examples/server/datasources.sample-es-plain-1.js b/examples/server/datasources.sample-es-plain-1.js deleted file mode 100644 index defbb0c..0000000 --- a/examples/server/datasources.sample-es-plain-1.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - 'db': { - 'name': 'elasticsearch-plain', - 'connector': 'es', - 'index': 'shakespeare', - 'hosts': [ - { - 'host': 'localhost', - 'port': 9201 - } - ], - 'apiVersion': '1.1', - 'log': 'trace', - 'defaultSize': 50, - 'requestTimeout': 30000, - 'mappings': [ - { - 'name': 'UserModel', - 'properties': { - 'id': {'type': 'string', 'index' : 'not_analyzed' }, - 'realm': {'type': 'string', 'index' : 'not_analyzed' }, - 'username': {'type': 'string', 'index' : 'not_analyzed' }, - 'password': {'type': 'string', 'index' : 'not_analyzed' }, - 'email': {'type': 'string', 'index' : 'not_analyzed' } - } - } - ] - } -}; diff --git a/examples/server/datasources.sample-es-plain-2.js b/examples/server/datasources.sample-es-plain-2.js deleted file mode 100644 index ad7f800..0000000 --- a/examples/server/datasources.sample-es-plain-2.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - 'db': { - 'name': 'elasticsearch-plain', - 'connector': 'es', - 'index': 'shakespeare', - 'hosts': [ - { - 'host': 'localhost', - 'port': 9202 - } - ], - 'apiVersion': '2.3', - 'log': 'trace', - 'defaultSize': 50, - 'requestTimeout': 30000, - 'mappings': [ - { - 'name': 'UserModel', - 'properties': { - 'id': {'type': 'string', 'index' : 'not_analyzed' }, - 'realm': {'type': 'string', 'index' : 'not_analyzed' }, - 'username': {'type': 'string', 'index' : 'not_analyzed' }, - 'password': {'type': 'string', 'index' : 'not_analyzed' }, - 'email': {'type': 'string', 'index' : 'not_analyzed' } - } - } - ] - } -}; diff --git a/examples/server/datasources.sample-es-ssl-1.js b/examples/server/datasources.sample-es-ssl-1.js deleted file mode 100644 index 03ff164..0000000 --- a/examples/server/datasources.sample-es-ssl-1.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - 'db': { - 'name': 'elasticsearch-ssl', - 'connector': 'es', - 'index': 'shakespeare', - 'hosts': [ - { - 'protocol': 'https', - 'host': 'localhost', - 'port': 9243, // TODO: think about defaulting it to 9243+1=9244 for an example run against v1.x of ES - 'auth': 'username:password' - } - ], - 'apiVersion': '1.1', - 'log': 'trace', - 'defaultSize': 50, - 'requestTimeout': 30000, - 'ssl': { - 'ca': './../cacert.pem', - 'rejectUnauthorized': true - }, - 'mappings': [ - { - 'name': 'UserModel', - 'properties': { - 'id': {'type': 'string', 'index' : 'not_analyzed' }, - 'realm': {'type': 'string', 'index' : 'not_analyzed' }, - 'username': {'type': 'string', 'index' : 'not_analyzed' }, - 'password': {'type': 'string', 'index' : 'not_analyzed' }, - 'email': {'type': 'string', 'index' : 'not_analyzed' } - } - } - ] - } -}; diff --git a/examples/server/model-config.json b/examples/server/model-config.json index b37d399..71c816b 100644 --- a/examples/server/model-config.json +++ b/examples/server/model-config.json @@ -1,30 +1,38 @@ { - "_meta": { - "sources": [ - "../common/models", - "./models" - ] - }, - "User": { - "dataSource": "db" - }, - "AccessToken": { - "dataSource": "db", - "public": false - }, - "ACL": { - "dataSource": "db", - "public": false - }, - "RoleMapping": { - "dataSource": "db", - "public": false - }, - "Role": { - "dataSource": "db", - "public": false - }, - "UserModel": { - "dataSource": "db" - } + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "User": { + "dataSource": "db" + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "UserModel": { + "dataSource": "elasticsearch-plain" + } } diff --git a/lib/all.js b/lib/all.js new file mode 100644 index 0000000..5cf0d99 --- /dev/null +++ b/lib/all.js @@ -0,0 +1,32 @@ +const log = require('debug')('loopback:connector:elasticsearch'); + +function all(model, filter, done) { + const self = this; + log('ESConnector.prototype.all', 'model', model, 'filter', JSON.stringify(filter, null, 0)); + + const idName = self.idName(model); + log('ESConnector.prototype.all', 'idName', idName); + + self.db.search( + self.buildFilter(model, idName, filter, self.defaultSize) + ).then( + ({ body }) => { + const result = []; + body.hits.hits.forEach((item) => { + result.push(self.dataSourceToModel(model, item, idName)); + }); + log('ESConnector.prototype.all', 'model', model, 'result', JSON.stringify(result, null, 2)); + if (filter && filter.include) { + // eslint-disable-next-line no-underscore-dangle + self._models[model].model.include(result, filter.include, done); + } else { + done(null, result); + } + } + ).catch((error) => { + log('ESConnector.prototype.all', error.message); + return done(error, null); + }); +} + +module.exports.all = all; diff --git a/lib/automigrate.js b/lib/automigrate.js index 3219d20..c79b091 100644 --- a/lib/automigrate.js +++ b/lib/automigrate.js @@ -1,8 +1,5 @@ -'use strict'; - -var log = null; -var _ = null; -// var Promise = null; +let log = null; +const _ = require('lodash'); /** * `Connector._models` are all known at the time `automigrate` is called @@ -13,28 +10,29 @@ var _ = null; * @param models * @param cb */ -var automigrate = function (models, cb) { +const automigrate = (models, cb) => { log('ESConnector.prototype.automigrate', 'models:', models); - var self = this; + const self = this; if (self.db) { - if ((!cb) && ('function' === typeof models)) { + if (!cb && (typeof models === 'function')) { cb = models; models = undefined; } // First argument is a model name - if ('string' === typeof models) { + if (typeof models === 'string') { models = [models]; } log('ESConnector.prototype.automigrate', 'models', models); + // eslint-disable-next-line no-underscore-dangle models = models || Object.keys(self._models); - var indices = [], - mappingTypes = []; + let indices = []; + let mappingTypes = []; - _.forEach(models, function (model) { + _.forEach(models, (model) => { log('ESConnector.prototype.automigrate', 'model', model); - var defaults = self.addDefaults(model); + const defaults = self.addDefaults(model); mappingTypes.push(defaults.type); indices.push(defaults.index); }); @@ -43,8 +41,10 @@ var automigrate = function (models, cb) { mappingTypes = _.uniq(mappingTypes); log('ESConnector.prototype.automigrate', 'calling self.db.indices.delete() for indices:', indices); - cb(); // TODO: - /*self.db.indices.delete({index: indices, ignore: 404}) + cb(); + // TODO: + /* + self.db.indices.delete({index: indices, ignore: 404}) .then(function(response) { log('ESConnector.prototype.automigrate', 'finished deleting all indices', response); return Promise.map( @@ -62,16 +62,19 @@ var automigrate = function (models, cb) { .catch(function(err){ log('ESConnector.prototype.automigrate', 'failed', err); cb(err); - });*/ + }); + */ } else { log('ESConnector.prototype.automigrate', 'ERROR', 'Elasticsearch connector has not been initialized'); cb('Elasticsearch connector has not been initialized'); } }; -module.exports = function (dependencies) { - log = (dependencies) ? (dependencies.log || console.log) : console.log; /*eslint no-console: ['error', { allow: ['log'] }] */ - _ = (dependencies) ? (dependencies.lodash || require('lodash')) : require('lodash'); - // Promise = (dependencies) ? (dependencies.bluebird || require('bluebird')) : require('bluebird'); +module.exports = (dependencies) => { + log = dependencies + // eslint-disable-next-line no-console + ? (dependencies.log || console.log) + // eslint-disable-next-line no-console + : console.log; return automigrate; -}; \ No newline at end of file +}; diff --git a/lib/buildDeepNestedQueries.js b/lib/buildDeepNestedQueries.js new file mode 100644 index 0000000..8702b73 --- /dev/null +++ b/lib/buildDeepNestedQueries.js @@ -0,0 +1,383 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function buildDeepNestedQueries( + root, + idName, + where, + body, + queryPath, + model, + nestedFields +) { + const self = this; + _.forEach(where, (value, key) => { + let cond = value; + if (key === 'id' || key === idName) { + key = '_id'; + } + const splitKey = key.split('.'); + let isNestedKey = false; + let nestedSuperKey = null; + if (key.indexOf('.') > -1 && !!splitKey[0] && nestedFields.indexOf(splitKey[0]) > -1) { + isNestedKey = true; + // eslint-disable-next-line prefer-destructuring + nestedSuperKey = splitKey[0]; + } + + if (key === 'and' && Array.isArray(value)) { + let andPath; + if (root) { + andPath = queryPath.bool.must; + } else { + const andObject = { + bool: { + must: [] + } + }; + andPath = andObject.bool.must; + queryPath.push(andObject); + } + cond.forEach((c) => { + log('ESConnector.prototype.buildDeepNestedQueries', 'mapped', 'body', JSON.stringify(body, null, 0)); + self.buildDeepNestedQueries(false, idName, c, body, andPath, model, nestedFields); + }); + } else if (key === 'or' && Array.isArray(value)) { + let orPath; + if (root) { + orPath = queryPath.bool.should; + } else { + const orObject = { + bool: { + should: [] + } + }; + orPath = orObject.bool.should; + queryPath.push(orObject); + } + cond.forEach((c) => { + log('ESConnector.prototype.buildDeepNestedQueries', 'mapped', 'body', JSON.stringify(body, null, 0)); + self.buildDeepNestedQueries(false, idName, c, body, orPath, model, nestedFields); + }); + } else { + let spec = false; + let options = null; + if (cond && cond.constructor.name === 'Object') { // need to understand + options = cond.options; + // eslint-disable-next-line prefer-destructuring + spec = Object.keys(cond)[0]; + cond = cond[spec]; + } + log('ESConnector.prototype.buildNestedQueries', + 'spec', spec, 'key', key, 'cond', JSON.stringify(cond, null, 0), 'options', options); + if (spec) { + if (spec === 'gte' || spec === 'gt' || spec === 'lte' || spec === 'lt') { + let rangeQuery = { + range: {} + }; + const rangeQueryGuts = {}; + rangeQueryGuts[spec] = cond; + rangeQuery.range[key] = rangeQueryGuts; + + // Additional handling for nested Objects + if (isNestedKey) { + rangeQuery = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: rangeQuery + } + }; + } + + if (root) { + queryPath.bool.must.push(rangeQuery); + } else { + queryPath.push(rangeQuery); + } + } + + /** + * Logic for loopback `between` filter of where + * @example {where: {size: {between: [0,7]}}} + */ + if (spec === 'between') { + if (cond.length === 2 && (cond[0] <= cond[1])) { + let betweenArray = { + range: {} + }; + betweenArray.range[key] = { + gte: cond[0], + lte: cond[1] + }; + + // Additional handling for nested Objects + if (isNestedKey) { + betweenArray = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: betweenArray + } + }; + } + if (root) { + queryPath.bool.must.push(betweenArray); + } else { + queryPath.push(betweenArray); + } + } + } + /** + * Logic for loopback `inq`(include) filter of where + * @example {where: { property: { inq: [val1, val2, ...]}}} + */ + if (spec === 'inq') { + let inArray = { + terms: {} + }; + inArray.terms[key] = cond; + // Additional handling for nested Objects + if (isNestedKey) { + inArray = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: inArray + } + }; + } + if (root) { + queryPath.bool.must.push(inArray); + } else { + queryPath.push(inArray); + } + log('ESConnector.prototype.buildDeepNestedQueries', + 'body', body, + 'inArray', JSON.stringify(inArray, null, 0)); + } + + /** + * Logic for loopback `nin`(not include) filter of where + * @example {where: { property: { nin: [val1, val2, ...]}}} + */ + if (spec === 'nin') { + let notInArray = { + terms: {} + }; + notInArray.terms[key] = cond; + // Additional handling for nested Objects + if (isNestedKey) { + notInArray = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: { + must: [notInArray] + } + } + } + }; + } + if (root) { + queryPath.bool.must_not.push(notInArray); + } else { + queryPath.push({ + bool: { + must_not: [notInArray] + } + }); + } + } + + /** + * Logic for loopback `neq` (not equal) filter of where + * @example {where: {role: {neq: 'lead' }}} + */ + if (spec === 'neq') { + /** + * First - filter the documents where the given property exists + * @type {{exists: {field: *}}} + */ + // var missingFilter = {exists :{field : key}}; + /** + * Second - find the document where value not equals the given value + * @type {{term: {}}} + */ + let notEqual = { + term: {} + }; + notEqual.term[key] = cond; + /** + * Apply the given filter in the main filter(body) and on given path + */ + // Additional handling for nested Objects + if (isNestedKey) { + notEqual = { + match: {} + }; + notEqual.match[key] = cond; + notEqual = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: { + must: [notEqual] + } + } + } + }; + } + if (root) { + queryPath.bool.must_not.push(notEqual); + } else { + queryPath.push({ + bool: { + must_not: [notEqual] + } + }); + } + + + // body.query.bool.must.push(missingFilter); + } + // TODO: near - For geolocations, return the closest points, + // ...sorted in order of distance. Use with limit to return the n closest points. + // TODO: like, nlike + // TODO: ilike, inlike + if (spec === 'like') { + let likeQuery = { + regexp: {} + }; + likeQuery.regexp[key] = cond; + + // Additional handling for nested Objects + if (isNestedKey) { + likeQuery = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: { + must: [likeQuery] + } + } + } + }; + } + if (root) { + queryPath.bool.must.push(likeQuery); + } else { + queryPath.push(likeQuery); + } + } + + if (spec === 'nlike') { + let nlikeQuery = { + regexp: {} + }; + nlikeQuery.regexp[key] = cond; + + // Additional handling for nested Objects + if (isNestedKey) { + nlikeQuery = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: { + must_not: [nlikeQuery] + } + } + } + }; + } + if (root) { + queryPath.bool.must_not.push(nlikeQuery); + } else { + queryPath.push({ + bool: { + must_not: [nlikeQuery] + } + }); + } + } + // TODO: regex + + // geo_shape || geo_distance || geo_bounding_box + if (spec === 'geo_shape' || spec === 'geo_distance' || spec === 'geo_bounding_box') { + let geoQuery = { + filter: {} + }; + geoQuery.filter[spec] = cond; + + if (isNestedKey) { + geoQuery = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: geoQuery + } + } + }; + if (root) { + queryPath.bool.must.push(geoQuery); + } else { + queryPath.push(geoQuery); + } + } else if (root) { + queryPath.bool.filter = geoQuery; + } else { + queryPath.push({ + bool: geoQuery + }); + } + } + } else { + let nestedQuery = {}; + if (typeof value === 'string') { + value = value.trim(); + if (value.indexOf(' ') > -1) { + nestedQuery.match_phrase = {}; + nestedQuery.match_phrase[key] = value; + } else { + nestedQuery.match = {}; + nestedQuery.match[key] = value; + } + } else { + nestedQuery.match = {}; + nestedQuery.match[key] = value; + } + // Additional handling for nested Objects + if (isNestedKey) { + nestedQuery = { + nested: { + path: nestedSuperKey, + score_mode: 'max', + query: { + bool: { + must: [nestedQuery] + } + } + } + }; + } + + if (root) { + queryPath.bool.must.push(nestedQuery); + } else { + queryPath.push(nestedQuery); + } + + log('ESConnector.prototype.buildDeepNestedQueries', + 'body', body, + 'nestedQuery', JSON.stringify(nestedQuery, null, 0)); + } + } + }); +} + +module.exports.buildDeepNestedQueries = buildDeepNestedQueries; diff --git a/lib/buildFilter.js b/lib/buildFilter.js new file mode 100644 index 0000000..3e05a25 --- /dev/null +++ b/lib/buildFilter.js @@ -0,0 +1,114 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function buildFilter(modelName, idName, criteria = {}, size = null, offset = null) { + const self = this; + log('ESConnector.prototype.buildFilter', 'model', modelName, 'idName', idName, + 'criteria', JSON.stringify(criteria, null, 0)); + + if (idName === undefined || idName === null) { + throw new Error('idName not set!'); + } + + const filter = this.addDefaults(modelName, 'buildFilter'); + filter.body = {}; + + if (size !== undefined && size !== null) { + filter.size = size; + } + if (offset !== undefined && offset !== null) { + filter.from = offset; + } + + if (criteria) { + // `criteria` is set by app-devs, therefore, it overrides any connector level arguments + if (criteria.limit !== undefined && criteria.limit !== null) { + filter.size = criteria.limit; + } + if (criteria.skip !== undefined && criteria.skip !== null) { + filter.from = criteria.skip; + } else if (criteria.offset !== undefined + && criteria.offset !== null) { // use offset as an alias for skip + filter.from = criteria.offset; + } + if (criteria.fields) { + // { fields: {propertyName: , propertyName: , ... } } + // filter.body.fields = self.buildOrder(model, idName, criteria.fields); + // TODO: make it so + // http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-request-source-filtering.html + // http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-request-fields.html + /* POST /shakespeare/User/_search + { + '_source': { + 'include': ['seq'], + 'exclude': ['seq'] + } + } */ + + /* @raymondfeng and @bajtos - I'm observing something super strange, + i haven't implemented the FIELDS filter for elasticsearch connector + but the test which should fail until I implement such a feature ... is actually passing! + ... did someone at some point of time implement an in-memory filter for FIELDS + in the underlying loopback-connector implementation? */ + + // Elasticsearch _source filtering code + /* if (Array.isArray(criteria.fields) || typeof criteria.fields === 'string') { + filter.body._source = criteria.fields; + } else if (typeof criteria.fields === 'object' && Object.keys(criteria.fields).length > 0) { + filter.body._source.includes = _.map(_.pickBy(criteria.fields, function(v, k) { + return v === true; + }), function(v, k) { return k; }); + filter.body._source.excludes = _.map(_.pickBy(criteria.fields, function(v, k) { + return v === false; + }), function(v, k) { return k; }); + } */ + } + if (criteria.order) { + log('ESConnector.prototype.buildFilter', 'will delegate sorting to buildOrder()'); + filter.body.sort = self.buildOrder(modelName, idName, criteria.order); + } else { // TODO: expensive~ish and no clear guidelines so turn it off? + // var idNames = this.idNames(model); // TODO: support for compound ids? + // eslint-disable-next-line no-underscore-dangle + const modelProperties = this._models[modelName].properties; + if (idName === 'id' && modelProperties.id.generated) { + // filter.body.sort = ['_id']; // requires mapping to contain: ... + // ...'_id' : {'index' : 'not_analyzed','store' : true} + log('ESConnector.prototype.buildFilter', 'will sort on _id by default when IDs are meant to be auto-generated by elasticsearch'); + filter.body.sort = ['_id']; + } else { + log('ESConnector.prototype.buildFilter', 'will sort on loopback specified IDs'); + filter.body.sort = [idName]; // default sort should be based on fields marked as id + } + } + if (criteria.where) { + filter.body.query = self.buildWhere(modelName, idName, criteria.where).query; + } else if (_.keys(criteria).length === 0) { + filter.body = { + query: { + bool: { + must: { + match_all: {} + }, + filter: [{ + term: { + docType: modelName + } + }] + } + } + }; + } else if (!Object.prototype.hasOwnProperty.call(criteria, 'where')) { + // For queries without 'where' filter, add docType filter + filter.body.query = self.buildWhere(modelName, idName, criteria.where || {}).query; + } + // TODO: native query support must be secured to restrict data access to request model + /* if (criteria.native) { + filter.body = criteria.native; // assume that the developer has provided ES compatible DSL + } */ + } + + log('ESConnector.prototype.buildFilter', 'constructed', JSON.stringify(filter, null, 0)); + return filter; +} + +module.exports.buildFilter = buildFilter; diff --git a/lib/buildNestedQueries.js b/lib/buildNestedQueries.js new file mode 100644 index 0000000..881607b --- /dev/null +++ b/lib/buildNestedQueries.js @@ -0,0 +1,68 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function buildNestedQueries(body, model, idName, where, nestedFields) { + /** + * Return an empty match all object if no property is set in where filter + * @example {where: {}} + */ + const self = this; + if (_.keys(where).length === 0) { + body = { + query: { + bool: { + must: { + match_all: {} + }, + filter: [{ + term: { + docType: model + } + }] + } + } + }; + log('ESConnector.prototype.buildNestedQueries', '\nbody', JSON.stringify(body, null, 0)); + return body; + } + const rootPath = body.query; + self.buildDeepNestedQueries(true, idName, where, + body, rootPath, model, nestedFields); + const docTypeQuery = _.find(rootPath.bool.filter, (v) => v.term && v.term.docType); + let addedDocTypeToRootPath = false; + if (typeof docTypeQuery !== 'undefined') { + addedDocTypeToRootPath = true; + docTypeQuery.term.docType = model; + } else { + addedDocTypeToRootPath = true; + rootPath.bool.filter.push({ + term: { + docType: model + } + }); + } + + if (addedDocTypeToRootPath) { + if (!!rootPath && rootPath.bool && rootPath.bool.should && rootPath.bool.should.length !== 0) { + rootPath.bool.must.push({ + bool: { + should: rootPath.bool.should + } + }); + rootPath.bool.should = []; + } + + if (!!rootPath && rootPath.bool + && rootPath.bool.must_not && rootPath.bool.must_not.length !== 0) { + rootPath.bool.must.push({ + bool: { + must_not: rootPath.bool.must_not + } + }); + rootPath.bool.must_not = []; + } + } + return true; +} + +module.exports.buildNestedQueries = buildNestedQueries; diff --git a/lib/buildOrder.js b/lib/buildOrder.js new file mode 100644 index 0000000..4d0c558 --- /dev/null +++ b/lib/buildOrder.js @@ -0,0 +1,29 @@ +function buildOrder(model, idName, order) { + const sort = []; + + let keys = order; + if (typeof keys === 'string') { + keys = keys.split(','); + } + for (let index = 0, len = keys.length; index < len; index += 1) { + const m = keys[index].match(/\s+(A|DE)SC$/); + let key = keys[index]; + key = key.replace(/\s+(A|DE)SC$/, '').trim(); + if (key === 'id' || key === idName) { + key = '_id'; + } + if (m && m[1] === 'DE') { + // sort[key] = -1; + const temp = {}; + temp[key] = 'desc'; + sort.push(temp); + } else { + // sort[key] = 1; + sort.push(key); + } + } + + return sort; +} + +module.exports.buildOrder = buildOrder; diff --git a/lib/buildWhere.js b/lib/buildWhere.js new file mode 100644 index 0000000..16609e3 --- /dev/null +++ b/lib/buildWhere.js @@ -0,0 +1,60 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function buildWhere(model, idName, where) { + const self = this; + + let nestedFields = _.map(self.settings.mappingProperties, (val, key) => (val.type === 'nested' ? key : null)); + nestedFields = _.filter(nestedFields, (v) => v); + log('ESConnector.prototype.buildWhere', 'model', model, 'idName', idName, 'where', JSON.stringify(where, null, 0)); + + const body = { + query: { + bool: { + must: [], + should: [], + filter: [], + must_not: [] + } + } + }; + + self.buildNestedQueries(body, model, idName, where, nestedFields); + if (body && body.query && body.query.bool + && body.query.bool.must && body.query.bool.must.length === 0) { + delete body.query.bool.must; + } + if (body && body.query && body.query.bool + && body.query.bool.filter && body.query.bool.filter.length === 0) { + delete body.query.bool.filter; + } + if (body && body.query && body.query.bool + && body.query.bool.should && body.query.bool.should.length === 0) { + delete body.query.bool.should; + } + if (body && body.query && body.query.bool + && body.query.bool.must_not && body.query.bool.must_not.length === 0) { + delete body.query.bool.must_not; + } + if (body && body.query && body.query.bool && _.isEmpty(body.query.bool)) { + delete body.query.bool; + } + + if (body && body.query && _.isEmpty(body.query)) { + body.query = { + bool: { + must: { + match_all: {} + }, + filter: [{ + term: { + docType: model + } + }] + } + }; + } + return body; +} + +module.exports.buildWhere = buildWhere; diff --git a/lib/count.js b/lib/count.js new file mode 100644 index 0000000..0f1e082 --- /dev/null +++ b/lib/count.js @@ -0,0 +1,24 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function count(modelName, done, where) { + const self = this; + log('ESConnector.prototype.count', 'model', modelName, 'where', where); + + const idName = self.idName(modelName); + const query = { + query: self.buildWhere(modelName, idName, where).query + }; + + const defaults = self.addDefaults(modelName, 'count'); + self.db.count(_.defaults({ + body: query + }, defaults)).then(({ body }) => { + done(null, body.count); + }).catch((error) => { + log('ESConnector.prototype.count', error.message); + done(error, null); + }); +} + +module.exports.count = count; diff --git a/lib/create.js b/lib/create.js new file mode 100644 index 0000000..43f4644 --- /dev/null +++ b/lib/create.js @@ -0,0 +1,45 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function create(model, data, done) { + const self = this; + if (self.debug) { + log('ESConnector.prototype.create', model, data); + } + + const idValue = self.getIdValue(model, data); + const idName = self.idName(model); + log('ESConnector.prototype.create', 'idName', idName, 'idValue', idValue); + /* TODO: If model has custom id with generated false and + if Id field is not prepopulated then create should fail. + */ + /* TODO: If model Id is not string and generated is true then + create should fail because the auto generated es id is of type string and we cannot change it. + */ + const document = self.addDefaults(model, 'create'); + document[self.idField] = self.getDocumentId(idValue); + document.body = {}; + _.assign(document.body, data); + log('ESConnector.prototype.create', 'document', document); + let method = 'create'; + if (!document[self.idField]) { + method = 'index'; // if there is no/empty id field, we must use the index method to create it (API 5.0) + } + document.body.docType = model; + self.db[method]( + document + ).then( + ({ body }) => { + log('ESConnector.prototype.create', 'response', body); + // eslint-disable-next-line no-underscore-dangle + log('ESConnector.prototype.create', 'will invoke callback with id:', body._id); + // eslint-disable-next-line no-underscore-dangle + done(null, body._id); // the connector framework expects the id as a return value + } + ).catch((error) => { + log('ESConnector.prototype.create', error.message); + return done(error, null); + }); +} + +module.exports.create = create; diff --git a/lib/destroy.js b/lib/destroy.js new file mode 100644 index 0000000..9137924 --- /dev/null +++ b/lib/destroy.js @@ -0,0 +1,24 @@ +const log = require('debug')('loopback:connector:elasticsearch'); + +function destroy(modelName, id, done) { + const self = this; + if (self.debug) { + log('ESConnector.prototype.destroy', 'model', modelName, 'id', id); + } + + const filter = self.addDefaults(modelName, 'destroy'); + filter[self.idField] = self.getDocumentId(id); + if (!filter[self.idField]) { + throw new Error('Document id not setted!'); + } + self.db.delete( + filter + ).then(({ body }) => { + done(null, body); + }).catch((error) => { + log('ESConnector.prototype.destroy', error.message); + done(error, null); + }); +} + +module.exports.destroy = destroy; diff --git a/lib/destroyAll.js b/lib/destroyAll.js new file mode 100644 index 0000000..d7860a1 --- /dev/null +++ b/lib/destroyAll.js @@ -0,0 +1,33 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function destroyAll(modelName, whereClause, cb) { + const self = this; + + if ((!cb) && _.isFunction(whereClause)) { + cb = whereClause; + whereClause = {}; + } + log('ESConnector.prototype.destroyAll', 'modelName', modelName, 'whereClause', JSON.stringify(whereClause, null, 0)); + + const idName = self.idName(modelName); + const filter = { + query: self.buildWhere(modelName, idName, whereClause).query + }; + + const defaults = self.addDefaults(modelName, 'destroyAll'); + const options = _.defaults({ + body: filter + }, defaults); + log('ESConnector.prototype.destroyAll', 'options:', JSON.stringify(options, null, 2)); + self.db.deleteByQuery(options) + .then(({ body }) => { + cb(null, body); + }) + .catch((error) => { + log('ESConnector.prototype.destroyAll', error.message); + cb(error); + }); +} + +module.exports.destroyAll = destroyAll; diff --git a/lib/esConnector.js b/lib/esConnector.js index a67df4d..107ee44 100755 --- a/lib/esConnector.js +++ b/lib/esConnector.js @@ -1,29 +1,90 @@ -'use strict'; - -var util = require('util'); -var fs = require('fs'); -var path = require('path'); -var _ = require('lodash'); -var Promise = require('bluebird'); - -var log = require('debug')('loopback:connector:elasticsearch'); - -var elasticsearch = require('elasticsearch'); -var Connector = require('loopback-connector').Connector; +/* eslint-disable func-names */ +const util = require('util'); +const fs = require('fs'); +const _ = require('lodash'); +const R = require('ramda'); + +const log = require('debug')('loopback:connector:elasticsearch'); + +const SupportedVersions = [6, 7]; // Supported elasticsearch versions +// 'Client' will be assigned either Client6 or Client7 from below definitions based on version +let Client = null; +const { Client: Client6 } = require('es6'); +const { Client: Client7 } = require('es7'); +const { Connector } = require('loopback-connector'); +const automigrate = require('./automigrate.js')({ + log +}); +const { setupIndex } = require('./setupIndex'); +const { all } = require('./all'); +const { buildDeepNestedQueries } = require('./buildDeepNestedQueries'); +const { buildNestedQueries } = require('./buildNestedQueries'); +const { buildFilter } = require('./buildFilter'); +const { buildOrder } = require('./buildOrder'); +const { buildWhere } = require('./buildWhere'); +const { count } = require('./count'); +const { create } = require('./create'); +const { destroy } = require('./destroy'); +const { destroyAll } = require('./destroyAll'); +const { exists } = require('./exists'); +const { find } = require('./find'); +const { replaceById } = require('./replaceById'); +const { replaceOrCreate } = require('./replaceOrCreate'); +const { save } = require('./save'); +const { updateAll } = require('./updateAll'); +const { updateAttributes } = require('./updateAttributes'); +const { updateOrCreate } = require('./updateOrCreate'); -/*eslint no-console: ['error', { allow: ['trace','log'] }] */ +/** + * Connector constructor + * @param {object} datasource settings + * @param {object} dataSource + * @constructor + */ +class ESConnector { + constructor(settings, dataSource) { + Connector.call(this, 'elasticsearch', settings); + const defaultRefreshIndexAPIs = [ + 'create', + 'save', + 'destroy', + 'destroyAll', + 'updateAttributes', + 'updateOrCreate', + 'updateAll', + 'replaceOrCreate', + 'replaceById' + ]; + this.configuration = settings.configuration || {}; + this.version = settings.version; + this.mappingType = settings.version < 7 ? settings.mappingType || 'basedata' : null; + this.index = settings.index; + this.indexSettings = settings.indexSettings || {}; + this.defaultSize = (settings.defaultSize || 50); + this.idField = 'id'; + this.refreshOn = defaultRefreshIndexAPIs; + + this.debug = settings.debug || log.enabled; + if (this.debug) { + log('Settings: %j', settings); + } + + this.dataSource = dataSource; + } +} /** * Initialize connector with datasource, configure settings and return * @param {object} dataSource * @param {function} done callback */ -module.exports.initialize = function (dataSource, callback) { - if (!elasticsearch) { +module.exports.initialize = (dataSource, callback) => { + if (!R.has('settings', dataSource) || !R.has('version', dataSource.settings) || SupportedVersions.indexOf(dataSource.settings.version) === -1) { return; } - - var settings = dataSource.settings || {}; + const { version } = dataSource.settings; + Client = version === 6 ? Client6 : Client7; + const settings = dataSource.settings || {}; dataSource.connector = new ESConnector(settings, dataSource); @@ -32,31 +93,6 @@ module.exports.initialize = function (dataSource, callback) { } }; -/** - * Connector constructor - * @param {object} datasource settings - * @param {object} dataSource - * @constructor - */ -var ESConnector = function (settings, dataSource) { - Connector.call(this, 'elasticsearch', settings); - - this.searchIndex = settings.index || 'shakespeare'; - this.searchIndexSettings = settings.settings || {}; - this.searchType = settings.mappingType || 'basedata'; - this.defaultSize = (settings.defaultSize || 50); - this.idField = 'id'; - this.apiVersion = (settings.apiVersion || '6.0'); - this.refreshOn = (settings.refreshOn || ['create', 'save', 'destroy', 'destroyAll', 'updateAttributes', 'updateOrCreate', 'updateAll', 'replaceOrCreate', 'replaceById']); - - this.debug = settings.debug || log.enabled; - if (this.debug) { - log('Settings: %j', settings); - } - - this.dataSource = dataSource; -}; - /** * Inherit the prototype methods */ @@ -66,38 +102,19 @@ util.inherits(ESConnector, Connector); * Generate a client configuration object based on settings. */ ESConnector.prototype.getClientConfig = function () { - // http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html - var config = { - hosts: this.settings.hosts || { - host: '127.0.0.1', - port: 9200 - }, - requestTimeout: this.settings.requestTimeout, - apiVersion: this.settings.apiVersion, - log: this.settings.log || 'error', - suggestCompression: true - }; - - if (this.settings.amazonES) { - config.connectionClass = require('http-aws-es'); - config.amazonES = this.settings.amazonES || { - region: 'us-east-1', - accessKey: 'AKID', - secretKey: 'secret' - } - } - - if (this.settings.ssl) { - config.ssl = { - ca: (this.settings.ssl.ca) ? fs.readFileSync(path.join(__dirname, this.settings.ssl.ca)) : fs.readFileSync(path.join(__dirname, '..', 'cacert.pem')), - rejectUnauthorized: this.settings.ssl.rejectUnauthorized || true - }; + // https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-configuration.html + const self = this; + const config = self.settings.configuration; + + if (config.ssl) { + const fskeys = ['ca', 'cert', 'key']; + R.forEach((key) => { + if (R.has(key, config.ssl)) { + config.ssl[key] = fs.readFileSync(config.ssl[key]); + } + }, fskeys); } - // Note: http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/configuration.html - // Due to the complex nature of the configuration, the config object you pass in will be modified - // and can only be used to create one Client instance. - // Related Github issue: https://github.com/elasticsearch/elasticsearch-js/issues/33 - // Luckily getClientConfig() pretty much clones settings so we shouldn't have to worry about it. + // Note: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-configuration.html return config; }; @@ -113,104 +130,35 @@ ESConnector.prototype.connect = function (callback) { // TODO: throw error if callback isn't provided? // what are the corner-cases when the loopback framework does not provide callback // and we need to be able to live with that? - var self = this; + const self = this; if (self.db) { - process.nextTick(function () { - callback && callback(null, self.db); + process.nextTick(() => { + callback(null, self.db); }); } else { - self.db = new elasticsearch.Client(self.getClientConfig()); - self.db.ping({ - requestTimeout: self.settings.requestTimeout - }, function (error) { - if (error) { - console.log('ESConnector.prototype.connect', 'ping', 'failed', error); - log('ESConnector.prototype.connect', 'ping', 'failed', error); - } + self.db = new Client(self.getClientConfig()); + self.ping(() => { + // }); if (self.settings.mappingProperties) { - self.setupMappings() - .then(function () { - log('ESConnector.prototype.connect', 'setupMappings', 'finished'); - callback && callback(null, self.db); + self.setupIndex() + .then(() => { + log('ESConnector.prototype.connect', 'setupIndex', 'finished'); + callback(null, self.db); }) - .catch(function (err) { - log('ESConnector.prototype.connect', 'setupMappings', 'failed', err); - callback && callback(null, self.db); + .catch((err) => { + log('ESConnector.prototype.connect', 'setupIndex', 'failed', err); + callback(null, self.db); }); } else { - process.nextTick(function () { - callback && callback(null, self.db); + process.nextTick(() => { + callback(null, self.db); }); } } }; -/** - * Delete a mapping (type definition) along with its data. - * - * @param modelNames - * @param callback - */ -ESConnector.prototype.removeMappings = function (modelNames, callback) { - var self = this; - var db = self.db; - var settings = self.settings; - if (_.isFunction(modelNames)) { - callback = modelNames; - modelNames = _.map(settings.mappings, 'name'); - } - log('ESConnector.prototype.removeMappings', 'modelNames', modelNames); - - var mappingsToRemove = _.filter(settings.mappings, function (mapping) { - return !modelNames || _.includes(modelNames, mapping.name); - }); - log('ESConnector.prototype.removeMappings', 'mappingsToRemove', _.map(mappingsToRemove, 'name')); - - Promise.map(mappingsToRemove, function (mapping) { - var defaults = self.addDefaults(mapping.name, 'removeMappings'); - log('ESConnector.prototype.removeMappings', 'calling self.db.indices.existsType()'); - return db.indices.existsType(defaults).then(function (exists) { - if (!exists) return Promise.resolve(); - log('ESConnector.prototype.removeMappings', 'calling self.db.indices.deleteMapping()'); - return db.indices.deleteMapping(defaults).then(function (body) { - log('ESConnector.prototype.removeMappings', mapping.name, body); - return Promise.resolve(); - }, function (err) { - log('ESConnector.prototype.removeMappings', err.message); - return Promise.reject(err); - }); - }, function (err) { - log('ESConnector.prototype.removeMappings', err.message); - return Promise.reject(err); - }); - }, { - concurrency: 1 - }).then(function () { - log('ESConnector.prototype.removeMappings', 'finished'); - callback(null, self.db); // TODO: what does the connector framework want back as arguments here? - }).catch(function (err) { - log('ESConnector.prototype.removeMappings', 'failed'); - callback(err); - }); -}; - -ESConnector.prototype.setupMappings = require('./setupMappings.js')({ - log: log, - lodash: _, - bluebird: Promise -}); - -ESConnector.prototype.setupMapping = require('./setupMapping.js')({ - log: log, - lodash: _, - bluebird: Promise -}); - -ESConnector.prototype.setupIndex = require('./setupIndex.js')({ - log: log, - bluebird: Promise -}); +ESConnector.prototype.setupIndex = setupIndex; /** @@ -218,17 +166,13 @@ ESConnector.prototype.setupIndex = require('./setupIndex.js')({ * @returns {String} with ping result */ ESConnector.prototype.ping = function (cb) { - var self = this; - self.db.ping({ - requestTimeout: self.settings.requestTimeout - }, function (error) { - if (error) { - log('Could not ping ES.'); - cb(error); - } else { - log('Pinged ES successfully.'); - cb(); - } + const self = this; + self.db.ping().then(() => { + log('Pinged ES successfully.'); + cb(); + }).catch((error) => { + log('Could not ping ES.'); + cb(error); }); }; @@ -249,19 +193,17 @@ ESConnector.prototype.getTypes = function () { ESConnector.prototype.getValueFromProperty = function (property, value) { if (property.type instanceof Array) { if (!value || (value.length === 0)) { - return new Array(); - } else { - return value; + return []; } - } else if (property.type === String) { + return value; + } if (property.type === String) { return value.toString(); - } else if (property.type === Number) { + } if (property.type === Number) { return Number(value); - } else if (property.type === Date) { + } if (property.type === Date) { return new Date(value); - } else { - return value; } + return value; }; /** @@ -271,25 +213,32 @@ ESConnector.prototype.getValueFromProperty = function (property, value) { * @returns {object} modeled document */ ESConnector.prototype.matchDataToModel = function (modelName, data, esId, idName) { - //log('ESConnector.prototype.matchDataToModel', 'modelName', modelName, 'data', JSON.stringify(data,null,0)); - var self = this; + /* + log('ESConnector.prototype.matchDataToModel', 'modelName', + modelName, 'data', JSON.stringify(data,null,0)); + */ + const self = this; if (!data) { return null; } try { - var document = {}; + const document = {}; - var properties = this._models[modelName].properties; + // eslint-disable-next-line no-underscore-dangle + const { properties } = this._models[modelName]; _.assign(document, data); // it can't be this easy, can it? document[idName] = esId; - for (var propertyName in properties) { - var propertyValue = data[propertyName]; + Object.keys(properties).forEach((propertyName) => { + const propertyValue = data[propertyName]; // log('ESConnector.prototype.matchDataToModel', propertyName, propertyValue); if (propertyValue !== undefined && propertyValue !== null) { - document[propertyName] = self.getValueFromProperty(properties[propertyName], propertyValue); + document[propertyName] = self.getValueFromProperty( + properties[propertyName], + propertyValue + ); } - } + }); log('ESConnector.prototype.matchDataToModel', 'document', JSON.stringify(document, null, 0)); return document; } catch (err) { @@ -307,7 +256,8 @@ ESConnector.prototype.matchDataToModel = function (modelName, data, esId, idName ESConnector.prototype.dataSourceToModel = function (modelName, data, idName) { log('ESConnector.prototype.dataSourceToModel', 'modelName', modelName, 'data', JSON.stringify(data, null, 0)); - //return data._source; // TODO: super-simplify? + // return data._source; // TODO: super-simplify? + // eslint-disable-next-line no-underscore-dangle return this.matchDataToModel(modelName, data._source, data._id, idName); }; @@ -319,41 +269,22 @@ ESConnector.prototype.dataSourceToModel = function (modelName, data, idName) { * @returns {object} Filter with index and type */ ESConnector.prototype.addDefaults = function (modelName, functionName) { - var self = this; + const self = this; log('ESConnector.prototype.addDefaults', 'modelName:', modelName); + const filter = { + index: self.settings.index + }; - //TODO: fetch index and type from `self.settings.mappings` too - var indexFromDatasource, typeFromDatasource; - /*var mappingFromDatasource = _.find(self.settings.mappings, - function (mapping) { - return mapping.name === modelName; - }); - if (mappingFromDatasource) { - indexFromDatasource = mappingFromDatasource.index; - typeFromDatasource = mappingFromDatasource.type; - }*/ - indexFromDatasource = self.settings.index; - typeFromDatasource = self.settings.mappingType; - var filter = {}; - if (this.searchIndex) { - filter.index = indexFromDatasource || this.searchIndex; + if (self.settings.version < 7) { + filter.type = self.settings.mappingType; } - filter.type = typeFromDatasource || this.searchType || modelName; - // When changing data, wait until the change has been indexed so it is instantly available for search - if (this.refreshOn.indexOf(functionName) != -1) { + // When changing data, wait until the change has been indexed... + // ...so it is instantly available for search + if (this.refreshOn.indexOf(functionName) !== -1) { filter.refresh = true; } - var modelClass = this._models[modelName]; - if (modelClass && _.isObject(modelClass.settings.elasticsearch) && _.isObject(modelClass.settings.elasticsearch.settings)) { - _.extend(filter, modelClass.settings.elasticsearch.settings); - } - - if (functionName && modelClass && _.isObject(modelClass.settings.elasticsearch) && _.isObject(modelClass.settings.elasticsearch[functionName])) { - _.extend(filter, modelClass.settings.elasticsearch[functionName]); - } - log('ESConnector.prototype.addDefaults', 'filter:', filter); return filter; }; @@ -369,120 +300,14 @@ ESConnector.prototype.addDefaults = function (modelName, functionName) { * @param {number} offset to return, if null then skip * @returns {object} filter */ -ESConnector.prototype.buildFilter = function (modelName, idName, criteria, size, offset) { - var self = this; - log('ESConnector.prototype.buildFilter', 'model', modelName, 'idName', idName, - 'criteria', JSON.stringify(criteria, null, 0)); - - if (idName === undefined || idName === null) { - throw new Error('idName not set!'); - } - - var filter = this.addDefaults(modelName, 'buildFilter'); - filter.body = {}; - - if (size !== undefined && size !== null) { - filter.size = size; - } - if (offset !== undefined && offset !== null) { - filter.from = offset; - } - - if (criteria) { - // `criteria` is set by app-devs, therefore, it overrides any connector level arguments - if (criteria.limit !== undefined && criteria.limit !== null) { - filter.size = criteria.limit; - } - if (criteria.skip !== undefined && criteria.skip !== null) { - filter.from = criteria.skip; - } else if (criteria.offset !== undefined && criteria.offset !== null) { // use offset as an alias for skip - filter.from = criteria.offset; - } - if (criteria.fields) { - // { fields: {propertyName: , propertyName: , ... } } - //filter.body.fields = self.buildOrder(model, idName, criteria.fields); - // TODO: make it so - // http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-request-source-filtering.html - // http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-request-fields.html - /*POST /shakespeare/User/_search - { - '_source': { - 'include': ['seq'], - 'exclude': ['seq'] - } - }*/ - - /* @raymondfeng and @bajtos - I'm observing something super strange, - i haven't implemented the FIELDS filter for elasticsearch connector - but the test which should fail until I implement such a feature ... is actually passing! - ... did someone at some point of time implement an in-memory filter for FIELDS - in the underlying loopback-connector implementation? */ - - // Elasticsearch _source filtering code - /*if (Array.isArray(criteria.fields) || typeof criteria.fields === 'string') { - filter.body._source = criteria.fields; - } else if (typeof criteria.fields === 'object' && Object.keys(criteria.fields).length > 0) { - filter.body._source.includes = _.map(_.pickBy(criteria.fields, function(v, k) { - return v === true; - }), function(v, k) { return k; }); - filter.body._source.excludes = _.map(_.pickBy(criteria.fields, function(v, k) { - return v === false; - }), function(v, k) { return k; }); - }*/ - } - if (criteria.order) { - log('ESConnector.prototype.buildFilter', 'will delegate sorting to buildOrder()'); - filter.body.sort = self.buildOrder(modelName, idName, criteria.order); - } else { // TODO: expensive~ish and no clear guidelines so turn it off? - //var idNames = this.idNames(model); // TODO: support for compound ids? - var modelProperties = this._models[modelName].properties; - if (idName === 'id' && modelProperties.id.generated) { - //filter.body.sort = ['_id']; // requires mapping to contain: '_id' : {'index' : 'not_analyzed','store' : true} - log('ESConnector.prototype.buildFilter', 'will sort on _id by default when IDs are meant to be auto-generated by elasticsearch'); - filter.body.sort = ['_id']; - } else { - log('ESConnector.prototype.buildFilter', 'will sort on loopback specified IDs'); - filter.body.sort = [idName]; // default sort should be based on fields marked as id - } - } - if (criteria.where) { - filter.body.query = self.buildWhere(modelName, idName, criteria.where).query; - } else if (criteria.suggests) { // TODO: remove HACK!!! - filter.body = { - suggest: criteria.suggests - }; // assume that the developer has provided ES compatible DSL - } else if (criteria.native) { - filter.body = criteria.native; // assume that the developer has provided ES compatible DSL - } else if (_.keys(criteria).length === 0) { - filter.body = { - 'query': { - 'bool': { - 'must': { - 'match_all': {} - }, - 'filter': [{ - 'term': { - 'docType': modelName - } - }] - } - } - }; - } - // For queries without 'where' filter, add docType filter - else if (!criteria.hasOwnProperty('where')) { - filter.body.query = self.buildWhere(modelName, idName, criteria.where || {}).query; - } - } - - log('ESConnector.prototype.buildFilter', 'constructed', JSON.stringify(filter, null, 0)); - return filter; -}; +ESConnector.prototype.buildFilter = buildFilter; /** * 1. Words of wisdom from @doublemarked: - * > When writing a query without an order specified, the author should not assume any reliable order. - * > So if we’re not assuming any order, there is not a compelling reason to potentially slow down + * > When writing a query without an order specified, + the author should not assume any reliable order. + * > So if we’re not assuming any order, + there is not a compelling reason to potentially slow down * > the query by enforcing a default order. * 2. Yet, most connector implementations do enforce a default order ... what to do? * @@ -491,522 +316,13 @@ ESConnector.prototype.buildFilter = function (modelName, idName, criteria, size, * @param order * @returns {Array} */ -ESConnector.prototype.buildOrder = function (model, idName, order) { - var sort = []; - - var keys = order; - if (typeof keys === 'string') { - keys = keys.split(','); - } - for (var index = 0, len = keys.length; index < len; index++) { - var m = keys[index].match(/\s+(A|DE)SC$/); - var key = keys[index]; - key = key.replace(/\s+(A|DE)SC$/, '').trim(); - if (key === 'id' || key === idName) { - key = '_id'; - } - if (m && m[1] === 'DE') { - //sort[key] = -1; - var temp = {}; - temp[key] = 'desc'; - sort.push(temp); - } else { - //sort[key] = 1; - sort.push(key); - } - } - - return sort; -}; - -ESConnector.prototype.buildWhere = function (model, idName, where) { - var self = this; - - var nestedFields = _.map(self.settings.mappingProperties, function (val, key) { - return val.type == 'nested' ? key : null; - }); - nestedFields = _.filter(nestedFields, function (v) { - return v; - }); - log('ESConnector.prototype.buildWhere', 'model', model, 'idName', idName, 'where', JSON.stringify(where, null, 0)); - - var body = { - query: { - bool: { - must: [], - should: [], - filter: [], - must_not: [] - } - } - }; - - self.buildNestedQueries(body, model, idName, where, nestedFields); - if (body && body.query && body.query.bool && body.query.bool.must && body.query.bool.must.length === 0) { - delete body.query.bool['must']; // jshint ignore:line - } - if (body && body.query && body.query.bool && body.query.bool.filter && body.query.bool.filter.length === 0) { // jshint ignore:line - delete body.query.bool['filter']; // jshint ignore:line - } - if (body && body.query && body.query.bool && body.query.bool.should && body.query.bool.should.length === 0) { - delete body.query.bool['should']; // jshint ignore:line - } - if (body && body.query && body.query.bool && body.query.bool.must_not && body.query.bool.must_not.length === 0) { // jshint ignore:line - delete body.query.bool['must_not']; // jshint ignore:line - } - if (body && body.query && body.query.bool && _.isEmpty(body.query.bool)) { - delete body.query['bool']; // jshint ignore:line - } +ESConnector.prototype.buildOrder = buildOrder; - if (body && body.query && _.isEmpty(body.query)) { - body.query = { - 'bool': { - 'must': { - 'match_all': {} - }, - 'filter': [{ - 'term': { - 'docType': model - } - }] - } - }; - } - return body; -}; - -ESConnector.prototype.buildNestedQueries = function (body, model, idName, where, nestedFields) { - /** - * Return an empty match all object if no property is set in where filter - * @example {where: {}} - */ - if (_.keys(where).length === 0) { - body = { - 'query': { - 'bool': { - 'must': { - 'match_all': {} - }, - 'filter': [{ - 'term': { - 'docType': model - } - }] - } - } - }; - log('ESConnector.prototype.buildNestedQueries', '\nbody', JSON.stringify(body, null, 0)); - return body; - } - var rootPath = body.query; - ESConnector.prototype.buildDeepNestedQueries(true, idName, where, body, rootPath, model, nestedFields); - var docTypeQuery = _.find(rootPath.bool.filter, function (v) { - return v.term && v.term.docType; - }); - var addedDocTypeToRootPath = false; - if (typeof docTypeQuery != 'undefined') { - addedDocTypeToRootPath = true; - docTypeQuery.term.docType = model; - } else { - addedDocTypeToRootPath = true; - rootPath.bool.filter.push({ - 'term': { - 'docType': model - } - }); - } - - if (addedDocTypeToRootPath) { - if (!!rootPath && rootPath.bool && rootPath.bool.should && rootPath.bool.should.length != 0) { - rootPath.bool.must.push({ - 'bool': { - 'should': rootPath.bool.should - } - }); - rootPath.bool.should = []; - } - - if (!!rootPath && rootPath.bool && rootPath.bool.must_not && rootPath.bool.must_not.length != 0) { - rootPath.bool.must.push({ - 'bool': { - 'must_not': rootPath.bool.must_not - } - }); - rootPath.bool.must_not = []; - } - } -}; - -ESConnector.prototype.buildDeepNestedQueries = function (root, idName, where, body, path, model, nestedFields) { - var self = this; - _.forEach(where, function (value, key) { - var cond = value; - if (key === 'id' || key === idName) { - key = '_id'; - } - var splitKey = key.split('.'); - var isNestedKey = false, - nestedSuperKey = null; - if (key.indexOf('.') > -1 && !!splitKey[0] && nestedFields.indexOf(splitKey[0]) > -1) { - isNestedKey = true; - nestedSuperKey = splitKey[0]; - } - - if (key === 'and' && Array.isArray(value)) { - var andPath; - if (root) { - andPath = path.bool.must; - } else { - var andObject = { - bool: { - must: [] - } - }; - andPath = andObject.bool.must; - path.push(andObject); - } - cond.map(function (c) { - log('ESConnector.prototype.buildDeepNestedQueries', 'mapped', 'body', JSON.stringify(body, null, 0)); - self.buildDeepNestedQueries(false, idName, c, body, andPath, model, nestedFields); - }); - } else if (key === 'or' && Array.isArray(value)) { - var orPath; - if (root) { - orPath = path.bool.should; - } else { - var orObject = { - bool: { - should: [] - } - }; - orPath = orObject.bool.should; - path.push(orObject); - } - cond.map(function (c) { - log('ESConnector.prototype.buildDeepNestedQueries', 'mapped', 'body', JSON.stringify(body, null, 0)); - self.buildDeepNestedQueries(false, idName, c, body, orPath, model, nestedFields); - }); - } else { - var spec = false; - var options = null; - if (cond && cond.constructor.name === 'Object') { // need to understand - options = cond.options; - spec = Object.keys(cond)[0]; - cond = cond[spec]; - } - log('ESConnector.prototype.buildNestedQueries', - 'spec', spec, 'key', key, 'cond', JSON.stringify(cond, null, 0), 'options', options); - if (spec) { - if (spec === 'gte' || spec === 'gt' || spec === 'lte' || spec === 'lt') { - var rangeQuery = { - range: {} - }; - var rangeQueryGuts = {}; - rangeQueryGuts[spec] = cond; - rangeQuery.range[key] = rangeQueryGuts; +ESConnector.prototype.buildWhere = buildWhere; - // Additional handling for nested Objects - if (isNestedKey) { - rangeQuery = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': rangeQuery - } - } - } +ESConnector.prototype.buildNestedQueries = buildNestedQueries; - if (root) { - path.bool.must.push(rangeQuery); - } else { - path.push(rangeQuery); - } - } - - /** - * Logic for loopback `between` filter of where - * @example {where: {size: {between: [0,7]}}} - */ - if (spec === 'between') { - if (cond.length == 2 && (cond[0] <= cond[1])) { - var betweenArray = { - range: {} - }; - betweenArray.range[key] = { - gte: cond[0], - lte: cond[1] - }; - - // Additional handling for nested Objects - if (isNestedKey) { - betweenArray = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': betweenArray - } - } - } - if (root) { - path.bool.must.push(betweenArray); - } else { - path.push(betweenArray); - } - } - } - /** - * Logic for loopback `inq`(include) filter of where - * @example {where: { property: { inq: [val1, val2, ...]}}} - */ - if (spec === 'inq') { - var inArray = { - terms: {} - }; - inArray.terms[key] = cond; - // Additional handling for nested Objects - if (isNestedKey) { - inArray = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': inArray - } - } - } - if (root) { - path.bool.must.push(inArray); - } else { - path.push(inArray); - } - log('ESConnector.prototype.buildDeepNestedQueries', - 'body', body, - 'inArray', JSON.stringify(inArray, null, 0)); - } - - /** - * Logic for loopback `nin`(not include) filter of where - * @example {where: { property: { nin: [val1, val2, ...]}}} - */ - if (spec === 'nin') { - var notInArray = { - terms: {} - }; - notInArray.terms[key] = cond; - // Additional handling for nested Objects - if (isNestedKey) { - notInArray = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - bool: { - must: [notInArray] - } - } - } - } - } - if (root) { - path.bool.must_not.push(notInArray); - } else { - path.push({ - bool: { - must_not: [notInArray] - } - }); - } - } - - /** - * Logic for loopback `neq` (not equal) filter of where - * @example {where: {role: {neq: 'lead' }}} - */ - if (spec === 'neq') { - /** - * First - filter the documents where the given property exists - * @type {{exists: {field: *}}} - */ - // var missingFilter = {exists :{field : key}}; - /** - * Second - find the document where value not equals the given value - * @type {{term: {}}} - */ - var notEqual = { - term: {} - }; - notEqual.term[key] = cond; - /** - * Apply the given filter in the main filter(body) and on given path - */ - // Additional handling for nested Objects - if (isNestedKey) { - notEqual = { - 'match': {} - }; - notEqual.match[key] = cond; - notEqual = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - bool: { - must: [notEqual] - } - } - } - } - } - if (root) { - path.bool.must_not.push(notEqual); - } else { - path.push({ - bool: { - must_not: [notEqual] - } - }); - } - - - // body.query.bool.must.push(missingFilter); - } - // TODO: near - For geolocations, return the closest points, sorted in order of distance. Use with limit to return the n closest points. - // TODO: like, nlike - // TODO: ilike, inlike - if (spec === 'like') { - var likeQuery = { - regexp: {} - }; - likeQuery.regexp[key] = cond; - - // Additional handling for nested Objects - if (isNestedKey) { - likeQuery = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - 'bool': { - 'must': [likeQuery] - } - } - } - } - } - if (root) { - path.bool.must.push(likeQuery); - } else { - path.push(likeQuery); - } - } - - if (spec === 'nlike') { - var nlikeQuery = { - regexp: {} - }; - nlikeQuery.regexp[key] = cond; - - // Additional handling for nested Objects - if (isNestedKey) { - nlikeQuery = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - bool: { - 'must_not': [nlikeQuery] - } - } - } - } - } - if (root) { - path.bool.must_not.push(nlikeQuery); - } else { - path.push({ - bool: { - must_not: [nlikeQuery] - } - }); - } - } - // TODO: regex - - // geo_shape || geo_distance || geo_bounding_box - if (spec === 'geo_shape' || spec === 'geo_distance' || spec === 'geo_bounding_box') { - var geoQuery = { - 'filter': {} - }; - geoQuery.filter[spec] = cond; - - if (isNestedKey) { - geoQuery = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - 'bool': geoQuery - } - } - } - if (root) { - path.bool.must.push(geoQuery); - } else { - path.push(geoQuery); - } - } else { - if (root) { - path.bool.filter = geoQuery; - } else { - path.push({ - 'bool': geoQuery - }); - } - } - - } - } else { - var nestedQuery = {}; - if (typeof value === 'string') { - value = value.trim(); - if (value.indexOf(' ') > -1) { - nestedQuery.match_phrase = {}; - nestedQuery.match_phrase[key] = value; - } else { - nestedQuery.match = {}; - nestedQuery.match[key] = value; - } - } else { - nestedQuery.match = {}; - nestedQuery.match[key] = value; - } - // Additional handling for nested Objects - if (isNestedKey) { - nestedQuery = { - 'nested': { - 'path': nestedSuperKey, - 'score_mode': 'max', - 'query': { - 'bool': { - 'must': [nestedQuery] - } - } - } - } - } - - if (root) { - path.bool.must.push(nestedQuery) - } else { - path.push(nestedQuery); - } - - log('ESConnector.prototype.buildDeepNestedQueries', - 'body', body, - 'nestedQuery', JSON.stringify(nestedQuery, null, 0)); - } - } - }); -}; +ESConnector.prototype.buildDeepNestedQueries = buildDeepNestedQueries; /** * Get document Id validating data @@ -1018,9 +334,8 @@ ESConnector.prototype.getDocumentId = function (id) { try { if (typeof id !== 'string') { return id.toString(); - } else { - return id; } + return id; } catch (e) { return id; } @@ -1046,49 +361,13 @@ ESConnector.prototype.getDocumentId = function (id) { ESConnector.prototype.getDefaultIdType = function () { return String; }; - /** * Create a new model instance * @param {String} model name * @param {object} data info * @param {Function} done - invoke the callback with the created model's id as an argument */ -ESConnector.prototype.create = function (model, data, done) { - var self = this; - if (self.debug) { - log('ESConnector.prototype.create', model, data); - } - - var idValue = self.getIdValue(model, data); - var idName = self.idName(model); - log('ESConnector.prototype.create', 'idName', idName, 'idValue', idValue); - //TODO: If model has custom id with generated false and if Id field is not prepopulated then create should fail. - //TODO: If model Id is not string and generated is true then create should fail because the auto generated es id is of type string and we cannot change it. - var document = self.addDefaults(model, 'create'); - document[self.idField] = self.getDocumentId(idValue); - document.body = {}; - _.assign(document.body, data); - log('ESConnector.prototype.create', 'document', document); - var method = 'create'; - if (!document[self.idField]) { - method = 'index'; // if there is no/empty id field, we must use the index method to create it (API 5.0) - } - document.body.docType = model; - self.db[method]( - document - ).then( - function (response) { - log('ESConnector.prototype.create', 'response', response); - log('ESConnector.prototype.create', 'will invoke callback with id:', response._id); - done(null, response._id); // the connector framework expects the id as a return value - } - ).catch(function (err) { - log('ESConnector.prototype.create', err.message); - if (err) { - return done(err, null); - } - }); -}; +ESConnector.prototype.create = create; /** * Query model instances by filter @@ -1101,63 +380,7 @@ ESConnector.prototype.create = function (model, data, done) { * it makes sense to return the id with the content! So for a datasource like elasticsearch, * make sure to map '_id' into the content, just in case its an auto-generated one. */ -ESConnector.prototype.all = function all(model, filter, done) { - var self = this; - log('ESConnector.prototype.all', 'model', model, 'filter', JSON.stringify(filter, null, 0)); - - var idName = self.idName(model); - log('ESConnector.prototype.all', 'idName', idName); - - if (filter && filter.suggests) { // TODO: remove HACK!!! - self.db.suggest( - self.buildFilter(model, idName, filter, self.defaultSize) - ).then( - function (body) { - var result = []; - if (body.hits) { - body.hits.hits.forEach(function (item) { - result.push(self.dataSourceToModel(model, item, idName)); - }); - } - log('ESConnector.prototype.all', 'model', model, 'result', JSON.stringify(result, null, 2)); - if (filter && filter.include) { - self._models[model].model.include(result, filter.include, done); - } else { - done(null, result); - } - }, - function (err) { - log('ESConnector.prototype.all', err.message); - if (err) { - return done(err, null); - } - } - ); - } else { - self.db.search( - self.buildFilter(model, idName, filter, self.defaultSize) - ).then( - function (body) { - var result = []; - body.hits.hits.forEach(function (item) { - result.push(self.dataSourceToModel(model, item, idName)); - }); - log('ESConnector.prototype.all', 'model', model, 'result', JSON.stringify(result, null, 2)); - if (filter && filter.include) { - self._models[model].model.include(result, filter.include, done); - } else { - done(null, result); - } - }, - function (err) { - log('ESConnector.prototype.all', err.message); - if (err) { - return done(err, null); - } - } - ); - } -}; +ESConnector.prototype.all = all; /** * Delete model instances by query @@ -1165,34 +388,7 @@ ESConnector.prototype.all = function all(model, filter, done) { * @param {String} whereClause criteria * @param {Function} cb callback */ -ESConnector.prototype.destroyAll = function destroyAll(modelName, whereClause, cb) { - var self = this; - - if ((!cb) && _.isFunction(whereClause)) { - cb = whereClause; - whereClause = {}; - } - log('ESConnector.prototype.destroyAll', 'modelName', modelName, 'whereClause', JSON.stringify(whereClause, null, 0)); - - var idName = self.idName(modelName); - var body = { - query: self.buildWhere(modelName, idName, whereClause).query - }; - - var defaults = self.addDefaults(modelName, 'destroyAll'); - var options = _.defaults({ - body: body - }, defaults); - log('ESConnector.prototype.destroyAll', 'options:', JSON.stringify(options, null, 2)); - self.db.deleteByQuery(options) - .then(function (response) { - cb(null, response); - }) - .catch(function (err) { - log('ESConnector.prototype.destroyAll', err.message); - return cb(err, null); - }); -}; +ESConnector.prototype.destroyAll = destroyAll; /** * Update model instances by query @@ -1214,54 +410,7 @@ ESConnector.prototype.destroyAll = function destroyAll(modelName, whereClause, c * Notice the second argument is an object with the count property * representing the number of rows that were updated. */ -ESConnector.prototype.updateAll = function updateAll(model, where, data, options, cb) { - var self = this; - if (self.debug) { - log('ESConnector.prototype.updateAll', 'model', model, 'options', options, 'where', where, 'date', data); - } - var idName = self.idName(model); - log('ESConnector.prototype.updateAll', 'idName', idName); - - var defaults = self.addDefaults(model, 'updateAll'); - - var body = { - query: self.buildWhere(model, idName, where).query - }; - - body.script = { - inline: '', - params: {} - }; - _.forEach(data, function (value, key) { - if (key !== '_id' || key !== idName) { - // default language for inline scripts is painless if ES 5, so this needs the extra params. - body.script.inline += 'ctx._source.' + key + '=params.' + key + ';'; - body.script.params[key] = value; - if (key === 'docType') { - body.script.params[key] = model; - } - } - }); - - var document = _.defaults({ - body: body - }, defaults); - log('ESConnector.prototype.updateAll', 'document to update', document); - - self.db.updateByQuery(document) - .then(function (response) { - log('ESConnector.prototype.updateAll', 'response', response); - return cb(null, { - updated: response.updated, - total: response.total - }); - }, function (err) { - log('ESConnector.prototype.updateAll', err.message); - if (err) { - return cb(err, null); - } - }); -}; +ESConnector.prototype.updateAll = updateAll; ESConnector.prototype.update = ESConnector.prototype.updateAll; @@ -1271,30 +420,7 @@ ESConnector.prototype.update = ESConnector.prototype.updateAll; * @param {String} where criteria * @param {Function} done callback */ -ESConnector.prototype.count = function count(modelName, done, where) { - var self = this; - log('ESConnector.prototype.count', 'model', modelName, 'where', where); - - var idName = self.idName(modelName); - var body = where.native ? where.native : { - query: self.buildWhere(modelName, idName, where).query - }; - - var defaults = self.addDefaults(modelName, 'count'); - self.db.count(_.defaults({ - body: body - }, defaults)).then( - function (response) { - done(null, response.count); - }, - function (err) { - log('ESConnector.prototype.count', err.message); - if (err) { - return done(err, null); - } - } - ); -}; +ESConnector.prototype.count = count; /** * Implement CRUD Level II - A connector can choose to implement the following methods, @@ -1315,29 +441,7 @@ ESConnector.prototype.count = function count(modelName, done, where) { * @param {String} id row identifier * @param {Function} done callback */ -ESConnector.prototype.find = function find(modelName, id, done) { - var self = this; - log('ESConnector.prototype.find', 'model', modelName, 'id', id); - - if (id === undefined || id === null) { - throw new Error('id not set!'); - } - - var defaults = self.addDefaults(modelName, 'find'); - self.db.get(_.defaults({ - id: self.getDocumentId(id) - }, defaults)).then( - function (response) { - done(null, self.dataSourceToModel(modelName, response)); - }, - function (err) { - log('ESConnector.prototype.find', err.message); - if (err) { - return done(err, null); - } - } - ); -}; +ESConnector.prototype.find = find; /** * Delete a model instance by id @@ -1345,87 +449,14 @@ ESConnector.prototype.find = function find(modelName, id, done) { * @param {String} id row identifier * @param {Function} done callback */ -ESConnector.prototype.destroy = function destroy(modelName, id, done) { - var self = this; - if (self.debug) { - log('ESConnector.prototype.destroy', 'model', modelName, 'id', id); - } - - var filter = self.addDefaults(modelName, 'destroy'); - filter[self.idField] = self.getDocumentId(id); - if (!filter[self.idField]) { - throw new Error('Document id not setted!'); - } - self.db.delete( - filter - ).then( - function (response) { - done(null, response); - }, - function (err) { - log('ESConnector.prototype.destroy', err.message); - if (err) { - return done(err, null); - } - } - ); -}; +ESConnector.prototype.destroy = destroy; /** * Update a model instance by id * */ -ESConnector.prototype.updateAttributes = function updateAttrs(modelName, id, data, callback) { - var self = this; - if (self.debug) { - log('ESConnector.prototype.updateAttributes', 'modelName', modelName, 'id', id, 'data', data); - } - var idName = self.idName(modelName); - log('ESConnector.prototype.updateAttributes', 'idName', idName); - - var defaults = self.addDefaults(modelName, 'updateAll'); - - var body = { - query: self.buildWhere(modelName, idName, { - _id: id - }).query - }; - - body.script = { - inline: '', - params: {} - }; - _.forEach(data, function (value, key) { - if (key !== '_id' || key !== idName) { - // default language for inline scripts is painless if ES 5, so this needs the extra params. - body.script.inline += 'ctx._source.' + key + '=params.' + key + ';'; - body.script.params[key] = value; - if (key === 'docType') { - body.script.params[key] = modelName; - } - } - }); - - var document = _.defaults({ - body: body - }, defaults); - log('ESConnector.prototype.updateAttributes', 'document to update', document); - - self.db.updateByQuery(document) - .then(function (response) { - log('ESConnector.prototype.updateAttributes', 'response', response); - return callback(null, { - updated: response.updated, - total: response.total - }); - }, function (err) { - log('ESConnector.prototype.updateAttributes', err.message); - if (err) { - return callback(err, null); - } - }); -}; +ESConnector.prototype.updateAttributes = updateAttributes; /** * Check existence of a model instance by id @@ -1433,29 +464,7 @@ ESConnector.prototype.updateAttributes = function updateAttrs(modelName, id, dat * @param {String} id row identifier * @param {function} done callback */ -ESConnector.prototype.exists = function (modelName, id, done) { - var self = this; - log('ESConnector.prototype.exists', 'model', modelName, 'id', id); - - if (id === undefined || id === null) { - throw new Error('id not set!'); - } - - var defaults = self.addDefaults(modelName, 'exists'); - self.db.exists(_.defaults({ - id: self.getDocumentId(id) - }, defaults)).then( - function (exists) { - done(null, exists); - }, - function (err) { - log('ESConnector.prototype.exists', err.message); - if (err) { - return done(err, null); - } - } - ); -}; +ESConnector.prototype.exists = exists; /** * Implement CRUD Level III - A connector can also optimize certain methods @@ -1475,39 +484,12 @@ ESConnector.prototype.exists = function (modelName, id, done) { * @param {Object} data document * @param {Function} done callback */ -ESConnector.prototype.save = function (model, data, done) { - var self = this; - if (self.debug) { - log('ESConnector.prototype.save ', 'model', model, 'data', data); - } - - var idName = self.idName(model); - var defaults = self.addDefaults(model, 'save'); - var id = self.getDocumentId(data[idName]); - - if (id === undefined || id === null) { - return done('Document id not setted!', null); - } - data.docType = model; - self.db.update(_.defaults({ - id: id, - body: { - doc: data, - 'doc_as_upsert': false - } - }, defaults)).then(function (response) { - done(null, response); - }, function (err) { - if (err) { - return done(err, null); - } - }); -}; +ESConnector.prototype.save = save; /** * Find or create a model instance */ -//ESConnector.prototype.findOrCreate = function (model, data, callback) {...}; +// ESConnector.prototype.findOrCreate = function (model, data, callback) {...}; /** * Update or insert a model instance @@ -1521,65 +503,7 @@ ESConnector.prototype.save = function (model, data, done) { * with the value true if a new model was created * and the value false is an existing model was found & updated. */ -ESConnector.prototype.updateOrCreate = function updateOrCreate(modelName, data, callback) { - var self = this; - log('ESConnector.prototype.updateOrCreate', 'modelName', modelName, 'data', data); - - var idName = self.idName(modelName); - var id = self.getDocumentId(data[idName]); - if (id === undefined || id === null) { - throw new Error('id not set!'); - } - - var defaults = self.addDefaults(modelName, 'updateOrCreate'); - data.docType = modelName; - self.db.update(_.defaults({ - id: id, - body: { - doc: data, - 'doc_as_upsert': true - } - }, defaults)).then( - function (response) { - /** - * In the case of an update, elasticsearch only provides a confirmation that it worked - * but does not provide any model data back. So what should be passed back in - * the data object (second argument of callback)? - * Q1) Should we just pass back the data that was meant to be updated - * and came in as an argument to the updateOrCreate() call? This is what - * the memory connector seems to do. - * A: [Victor Law] Yes, that's fine to do. The reason why we are passing the data there - * and back is to support databases that can add default values to undefined properties, - * typically the id property is often generated by the backend. - * Q2) OR, should we make an additional call to fetch the data for that id internally, - * within updateOrCreate()? So we can make sure to pass back a data object? - * A: [Victor Law] - * - Most connectors don't fetch the inserted/updated data and hope the data stored into DB - * will be the same as the data sent to DB for create/update. - * - It's true in most cases but not always. For example, the DB might have triggers - * that change the value after the insert/update. - * - We don't support that yet. - * - In the future, that can be controlled via an options property, - * such as fetchNewInstance = true. - * - * NOTE: Q1 based approach has been implemented for now. - */ - if (response._version === 1) { // distinguish if it was an update or create operation in ES - data[idName] = response._id; - log('ESConnector.prototype.updateOrCreate', 'assigned ID', idName, '=', response._id); - } - callback(null, data, { - isNewInstance: response.created - }); - }, - function (err) { - log('ESConnector.prototype.updateOrCreate', err.message); - if (err) { - return callback(err, null); - } - } - ); -}; +ESConnector.prototype.updateOrCreate = updateOrCreate; /** * replace or insert a model instance @@ -1593,73 +517,9 @@ ESConnector.prototype.updateOrCreate = function updateOrCreate(modelName, data, * with the value true if a new model was created * and the value false is an existing model was found & updated. */ -ESConnector.prototype.replaceOrCreate = function (modelName, data, callback) { - var self = this; - log('ESConnector.prototype.replaceOrCreate', 'modelName', modelName, 'data', data); - - var idName = self.idName(modelName); - var id = self.getDocumentId(data[idName]); - if (id === undefined || id === null) { - throw new Error('id not set!'); - } - - var document = self.addDefaults(modelName, 'replaceOrCreate'); - document[self.idField] = id; - document.body = {}; - _.assign(document.body, data); - document.body.docType = modelName; - log('ESConnector.prototype.replaceOrCreate', 'document', document); - self.db.index( - document - ).then( - function (response) { - log('ESConnector.prototype.replaceOrCreate', 'response', response); - log('ESConnector.prototype.replaceOrCreate', 'will invoke callback with id:', response._id); - callback(null, response._id); // the connector framework expects the id as a return value - } - ).catch(function (err) { - log('ESConnector.prototype.replaceOrCreate', err.message); - if (err) { - return callback(err, null); - } - }); -}; - -ESConnector.prototype.replaceById = function replace(modelName, id, data, options, callback) { - var self = this; - log('ESConnector.prototype.replaceById', 'modelName', modelName, 'id', id, 'data', data); - - var idName = self.idName(modelName); - if (id === undefined || id === null) { - throw new Error('id not set!'); - } - - var modelProperties = this._models[modelName].properties; +ESConnector.prototype.replaceOrCreate = replaceOrCreate; - var document = self.addDefaults(modelName, 'replaceById'); - document[self.idField] = self.getDocumentId(id); - document.body = {}; - _.assign(document.body, data); - document.body.docType = modelName; - if (modelProperties.hasOwnProperty(idName)) { - document.body[idName] = id; - } - log('ESConnector.prototype.replaceById', 'document', document); - self.db.index( - document - ).then( - function (response) { - log('ESConnector.prototype.replaceById', 'response', response); - log('ESConnector.prototype.replaceById', 'will invoke callback with id:', response._id); - callback(null, response._id); // the connector framework expects the id as a return value - } - ).catch(function (err) { - log('ESConnector.prototype.replaceById', err.message); - if (err) { - return callback(err, null); - } - }); -}; +ESConnector.prototype.replaceById = replaceById; /** * Migration @@ -1674,14 +534,11 @@ ESConnector.prototype.replaceById = function replace(modelName, id, data, option * --> Drop the corresponding indices: both mappings and data are done away with * --> create/recreate mappings and indices * - * @param {String[]} [models] A model name or an array of model names. If not present, apply to all models + * @param {String[]} [models] A model name or an array of model names. + * If not present, apply to all models * @param {Function} [cb] The callback function */ -ESConnector.prototype.automigrate = require('./automigrate.js')({ - log: log, - lodash: _, - bluebird: Promise -}); +ESConnector.prototype.automigrate = automigrate; module.exports.name = ESConnector.name; -module.exports.ESConnector = ESConnector; \ No newline at end of file +module.exports.ESConnector = ESConnector; diff --git a/lib/exists.js b/lib/exists.js new file mode 100644 index 0000000..12649a9 --- /dev/null +++ b/lib/exists.js @@ -0,0 +1,23 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function exists(modelName, id, done) { + const self = this; + log('ESConnector.prototype.exists', 'model', modelName, 'id', id); + + if (id === undefined || id === null) { + throw new Error('id not set!'); + } + + const defaults = self.addDefaults(modelName, 'exists'); + self.db.exists(_.defaults({ + id: self.getDocumentId(id) + }, defaults)).then(({ body }) => { + done(null, body); + }).catch((error) => { + log('ESConnector.prototype.exists', error.message); + done(error); + }); +} + +module.exports.exists = exists; diff --git a/lib/find.js b/lib/find.js new file mode 100644 index 0000000..614a8df --- /dev/null +++ b/lib/find.js @@ -0,0 +1,23 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function find(modelName, id, done) { + const self = this; + log('ESConnector.prototype.find', 'model', modelName, 'id', id); + + if (id === undefined || id === null) { + throw new Error('id not set!'); + } + + const defaults = self.addDefaults(modelName, 'find'); + self.db.get(_.defaults({ + id: self.getDocumentId(id) + }, defaults)).then(({ body }) => { + done(null, self.dataSourceToModel(modelName, body)); + }).catch((error) => { + log('ESConnector.prototype.find', error.message); + done(error); + }); +} + +module.exports.find = find; diff --git a/lib/replaceById.js b/lib/replaceById.js new file mode 100644 index 0000000..5a55c69 --- /dev/null +++ b/lib/replaceById.js @@ -0,0 +1,41 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function replaceById(modelName, id, data, options, callback) { + const self = this; + log('ESConnector.prototype.replaceById', 'modelName', modelName, 'id', id, 'data', data); + + const idName = self.idName(modelName); + if (id === undefined || id === null) { + throw new Error('id not set!'); + } + + // eslint-disable-next-line no-underscore-dangle + const modelProperties = this._models[modelName].properties; + + const document = self.addDefaults(modelName, 'replaceById'); + document[self.idField] = self.getDocumentId(id); + document.body = {}; + _.assign(document.body, data); + document.body.docType = modelName; + if (Object.prototype.hasOwnProperty.call(modelProperties, idName)) { + document.body[idName] = id; + } + log('ESConnector.prototype.replaceById', 'document', document); + self.db.index( + document + ).then( + ({ body }) => { + log('ESConnector.prototype.replaceById', 'response', body); + // eslint-disable-next-line no-underscore-dangle + log('ESConnector.prototype.replaceById', 'will invoke callback with id:', body._id); + // eslint-disable-next-line no-underscore-dangle + callback(null, body._id); // the connector framework expects the id as a return value + } + ).catch((error) => { + log('ESConnector.prototype.replaceById', error.message); + callback(error); + }); +} + +module.exports.replaceById = replaceById; diff --git a/lib/replaceOrCreate.js b/lib/replaceOrCreate.js new file mode 100644 index 0000000..d025c12 --- /dev/null +++ b/lib/replaceOrCreate.js @@ -0,0 +1,42 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function replaceOrCreate(modelName, data, callback) { + const self = this; + log('ESConnector.prototype.replaceOrCreate', 'modelName', modelName, 'data', data); + + const idName = self.idName(modelName); + const id = self.getDocumentId(data[idName]); + if (id === undefined || id === null) { + throw new Error('id not set!'); + } + + const document = self.addDefaults(modelName, 'replaceOrCreate'); + document[self.idField] = id; + document.body = {}; + _.assign(document.body, data); + document.body.docType = modelName; + log('ESConnector.prototype.replaceOrCreate', 'document', document); + self.db.index( + document + ).then( + ({ body }) => { + log('ESConnector.prototype.replaceOrCreate', 'response', body); + const options = { + // eslint-disable-next-line no-underscore-dangle + id: body._id, + index: self.index + }; + if (self.mappingType) { + options.type = self.mappingType; + } + return self.db.get(options); + } + ).then(({ body }) => callback(null, + self.dataSourceToModel(modelName, body, idName))).catch((error) => { + log('ESConnector.prototype.replaceOrCreate', error.message); + return callback(error, null); + }); +} + +module.exports.replaceOrCreate = replaceOrCreate; diff --git a/lib/save.js b/lib/save.js new file mode 100644 index 0000000..22b5740 --- /dev/null +++ b/lib/save.js @@ -0,0 +1,33 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +// eslint-disable-next-line consistent-return +function save(model, data, done) { + const self = this; + if (self.debug) { + log('ESConnector.prototype.save ', 'model', model, 'data', data); + } + + const idName = self.idName(model); + const defaults = self.addDefaults(model, 'save'); + const id = self.getDocumentId(data[idName]); + + if (id === undefined || id === null) { + return done('Document id not setted!', null); + } + data.docType = model; + self.db.update(_.defaults({ + id, + body: { + doc: data, + doc_as_upsert: false + } + }, defaults)).then(({ body }) => { + done(null, body); + }).catch((error) => { + log('ESConnector.prototype.save', error.message); + done(error); + }); +} + +module.exports.save = save; diff --git a/lib/setupIndex.js b/lib/setupIndex.js index e5a5aea..0c0673e 100644 --- a/lib/setupIndex.js +++ b/lib/setupIndex.js @@ -1,54 +1,50 @@ -'use strict'; +const log = require('debug')('loopback:connector:elasticsearch'); -var log = null; -var Promise = null; - -var createIndex = function createIndex(self, params) { - log('ESConnector.prototype.setupIndices', 'createIndex()', params); - return self.db.indices.create(params) - .then(function (info) { - log('ESConnector.prototype.setupIndices', 'createIndex()', 'self.db.indices.create()', 'response:', info); - return Promise.resolve(); - }, function (err) { - if (err.message.indexOf('IndexAlreadyExistsException') !== -1 || - err.message.indexOf('index_already_exists_exception') !== -1) { - //console.trace('OMG WTF', err); - log('ESConnector.prototype.setupIndices', 'createIndex()', 'self.db.indices.create()', 'we ate IndexAlreadyExistsException'); - return Promise.resolve(); - } else { - log('ESConnector.prototype.setupIndices', 'createIndex()', 'self.db.indices.create()', 'failed:', err); - return Promise.reject(err); +async function setupIndex() { + const self = this; + const { + db, + version, + index, + settings: { + mappingProperties, + mappingType, + indexSettings + } + } = self; + const { body: exists } = await db.indices.exists({ + index + }); + mappingProperties.docType = { + type: 'keyword', + index: true + }; + const mapping = { + properties: mappingProperties + }; + if (!exists) { + log('ESConnector.prototype.setupIndex', 'create index with mapping for', index); + await db.indices.create({ + index, + body: { + settings: indexSettings, + mappings: version < 7 ? { + [mappingType]: mapping + } : mapping } }); -}; - -var setupIndex = function setupIndex(indexName) { - var self = this; - - if (!indexName) { // validate input - return Promise.reject('missing indexName'); + return Promise.resolve(); } - - var params = { - index: indexName, - body: self.searchIndexSettings + const updateMapping = { + index, + body: mapping }; - return self.db.indices.exists(params) - .then(function (exists) { - log('ESConnector.prototype.setupIndices', 'self.db.indices.exists()', 'response:', exists); - if (!exists) { - return createIndex(self, params); - } else { - return Promise.resolve(); - } - }, function (err) { - log('ESConnector.prototype.setupIndices', 'self.db.indices.exists()', 'failed:', err); - return Promise.reject(err); - }); -}; + log('ESConnector.prototype.setupIndex', 'update mapping for index', index); + if (version < 7) { + updateMapping.type = mappingType; + } + await db.indices.putMapping(updateMapping); + return Promise.resolve(); +} -module.exports = function (dependencies) { - log = (dependencies) ? (dependencies.log || console.log) : console.log; /*eslint no-console: ['error', { allow: ['log'] }] */ - Promise = (dependencies) ? (dependencies.bluebird || require('bluebird')) : require('bluebird'); - return setupIndex; -}; \ No newline at end of file +module.exports.setupIndex = setupIndex; diff --git a/lib/setupMapping.js b/lib/setupMapping.js deleted file mode 100644 index dc7d0b3..0000000 --- a/lib/setupMapping.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -var _ = null; -var log = null; -var Promise = null; - -var setupMapping = function (mappingType) { - if (!mappingType) { - return Promise.reject('missing mappingType'); - } - var self = this; - var db = self.db; - var settings = self.settings; - - var mappingsFromDatasource = [{ - 'name': mappingType, - 'properties': settings.mappingProperties - }] - log('ESConnector.prototype.setupMapping', 'mappingsFromDatasource:', mappingsFromDatasource); - - var mappingFromDatasource; - if (mappingsFromDatasource.length === 0) { - log('ESConnector.prototype.setupMapping', 'missing mapping for mappingType:', mappingType, - ' ... this usecase is legitimate if you want elasticsearch to take care of mapping dynamically'); - return Promise.resolve(); - } else if (mappingsFromDatasource.length > 1) { - return Promise.reject('more than one mapping for mappingType:', mappingType); - } else { - log('ESConnector.prototype.setupMapping', 'found mapping for mappingType:', mappingType); - mappingFromDatasource = mappingsFromDatasource[0]; - } - - var defaults = self.addDefaults(mappingFromDatasource.name); // NOTE: this is where the magic happens - var mapping = _.clone(mappingFromDatasource); - - // TODO: create a method called cleanUpMapping or something like that to blackbox this stuff - delete mapping.name; - // delete mapping.index; - delete mapping.type; - - // adding 'docType' mandatory keyword field to mapping properties - mapping.properties.docType = { - 'type': 'keyword', - 'index': true - }; - - log('ESConnector.prototype.setupMapping', 'will setup mapping for mappingType:', mappingFromDatasource.name); - - //return self.setupIndices(defaults.index) - return self.setupIndex(defaults.index).then(function () { - log('ESConnector.prototype.setupMapping', 'db.indices.putMapping', 'mappingType:', mappingType, 'start'); - return db.indices.putMapping(_.defaults({ - body: mapping - }, defaults)).then(function (body) { - log('ESConnector.prototype.setupMapping', 'db.indices.putMapping', 'mappingType:', mappingType, 'response', body); - return Promise.resolve(); - }, function (err) { - log('ESConnector.prototype.setupMapping', 'db.indices.putMapping', 'mappingType:', mappingType, 'failed', err); - //console.trace(err.message); - return Promise.reject(err); - }); - }); -}; - -module.exports = function (dependencies) { - log = (dependencies) ? (dependencies.log || console.log) : console.log; /*eslint no-console: ['error', { allow: ['log'] }] */ - _ = (dependencies) ? (dependencies.lodash || require('lodash')) : require('lodash'); - Promise = (dependencies) ? (dependencies.bluebird || require('bluebird')) : require('bluebird'); - return setupMapping; -}; \ No newline at end of file diff --git a/lib/setupMappings.js b/lib/setupMappings.js deleted file mode 100644 index a401600..0000000 --- a/lib/setupMappings.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -// var _ = null; -var log = null; -var Promise = null; - -var setupMappings = function () { - var self = this; - // var modelNames = _.map(self.settings.mappings, 'name'); - var mappingTypes = [(self.settings.mappingType || 'basedata')]; - - return Promise.map(mappingTypes, function (mappingType) { - log('ESConnector.prototype.setupMappings', 'will setup mapping for mappingType:', mappingType); - return self.setupMapping(mappingType).then(function (body) { - log('ESConnector.prototype.setupMappings', 'finished mapping for mappingType:', mappingType, 'response:', body); - return Promise.resolve(); - }, function (err) { - log('ESConnector.prototype.setupMappings', 'failed mapping for mappingType:', mappingType, 'err:', err); - return Promise.reject(err); - }); - }, { - concurrency: 1 - }).then(function () { - log('ESConnector.prototype.setupMappings', 'finished'); - return Promise.resolve(); - }).catch(function (err) { - log('ESConnector.prototype.setupMappings', 'failed', err); - return Promise.reject(err); - }); -}; - -module.exports = function (dependencies) { - log = (dependencies) ? (dependencies.log || console.log) : console.log; /*eslint no-console: ['error', { allow: ['log'] }] */ - // _ = (dependencies) ? (dependencies.lodash || require('lodash')) : require('lodash'); - Promise = (dependencies) ? (dependencies.bluebird || require('bluebird')) : require('bluebird'); - return setupMappings; -}; \ No newline at end of file diff --git a/lib/updateAll.js b/lib/updateAll.js new file mode 100644 index 0000000..332df7d --- /dev/null +++ b/lib/updateAll.js @@ -0,0 +1,51 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function updateAll(model, where, data, options, cb) { + const self = this; + if (self.debug) { + log('ESConnector.prototype.updateAll', 'model', model, 'options', options, 'where', where, 'date', data); + } + const idName = self.idName(model); + log('ESConnector.prototype.updateAll', 'idName', idName); + + const defaults = self.addDefaults(model, 'updateAll'); + + const reqBody = { + query: self.buildWhere(model, idName, where).query + }; + + reqBody.script = { + inline: '', + params: {} + }; + _.forEach(data, (value, key) => { + if (key !== '_id' || key !== idName) { + // default language for inline scripts is painless if ES 5, so this needs the extra params. + reqBody.script.inline += `ctx._source.${key}=params.${key};`; + reqBody.script.params[key] = value; + if (key === 'docType') { + reqBody.script.params[key] = model; + } + } + }); + + const document = _.defaults({ + body: reqBody + }, defaults); + log('ESConnector.prototype.updateAll', 'document to update', document); + + self.db.updateByQuery(document) + .then(({ body }) => { + log('ESConnector.prototype.updateAll', 'response', body); + return cb(null, { + updated: body.updated, + total: body.total + }); + }).catch((error) => { + log('ESConnector.prototype.updateAll', error.message); + return cb(error); + }); +} + +module.exports.updateAll = updateAll; diff --git a/lib/updateAttributes.js b/lib/updateAttributes.js new file mode 100644 index 0000000..bdf5b53 --- /dev/null +++ b/lib/updateAttributes.js @@ -0,0 +1,53 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function updateAttributes(modelName, id, data, callback) { + const self = this; + if (self.debug) { + log('ESConnector.prototype.updateAttributes', 'modelName', modelName, 'id', id, 'data', data); + } + const idName = self.idName(modelName); + log('ESConnector.prototype.updateAttributes', 'idName', idName); + + const defaults = self.addDefaults(modelName, 'updateAll'); + + const reqBody = { + query: self.buildWhere(modelName, idName, { + _id: id + }).query + }; + + reqBody.script = { + inline: '', + params: {} + }; + _.forEach(data, (value, key) => { + if (key !== '_id' || key !== idName) { + // default language for inline scripts is painless if ES 5, so this needs the extra params. + reqBody.script.inline += `ctx._source.${key}=params.${key};`; + reqBody.script.params[key] = value; + if (key === 'docType') { + reqBody.script.params[key] = modelName; + } + } + }); + + const document = _.defaults({ + body: reqBody + }, defaults); + log('ESConnector.prototype.updateAttributes', 'document to update', document); + + self.db.updateByQuery(document) + .then(({ body }) => { + log('ESConnector.prototype.updateAttributes', 'response', body); + return callback(null, { + updated: body.updated, + total: body.total + }); + }).catch((error) => { + log('ESConnector.prototype.updateAttributes', error.message); + return callback(error); + }); +} + +module.exports.updateAttributes = updateAttributes; diff --git a/lib/updateOrCreate.js b/lib/updateOrCreate.js new file mode 100644 index 0000000..8eb477a --- /dev/null +++ b/lib/updateOrCreate.js @@ -0,0 +1,63 @@ +const _ = require('lodash'); +const log = require('debug')('loopback:connector:elasticsearch'); + +function updateOrCreate(modelName, data, callback) { + const self = this; + log('ESConnector.prototype.updateOrCreate', 'modelName', modelName, 'data', data); + + const idName = self.idName(modelName); + const id = self.getDocumentId(data[idName]); + if (id === undefined || id === null) { + throw new Error('id not set!'); + } + + const defaults = self.addDefaults(modelName, 'updateOrCreate'); + data.docType = modelName; + self.db.update(_.defaults({ + id, + body: { + doc: data, + doc_as_upsert: true + } + }, defaults)).then(({ body }) => { + /** + * In the case of an update, elasticsearch only provides a confirmation that it worked + * but does not provide any model data back. So what should be passed back in + * the data object (second argument of callback)? + * Q1) Should we just pass back the data that was meant to be updated + * and came in as an argument to the updateOrCreate() call? This is what + * the memory connector seems to do. + * A: [Victor Law] Yes, that's fine to do. The reason why we are passing the data there + * and back is to support databases that can add default values to undefined properties, + * typically the id property is often generated by the backend. + * Q2) OR, should we make an additional call to fetch the data for that id internally, + * within updateOrCreate()? So we can make sure to pass back a data object? + * A: [Victor Law] + * - Most connectors don't fetch the inserted/updated data + * and hope the data stored into DB + * will be the same as the data sent to DB for create/update. + * - It's true in most cases but not always. For example, the DB might have triggers + * that change the value after the insert/update. + * - We don't support that yet. + * - In the future, that can be controlled via an options property, + * such as fetchNewInstance = true. + * + * NOTE: Q1 based approach has been implemented for now. + */ + // eslint-disable-next-line no-underscore-dangle + if (body._version === 1) { // distinguish if it was an update or create operation in ES + // eslint-disable-next-line no-underscore-dangle + data[idName] = body._id; + // eslint-disable-next-line no-underscore-dangle + log('ESConnector.prototype.updateOrCreate', 'assigned ID', idName, '=', body._id); + } + callback(null, data, { + isNewInstance: body.created + }); + }).catch((error) => { + log('ESConnector.prototype.updateOrCreate', error.message); + return callback(error); + }); +} + +module.exports.updateOrCreate = updateOrCreate; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7959b7d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2601 +0,0 @@ -{ - "name": "loopback-connector-esv6", - "version": "1.0.4", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@kyleshockey/object-assign-deep": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@kyleshockey/object-assign-deep/-/object-assign-deep-0.4.2.tgz", - "integrity": "sha1-hJAPDu/DcnmPR1G1JigwuCCJIuw=", - "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 - }, - "accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha1-9QJfF79lpGaoRYOMz5jNuHfYM4Q=", - "requires": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "agentkeepalive": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", - "requires": { - "humanize-ms": "^1.2.1" - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "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=" - }, - "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" - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "^4.17.10" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true - }, - "bcp47": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bcp47/-/bcp47-1.1.2.tgz", - "integrity": "sha1-NUvjMH/9CEM6ePXh4glYRfifx/4=" - }, - "bl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.1.2.tgz", - "integrity": "sha512-DvC0x+PxmSJNx8wXoFV15pC2+GOJ3ohb4F1REq3X32a2Z3nEBpR1Guu740M7ouYAImFj4BXDNilLNZbygtG9lQ==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "2.11.0", - "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" - }, - "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" - } - }, - "btoa": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.1.2.tgz", - "integrity": "sha1-PkC4FmP4HS3WWWpMtxSo3BbPq+A=", - "dev": true - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "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 - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "chai": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/chai/-/chai-2.3.0.tgz", - "integrity": "sha1-ii9qNHSNqAEJD9cyh7Kqc5pOkJo=", - "dev": true, - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://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" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cldrjs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cldrjs/-/cldrjs-0.5.0.tgz", - "integrity": "sha1-N76S2NGo5myO4S8TA+0xbYXY6zc=" - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "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 - }, - "coffee-script": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", - "dev": true - }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.3.0.tgz", - "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", - "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=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", - "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-fetch": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-0.0.8.tgz", - "integrity": "sha1-Ae2U3EB98sAPGAf95wCnz6SKIFw=", - "dev": true, - "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.3" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "d": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, - "dateformat": { - "version": "1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "http://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "diff": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", - "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", - "dev": true - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, - "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } - } - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "elasticsearch": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-15.2.0.tgz", - "integrity": "sha512-jOFcBoEh3Sn3gjUTozInODZTLriJtfppAUC7jnQCUE+OUj8o7GoAyC+L4h/L3ZxmXNFbQCunqVR+nmSofHdo9A==", - "requires": { - "agentkeepalive": "^3.4.1", - "chalk": "^1.0.0", - "lodash": "^4.17.10" - } - }, - "encode-3986": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/encode-3986/-/encode-3986-1.0.0.tgz", - "integrity": "sha1-lA1RSY+HQa3hhLda0UObMXwMemA=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, - "requires": { - "iconv-lite": "~0.4.13" - }, - "dependencies": { - "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" - } - } - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "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=" - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "2.13.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", - "integrity": "sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "concat-stream": "^1.4.6", - "debug": "^2.1.1", - "doctrine": "^1.2.2", - "es6-map": "^0.1.3", - "escope": "^3.6.0", - "espree": "^3.1.6", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^1.1.1", - "glob": "^7.0.3", - "globals": "^9.2.0", - "ignore": "^3.1.2", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "optionator": "^0.8.1", - "path-is-absolute": "^1.0.0", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.6.0", - "strip-json-comments": "~1.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-config-loopback": { - "version": "4.0.0", - "resolved": "http://registry.npmjs.org/eslint-config-loopback/-/eslint-config-loopback-4.0.0.tgz", - "integrity": "sha1-0iwQUq19TNELij7KJStKkYSey9I=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "fast-json-patch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.0.7.tgz", - "integrity": "sha512-DQeoEyPYxdTtfmB3yDlxkLyKTdbJ6ABfFGcMynDqjvGhPYLto/pZyb/dG2Nyd/n9CArjEWN9ZST++AFmgzgbGw==", - "dev": true, - "requires": { - "deep-equal": "^1.0.1" - } - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", - "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "findup-sync": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "dev": true, - "requires": { - "glob": "~3.2.9", - "lodash": "~2.4.1" - }, - "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, - "requires": { - "inherits": "2", - "minimatch": "0.3" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - } - }, - "form-data": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", - "dev": true, - "requires": { - "async": "^2.0.1", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "g11n-pipeline": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/g11n-pipeline/-/g11n-pipeline-2.0.6.tgz", - "integrity": "sha512-ykVjThha+dGKAR/F31kCUxMn7vu1JrmUkDxMs+h7TvjGbQoNx29hsw618GQKm9eT4Qo6E+8zJAnt0BT3gMtggQ==", - "dev": true, - "requires": { - "swagger-client": "^3.8.3" - } - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "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" - } - }, - "globalize": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/globalize/-/globalize-1.4.0.tgz", - "integrity": "sha1-TACnneZ9c5qbf/g7ZrkNAlfCdJM=", - "requires": { - "cldrjs": "^0.5.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "growl": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", - "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", - "dev": true - }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "dev": true, - "requires": { - "async": "~0.1.22", - "coffee-script": "~1.3.3", - "colors": "~0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "~0.4.13", - "exit": "~0.1.1", - "findup-sync": "~0.1.2", - "getobject": "~0.1.0", - "glob": "~3.1.21", - "grunt-legacy-log": "~0.1.0", - "grunt-legacy-util": "~0.2.0", - "hooker": "~0.2.3", - "iconv-lite": "~0.2.11", - "js-yaml": "~2.0.5", - "lodash": "~0.9.2", - "minimatch": "~0.2.12", - "nopt": "~1.0.10", - "rimraf": "~2.2.8", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - }, - "dependencies": { - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "dev": true, - "requires": { - "underscore": "~1.7.0", - "underscore.string": "~2.4.0" - }, - "dependencies": { - "underscore.string": { - "version": "2.4.0", - "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", - "dev": true - } - } - }, - "async": { - "version": "0.1.22", - "resolved": "http://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", - "dev": true - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true, - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "js-yaml": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "dev": true, - "requires": { - "argparse": "~ 0.1.11", - "esprima": "~ 1.0.2" - } - }, - "lodash": { - "version": "0.9.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "rimraf": { - "version": "2.2.8", - "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", - "dev": true - } - } - }, - "grunt-legacy-log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "dev": true, - "requires": { - "colors": "~0.6.2", - "grunt-legacy-log-utils": "~0.1.1", - "hooker": "~0.2.3", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-log-utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "dev": true, - "requires": { - "colors": "~0.6.2", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" - }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", - "dev": true - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "dev": true, - "requires": { - "async": "~0.1.22", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~0.9.2", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - }, - "dependencies": { - "async": { - "version": "0.1.22", - "resolved": "http://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", - "dev": true - }, - "lodash": { - "version": "0.9.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", - "dev": true - }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", - "dev": true - } - } - }, - "grunt-mocha-test": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.12.7.tgz", - "integrity": "sha1-xhzfMqZ2KVQRX+cSuYPj3Y4MlVQ=", - "dev": true, - "requires": { - "hooker": "~0.2.3", - "mkdirp": "^0.5.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" - } - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", - "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.2.11", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", - "dev": true - }, - "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inquirer": { - "version": "0.12.0", - "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-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" - } - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", - "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "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=" - }, - "isomorphic-form-data": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-0.0.1.tgz", - "integrity": "sha1-Am9ifgMrDNhBPsyHVZKLlKRosGI=", - "dev": true, - "requires": { - "form-data": "^1.0.0-rc3" - } - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "dev": true, - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", - "dev": true - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", - "dev": true - } - } - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "requires": { - "invert-kv": "^2.0.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "loopback-connector": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.5.1.tgz", - "integrity": "sha512-tgpOUIW7gPUHlp6yH0u8RSjl4zBmmRZCFy4dWrriniz8IadF5bSo06LOVTOJRtrsbuW8163d0o8W1dNKG7XDfA==", - "requires": { - "async": "^2.1.5", - "bluebird": "^3.4.6", - "debug": "^3.1.0", - "msgpack5": "^4.2.0", - "strong-globalize": "^4.1.1", - "uuid": "^3.0.1" - }, - "dependencies": { - "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" - } - } - }, - "loopback-datasource-juggler": { - "version": "2.58.1", - "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-2.58.1.tgz", - "integrity": "sha512-gxbuJu3oX5HGaOaymDMKYljdcHdP0AoB8ReFm6It1z/MU1gGVesObXzq5HPLNoSi1+i62T1eba+eduIeRCEoEA==", - "dev": true, - "requires": { - "async": "~1.0.0", - "debug": "^2.1.1", - "depd": "^1.0.0", - "inflection": "^1.6.0", - "loopback-connector": "^2.1.0", - "minimatch": "^3.0.3", - "qs": "^6.5.0", - "shortid": "^2.2.6", - "strong-globalize": "^2.6.2", - "traverse": "^0.6.6", - "uuid": "^3.0.1" - }, - "dependencies": { - "async": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", - "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", - "dev": true - }, - "bl": { - "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "loopback-connector": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-2.7.1.tgz", - "integrity": "sha1-YK/CROMZGZdjkamXg4aFeB+ijlI=", - "dev": true, - "requires": { - "async": "^1.0.0", - "bluebird": "^3.4.6", - "debug": "^2.2.0", - "msgpack5": "^3.4.1", - "strong-globalize": "^2.5.8" - } - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "msgpack5": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-3.6.0.tgz", - "integrity": "sha512-6HuCZHA57WtNUzrKIvjJ8OMxigzveJ6D5i13y6TsgGu3X3zxABpuBvChpppOoGdB9SyWZcmqUs1fwUV/PpSQ7Q==", - "dev": true, - "requires": { - "bl": "^1.2.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.3", - "safe-buffer": "^5.1.1" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "strong-globalize": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-2.10.0.tgz", - "integrity": "sha512-g2nNtA6YKBDXhIe6TC/b0lInge8WxcAlFss9OKNGiUHUlOkhIdBHn9AGMLVbKyfI9T8ijEBATcwFIPayWUpOdQ==", - "dev": true, - "requires": { - "async": "^1.5.2", - "debug": "^3.1.0", - "esprima": "^4.0.0", - "estraverse": "^4.2.0", - "g11n-pipeline": "^2.0.1", - "htmlparser2": "^3.9.0", - "lodash": "^4.15.0", - "md5": "^2.0.0", - "mkdirp": "^0.5.1", - "mktmpdir": "^0.1.1", - "optional": "^0.1.3", - "os-locale": "^2.1.0", - "posix-getopt": "^1.2.0", - "word-count": "^0.2.1", - "xtend": "^4.0.1", - "yamljs": "^0.3.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "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" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - } - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "map-age-cleaner": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", - "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", - "requires": { - "p-defer": "^1.0.0" - } - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, - "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" - } - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", - "dev": true - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "dev": true, - "requires": { - "mime-db": "~1.37.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "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": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mktmpdir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/mktmpdir/-/mktmpdir-0.1.1.tgz", - "integrity": "sha1-OKyCCVDXjvoLnN38A/99XFp4bbk=", - "dev": true, - "requires": { - "rimraf": "~2.2.8" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "mocha": { - "version": "1.21.5", - "resolved": "http://registry.npmjs.org/mocha/-/mocha-1.21.5.tgz", - "integrity": "sha1-fFiwkXTfl25DSiOx6NY5hz/FKek=", - "dev": true, - "requires": { - "commander": "2.3.0", - "debug": "2.0.0", - "diff": "1.0.8", - "escape-string-regexp": "1.0.2", - "glob": "3.2.3", - "growl": "1.8.1", - "jade": "0.26.3", - "mkdirp": "0.5.0" - }, - "dependencies": { - "debug": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.0.0.tgz", - "integrity": "sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=", - "dev": true, - "requires": { - "ms": "0.6.2" - } - }, - "escape-string-regexp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", - "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", - "dev": true - }, - "glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", - "dev": true, - "requires": { - "graceful-fs": "~2.0.0", - "inherits": "2", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "mkdirp": { - "version": "0.5.0", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "0.6.2", - "resolved": "http://registry.npmjs.org/ms/-/ms-0.6.2.tgz", - "integrity": "sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=", - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "msgpack5": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz", - "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==", - "requires": { - "bl": "^2.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.6", - "safe-buffer": "^5.1.2" - } - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "nanoid": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.3.tgz", - "integrity": "sha512-07OUEbP7fMX/tFLP3oIa3yTt+sUfDQf99JULSKc/ZNERIVG8T87S+Kt9iu6N4efVzmeMvlXjVUUQcEXKEm0OCQ==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^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=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optional": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", - "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==", - "dev": true - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "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-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", - "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "posix-getopt": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/posix-getopt/-/posix-getopt-1.2.0.tgz", - "integrity": "sha1-Su7rfa3mb8qKk2XdqfawBXQctiE=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "progress": { - "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-browser": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/querystring-browser/-/querystring-browser-1.0.4.tgz", - "integrity": "sha1-8uNYgYQKgZvHsb9Zf68JeeZiLcY=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "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" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "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==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shelljs": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", - "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=", - "dev": true - }, - "shortid": { - "version": "2.2.13", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.13.tgz", - "integrity": "sha512-dBuNnQGKrJNfjunmXI2X7bl1gnMO4PwbNxrTzO1JvilODmL7WyyCtA+DYxe9XunLXmxmgzFIvKPQ6XRAQrr46Q==", - "dev": true, - "requires": { - "nanoid": "^1.0.7" - } - }, - "should": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/should/-/should-5.2.0.tgz", - "integrity": "sha1-mkUZtEe4te7c6e7ZavNCDUUaVAs=", - "dev": true, - "requires": { - "should-equal": "0.3.1", - "should-format": "0.0.7", - "should-type": "0.0.4" - } - }, - "should-equal": { - "version": "0.3.1", - "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-0.3.1.tgz", - "integrity": "sha1-vY6pemdI45+tR2o75v1y68LnK/A=", - "dev": true, - "requires": { - "should-type": "0.0.4" - } - }, - "should-format": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.0.7.tgz", - "integrity": "sha1-Hi74a9kdqcLgQSM1tWq6vZov3hI=", - "dev": true, - "requires": { - "should-type": "0.0.4" - } - }, - "should-type": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.0.4.tgz", - "integrity": "sha1-ATKgVBemEmhmQmrPEW8e1WI6XNA=", - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "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" - } - }, - "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" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", - "dev": true - }, - "strong-globalize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.2.tgz", - "integrity": "sha512-2ks3/fuQy4B/AQDTAaEvTXYSqH4TWrv9VGlbZ4YujzijEJbIWbptF/9dO13duv87aRhWdM5ABEiTy7ZmnmBhdQ==", - "requires": { - "accept-language": "^3.0.18", - "debug": "^4.0.1", - "globalize": "^1.3.0", - "lodash": "^4.17.4", - "md5": "^2.2.1", - "mkdirp": "^0.5.1", - "os-locale": "^3.0.1", - "yamljs": "^0.3.0" - }, - "dependencies": { - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "swagger-client": { - "version": "3.8.21", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.8.21.tgz", - "integrity": "sha512-J6zjWDVRh4Ce74wIkwmHIVIrb3PXAlEsGnyH0ZoZnkyjE0gvaRh9GjE+NFNgFUHg3DXjER9Vqx2/Rjundu0WIg==", - "dev": true, - "requires": { - "@kyleshockey/js-yaml": "^1.0.1", - "@kyleshockey/object-assign-deep": "^0.4.0", - "babel-runtime": "^6.26.0", - "btoa": "1.1.2", - "buffer": "^5.1.0", - "cookie": "^0.3.1", - "cross-fetch": "0.0.8", - "deep-extend": "^0.5.1", - "encode-3986": "^1.0.0", - "fast-json-patch": "^2.0.6", - "isomorphic-form-data": "0.0.1", - "lodash": "^4.16.2", - "qs": "^6.3.0", - "querystring-browser": "^1.0.4", - "url": "^0.11.0", - "utf8-bytes": "0.0.1", - "utfstring": "^2.0.0" - }, - "dependencies": { - "@kyleshockey/js-yaml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@kyleshockey/js-yaml/-/js-yaml-1.0.1.tgz", - "integrity": "sha512-coFyIk1LvTscq1cUU4nCCfYwv+cmG4fCP+wgDKgYZjhM4f++YwZy+g0k+1tUqa4GuUpBTEOGH2KUqKFFWdT73g==", - "dev": true, - "requires": { - "argparse": "^1.0.7" - } - } - } - }, - "table": { - "version": "3.8.3", - "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "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" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "underscore.string": { - "version": "2.2.1", - "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "utf8-bytes": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/utf8-bytes/-/utf8-bytes-0.0.1.tgz", - "integrity": "sha1-EWsCVEjJtQAIHN+/H01sbDfYg30=", - "dev": true - }, - "utfstring": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/utfstring/-/utfstring-2.0.0.tgz", - "integrity": "sha512-/ugBfyvIoLe9xqkFHio3CxXnpUKQ1p2LfTxPr6QTRj6GiwpHo73YGdh03UmAzDQNOWpNIE0J5nLss00L4xlWgg==", - "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": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "word-count": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.2.2.tgz", - "integrity": "sha1-aZGS/KaCn+k21Byw2V25JIxXBFE=", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", - "requires": { - "argparse": "^1.0.7", - "glob": "^7.0.5" - } - } - } -} diff --git a/package.json b/package.json index 82341f1..d851bdd 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,19 @@ { "name": "loopback-connector-esv6", - "version": "1.3.2", - "description": "LoopBack Connector for Elasticsearch 6.x", + "version": "2.0.0", + "description": "LoopBack Connector for Elasticsearch 6.x and 7.x", "main": "index.js", "scripts": { - "eslint": "eslint", - "pretest": "eslint **/*.js", + "lint": "./node_modules/.bin/eslint .", "test": "mocha --recursive", "testv6": "mocha test/es-v6/**/*.js" }, + "husky": { + "hooks": { + "pre-commit": "npm run lint", + "pre-push": "npm run lint" + } + }, "keywords": [ "loopback", "elastic", @@ -16,10 +21,11 @@ "elasticsearchv6", "es", "esv6", + "esv7", "loopback-connector", "connector" ], - "author": "Drakerian", + "author": "bharathkontham", "contributors": [ { "name": "Pulkit Singhal", @@ -45,34 +51,40 @@ "type": "git", "url": "https://github.com/strongloop-community/loopback-connector-elastic-search.git" }, - "license": { - "name": "MIT", - "url": "https://github.com/strongloop-community/loopback-connector-elastic-search/blob/master/LICENCE" - }, + "license": "MIT", "bugs": { "url": "https://github.com/strongloop-community/loopback-connector-elastic-search/issues" }, "homepage": "https://github.com/strongloop-community/loopback-connector-elastic-search", "dependencies": { - "async": "2.6.1", - "bluebird": "2.11.0", + "async": "3.1.0", "debug": "3.2.6", "elasticsearch": "15.2.0", + "es6": "npm:@elastic/elasticsearch@6", + "es7": "npm:@elastic/elasticsearch@7", "lodash": "4.17.11", - "loopback-connector": "4.5.1" + "loopback-connector": "4.9.0", + "ramda": "^0.26.1" }, "directories": { "example": "examples", "test": "test" }, "devDependencies": { - "chai": "^2.1.2", - "eslint": "2.13.1", - "eslint-config-loopback": "4.0.0", "grunt": "^0.4.5", "grunt-mocha-test": "^0.12.1", "loopback-datasource-juggler": "^2.55.3", - "mocha": "^1.21.4", - "should": "^5.2.0" + "should": "^5.2.0", + "chai": "4.1.2", + "eslint": "6.5.1", + "eslint-config-airbnb": "18.0.1", + "eslint-config-loopback": "8.0.0", + "eslint-plugin-import": "2.18.2", + "eslint-plugin-jsx-a11y": "6.2.3", + "eslint-plugin-react": "7.15.0", + "husky": "1.0.1", + "mocha": "5.2.0", + "sonarqube-scanner": "2.5.0", + "supertest": "3.0.0" } } diff --git a/test/es-v1/01.filters.test.js b/test/es-v1/01.filters.test.js deleted file mode 100644 index ae83d57..0000000 --- a/test/es-v1/01.filters.test.js +++ /dev/null @@ -1,725 +0,0 @@ -/*global getSettings getDataSource expect*/ -/*eslint no-console: ["error", { allow: ["trace","log"] }] */ -describe('Connector', function () { - var testConnector; - - before(function () { - require('./init.js'); - var settings = getSettings(); - settings.log = 'error'; - var datasource = getDataSource(settings); - testConnector = datasource.connector; - - datasource.define('MockLoopbackModel', { - // here we want to let elasticsearch auto-populate a field that will be mapped back to loopback as the `id` - id: {type: String, generated: true, id: true} - }); - }); - - it('should configure defaults when building filters', function (done) { - var modelName = 'MockLoopbackModel'; - var defaults = testConnector.addDefaults(modelName); - - expect(defaults.index).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - expect(defaults.type).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - - done(); - }); - - it('should build a query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'title': 'Futuro' - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { // c. here we are testing the bigger picture `should build a query for the WHERE filter` - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should use a NATIVE filter query as-is', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'native': { - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a simple "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - name: 'John Lennon' - } - }, - { - match: { - role: 'lead' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a complex "and" query with "inq" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {inq:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "or" and "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - or: [ - { and: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - should: [ - { - bool: { - must: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "and" and "or" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - { or: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - should: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'id': { - 'nin': [0, 1, 2] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {nin:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - order: { - between: [3, 6] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 3, - lte: 6 - } - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {order: {between:[2,6] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 2, - lte: 6 - } - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build multiple normal where clause query without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: 'lead', - vip: true - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - role: 'lead' - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build two "inq" and one "between" without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: {inq: ['lead']}, - order: {between: [1,6]}, - id: {inq: [2,3,4,5]} - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - role: [ - 'lead' - ] - } - }, - { - range: { - order: { - gte: 1, - lte: 6 - } - } - }, - { - terms: { - _id: [ - 2, - 3, - 4, - 5 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); -}); \ No newline at end of file diff --git a/test/es-v1/02.basic-querying.test.js b/test/es-v1/02.basic-querying.test.js deleted file mode 100644 index 4fca53e..0000000 --- a/test/es-v1/02.basic-querying.test.js +++ /dev/null @@ -1,1849 +0,0 @@ -require('./init.js'); -var async = require('async'); -var logger = require('debug')('test:es-v1:02.basic-querying.test.js'); -var db, User, Customer, AccessToken, Post, PostWithId, Category, SubCategory; - -/*eslint no-console: "off"*/ -/*global getSchema should*/ -describe('basic-querying', function () { - - this.timeout(30000); - - before(function (done) { - - // turn on additional logging - /*process.env.DEBUG += ',loopback:connector:*'; - console.log('process.env.DEBUG: ' + process.env.DEBUG);*/ - - db = getSchema(); - User = db.define('User', { - seq: {type: Number, index: true, id: true}, - name: {type: String, index: true, sort: true}, - email: {type: String, index: true}, - birthday: {type: Date, index: true}, - role: {type: String, index: true}, - order: {type: Number, index: true, sort: true}, - vip: {type: Boolean} - }); - - Customer = db.define('Customer', - { - objectId: {type: String, id: true, generated: false}, - name: {type: String, index: true, sort: true}, - email: {type: String, index: true}, - birthday: {type: Date, index: true}, - role: {type: String, index: true}, - order: {type: Number, index: true, sort: true}, - vip: {type: Boolean} - }/*, - { - // NOTE: overriding by specifying "datasource specific options" is possible - // but not recommended for index and type because the timing for setting them up - // becomes tricky. It is better to provide them in the `mappings` property - // of datasource..json file - elasticsearch: { - index: 'juju', - type: 'consumer' // could set override here - } - }*/ - ); - - AccessToken = db.define('AccessToken', { - ttl: { - type: Number, - ttl: true, - default: 1209600, - description: "time to live in seconds (2 weeks by default)" - }, - created: { - type: Date - } - }); - - Post = db.define('Post', { - title: {type: String, length: 255}, - content: {type: String}, - comments: [String] - }, { - elasticsearch: { - type: 'PostCollection' // Customize the collection name - }, - forceId: false - }); - - PostWithId = db.define('PostWithId', { - id: {type: String}, - title: {type: String, length: 255}, - content: {type: String} - }); - - Category = db.define('Category', { - category_name: {type: String, index: true, sort: true}, - desc: {type: String, length: 100} - }); - - SubCategory = db.define('SubCategory', { - subcategory_name: {type: String} - }); - - Category.embedsMany(SubCategory, { - options: { - "validate": true, - "forceId": false, - "persistent": true - } - }); - - User.hasMany(Post); - Post.belongsTo(User); - - //TODO: add tests for a model where type doesn't match its name - // Added few test for model Post with type name as PostCollection in `save` block test cases. - - setTimeout(function () { - // no big reason to delay this ... - // just want to give the feel that getSchema and automigrate are sequential actions - db.automigrate(done); - }, 6000); - - }); - - describe('ping', function () { - it('should be able to test connections', function (done) { - db.ping(function (err) { - should.not.exist(err); - done(); - }); - }); - }); - - describe('for a model with string IDs', function () { - - beforeEach(seedCustomers); - - it('should work for findById', function (done) { - this.timeout(4000); - setTimeout(function () { - Customer.findById('aaa', function (err, customer) { - should.exist(customer); - should.not.exist(err); - logger(customer); - done(); - }); - }, 2000); - }); - - it('should work for updateAttributes', function (done) { - this.timeout(6000); - setTimeout(function () { - var updateAttrs = {newField: 1, order: 999}; - Customer.findById('aaa', function (err, customer) { - should.not.exist(err); - should.exist(customer); - should.exist(customer.order); - should.not.exist(customer.newField); - customer.updateAttributes(updateAttrs, function (err, updatedCustomer) { - should.not.exist(err); - should.exist(updatedCustomer); - should.exist(updatedCustomer.order); - updatedCustomer.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(updatedCustomer.newField); - updatedCustomer.newField.should.equal(updateAttrs.newField); - setTimeout(function () { - Customer.findById('aaa', function (err, customerFetchedAgain) { - should.not.exist(err); - should.exist(customerFetchedAgain); - should.exist(customerFetchedAgain.order); - customerFetchedAgain.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(customerFetchedAgain.newField); - customerFetchedAgain.newField.should.equal(updateAttrs.newField); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - }); - - describe('findById', function () { - - before(function (done) { - User.destroyAll(done); - }); - - it('should query by id: not found', function (done) { - // TODO: wait a few seconds for the Users to be destroyed? near-real-time != real-time - User.findById(1, function (err, u) { - should.not.exist(u); - should.not.exist(err); - done(); - }); - }); - - it('should query by id: found', function (done) { - this.timeout(4000); - User.create(function (err, u) { - should.not.exist(err); - should.exist(u.id); - setTimeout(function () { - User.findById(u.id, function (err, u) { - logger('err: ', err); - logger('user: ', u); - should.exist(u); - should.not.exist(err); - u.should.be.an.instanceOf(User); - done(); - }); - }, 2000); - }); - }); - - }); - - describe('custom', function () { - - it('suggests query should work', function (done) { - User.all({ - suggest: { - title_suggester: { - text: 'd', - term: { - field: 'name' - } - } - } - }, function (err/*, u*/) { - //should.exist(u); - should.not.exist(err); - done(); - }); - }); - - it('native query should work', function (done) { - User.all({ - native: { - query: { - 'match_all': {} - } - } - }, function (err, u) { - should.exist(u); - should.not.exist(err); - done(); - }); - }); - }); - - // TODO: Resolve the discussion around: https://support.strongloop.com/requests/676 - describe('findByIds', function () { - var createdUsers; - before(function (done) { - this.timeout(4000); - var people = [ - {seq: 1, name: 'a', vip: true}, - {seq: 2, name: 'b'}, - {seq: 3, name: 'c'}, - {seq: 4, name: 'd', vip: true}, - {seq: 5, name: 'e'}, - {seq: 6, name: 'f'} - ]; - db.automigrate(['User'], function (err) { - should.not.exist(err); - User.create(people, function (err, users) { - should.not.exist(err); - // Users might be created in parallel and the generated ids can be - // out of sequence - createdUsers = users; - done(); - }); - }); - }); - - it('should query by ids', function (done) { - this.timeout(4000); - setTimeout(function () { - User.findByIds( - [createdUsers[2].id, createdUsers[1].id, createdUsers[0].id], - function (err, users) { - should.exist(users); - should.not.exist(err); - var names = users.map(function (u) { - return u.name; - }); - - // TODO: 1. find code that tries to add sort order and tell it not to do so - // because findByIds isn't meant to work like that - // 2. can get clues from how mongo connector tracks the calling - // method name to accomplish the same thing - - // TODO: Resolve the discussion around: https://support.strongloop.com/requests/676 - /** - * 1) find() by default sorts by id property. - * 2) findByIds() expects the results sorted by the ids as they are passed in the argument. - * i) Connector.prototype.all() should NOT deal with the rules for findByIds() - * as the sorting for findByIds() is done after the connector returned an array of objects. - * ii) Here is how findByIds() implemented: - * i) Build a query with inq for ids from the arg - * ii) Call Model.find() (no ordering is set, connectors will default it to id) - * iii) Sort the results by the order of ids in the arg - * - */ - /*names.should.eql( // NOTE: order doesn't add up, is 2.ii.iii broken? - [createdUsers[2].name, createdUsers[1].name, createdUsers[0].name]);*/ - - // temporary workaround to help tests pass - names.should.include(createdUsers[2].name); - names.should.include(createdUsers[1].name); - names.should.include(createdUsers[0].name); - done(); - }); - }, 2000); - }); - - it('should query by ids and condition', function (done) { - this.timeout(4000); - setTimeout(function () { - User.findByIds([ - createdUsers[0].id, - createdUsers[1].id, - createdUsers[2].id, - createdUsers[3].id], // this helps test "inq" - {where: {vip: true}}, function (err, users) { - should.exist(users); - should.not.exist(err); - var names = users.map(function (u) { - return u.name; - }); - names.should.eql(createdUsers.slice(0, 4).filter(function (u) { - return u.vip; - }).map(function (u) { - return u.name; - })); - done(); - }); - }, 2000); - }); - - }); - - describe('sanity test IDs', function () { - - before(function (done) { - User.destroyAll(done); - }); - - it('should auto generate an id', function (done) { - this.timeout(4000); - User.create(function (err, u) { - should.not.exist(err); - should.exist(u.id); - should.exist(u.seq); - done(); - }); - }); - - it('should use specified id', function (done) { - this.timeout(4000); - User.create({seq: 666}, function (err, u) { - should.not.exist(err); - should.exist(u.id); - should.exist(u.seq); - u.id.should.equal('666'); - u.seq.should.equal('666'); - done(); - }); - }); - - after(function (done) { - db.automigrate(done); - }); - }); - - describe('find', function () { - - before(seed); - - it('should query collection', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.find(function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - }, 2000); - }); - - it('should query limited collection', function (done) { - User.find({limit: 3}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - }); - - it('should query ordered collection with skip & limit', function (done) { - User.find({skip: 1, limit: 4, order: 'seq'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users[0].seq.should.be.eql(1); - users.should.have.lengthOf(4); - done(); - }); - }); - - it('should query ordered collection with offset & limit', function (done) { - User.find({offset: 2, limit: 3, order: 'seq'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users[0].seq.should.be.eql(2); - users.should.have.lengthOf(3); - done(); - }); - }); - - it('should query filtered collection', function (done) { - setTimeout(function () { - User.find({where: {role: 'lead'}}, function (err, users) { - logger('users',users); - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(2); - done(); - }); - },2000); - - }); - - it('should query collection sorted by numeric field', function (done) { - User.find({order: 'order'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.forEach(function (u, i) { - u.order.should.eql(i + 1); - }); - done(); - }); - }); - - it('should query collection desc sorted by numeric field', function (done) { - User.find({order: 'order DESC'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.forEach(function (u, i) { - u.order.should.eql(users.length - i); - }); - done(); - }); - }); - - it('should query collection sorted by string field', function (done) { - User.find({order: 'name'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.shift().name.should.equal('George Harrison'); - users.shift().name.should.equal('John Lennon'); - users.pop().name.should.equal('Stuart Sutcliffe'); - done(); - }); - }); - - it('should query collection desc sorted by string field', function (done) { - User.find({order: 'name DESC'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.pop().name.should.equal('George Harrison'); - users.pop().name.should.equal('John Lennon'); - users.shift().name.should.equal('Stuart Sutcliffe'); - done(); - }); - }); - - it('should support "and" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "and" with "inq" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {seq: {inq:[] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - },2000); - }); - - it('should support "or" with nested "and" using "inq" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - or: [ - { and: [{seq: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }, function (err, users) { - should.not.exist(err); - should.exist(users); - users.should.have.property('length', 3); - done(); - }); - },2000); - }); - - /** - * Testing if es can support nested queries which the loopback ORM can. - * where: {or: [{ and: [{seq: {inq:[3,4,5] }}, { vip: true }] },{ role: 'lead' }]} - */ - it('should support "or" with nested "and" using "inq" operator that is satisfied with native query', function (done) { - setTimeout(function () { - User.find({ - native: { - 'query': { - 'bool': { - 'should': [ - { - 'bool': { - 'must': [ - { - 'terms': { - '_id': [ - 3, - 4, - 5 - ] - } - }, - { - 'match': { - 'vip': true - } - } - ] - } - } - ] - } - } - } - }, function (err, users) { - should.not.exist(err); - should.exist(users); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "and" operator that is not satisfied', function (done) { - User.find({ - where: { - and: [ - {name: 'John Lennon'}, - {role: 'member'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support "or" that is satisfied', function (done) { - User.find({ - where: { - or: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - done(); - }); - }); - - it('should support "or" operator that is not satisfied', function (done) { - User.find({ - where: { - or: [ - {name: 'XYZ'}, - {role: 'Hello1'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support date "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gte": new Date('1980-12-08')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support date "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gt": new Date('1980-12-08')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support date "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gt": new Date('1980-12-07')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support date "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"lt": new Date('1980-12-07')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should support number "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gte": 3} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 4); - users[0].name.should.equal('George Harrison'); - done(); - }); - }); - - it('should support number "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": 6} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support number "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": 5} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should support number "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"lt": 2} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - xit('should support number "gt" that is satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - xit('should support number "lt" that is not satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - order: {"lt": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - xit('should support string "gte" that is satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - name: {"gte": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support string "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gte": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 4); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should support string "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gt": 'xyz'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support string "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gt": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should support string "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"lt": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gte": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gt": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support boolean "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gt": false} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"lt": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - users[0].name.should.equal('George Harrison'); - done(); - }); - }); - - it('should support "inq" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {inq: [0,1,2] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - },2000); - }); - - it('should support "inq" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {inq: [] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(0); - done(); - }); - },2000); - }); - - it('should support "nin" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {nin: [0,1,2] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - },2000); - }); - - it('should support "nin" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {nin: [] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support "and" with "nin" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {seq: {nin:[0,1,2] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "between" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {order: {between: [3,6] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(4); - done(); - }); - },2000); - }); - - it('should support "between" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {order: {between: [3,1] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support "and" with "between" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {order: {between:[2,6] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {name: {neq: 'John Lennon' }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(5); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is satisfied with undefined property', function (done) { - setTimeout(function () { - User.find({where: {role: {neq: 'lead' }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(4); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {role: {neq: ''}}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support multiple comma separated property filter without "and" that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {role: 'lead', vip: true}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(2); - done(); - }); - },2000); - }); - - it('should support multiple comma separated "inq" without "and" filter that is satisfied', function (done) { - setTimeout(function () { - User.find( - { - where: { - role: {inq: ['lead']}, - seq: {inq: [0,2,3,4,5]} - } - }, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(1); - done(); - }); - },2000); - }); - - it('should support two "inq" and one "between" without "and" filter that is satisfied', function (done) { - setTimeout(function () { - User.find( - { - where: { - role: {inq: ['lead']}, - order: {between: [1,6]}, - seq: {inq: [2,3,4,5]} - } - }, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(0); - done(); - }); - },2000); - }); - }); - - // TODO: there is no way for us to test the connector code explicitly - // if the underlying juggler performs the same work as well! - // https://support.strongloop.com/requests/679 - // https://github.com/strongloop-community/loopback-connector-elastic-search/issues/5 - describe('find', function () { - - before(seed); - - it('should only include fields as specified', function (done) { - this.timeout(30000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var remaining = 0; - - function sample(fields) { - logger('expect: ', fields); - return { - expect: function (arr) { - remaining++; - User.find({fields: fields}, function (err, users) { - - remaining--; - if (err) { - return done(err); - } - - should.exist(users); - logger(JSON.stringify(users, null, 2)); - - if (remaining === 0) { - done(); - } - - users.forEach(function (user) { - var obj = user.toObject(); - Object.keys(obj) - .forEach(function (key) { - // if the obj has an unexpected value - if (obj[key] !== undefined && arr.indexOf(key) === -1) { - throw new Error('should not include data for key: ' + key); - } - }); - }); - }); - } - }; - } - - sample({email: false}).expect(['id', 'seq', 'name', 'role', 'order', 'birthday', 'vip']); - /*sample({name: true}).expect(['name']); - sample({name: false}).expect(['id', 'seq', 'email', 'role', 'order', 'birthday', 'vip']); - sample({name: false, id: true}).expect(['id']); - sample({id: true}).expect(['id']); - sample('id').expect(['id']); - sample(['id']).expect(['id']); - sample(['email']).expect(['email']);*/ - }, 2000); - }); - - }); - - describe('count', function () { - - before(seed); - - it('should query total count', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.count(function (err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(6); - done(); - }); - }, 2000); - }); - - it('should query filtered count', function (done) { - User.count({role: 'lead'}, function (err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(2); - done(); - }); - }); - }); - - describe('findOne', function () { - - before(seed); - - it('should find first record (default sort by id)', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.all({order: 'id'}, function (err, users) { - User.findOne(function (e, u) { - should.not.exist(e); - should.exist(u); - // NOTE: if `id: true` is not set explicitly when defining a model, there will be trouble! - u.id.toString().should.equal(users[0].id.toString()); - done(); - }); - }); - }, 2000); - }); - - it('should find first record', function (done) { - User.findOne({order: 'order'}, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(1); - u.name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should find last record', function (done) { - User.findOne({order: 'order DESC'}, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(6); - u.name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should find last record in filtered set', function (done) { - User.findOne({ - where: {role: 'lead'}, - order: 'order DESC' - }, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(2); - u.name.should.equal('John Lennon'); - done(); - }); - }); - - it('should work even when find by id', function (done) { - User.findOne(function (e, u) { - //logger(JSON.stringify(u)); - // ESConnector.prototype.all +0ms model User filter {"where":{},"limit":1,"offset":0,"skip":0} - /* - * Ideally, instead of always generating: - * filter {"where":{"id":0},"limit":1,"offset":0,"skip":0} - * the id-literal should be replaced with the actual idName by loopback's core: - * filter {"where":{"seq":0},"limit":1,"offset":0,"skip":0} - * in my opinion. - */ - User.findOne({where: {id: u.id}}, function (err, user) { - should.not.exist(err); - should.exist(user); - done(); - }); - }); - }); - - }); - - describe('exists', function () { - - before(seed); - - it('should check whether record exist', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.findOne(function (e, u) { - User.exists(u.id, function (err, exists) { - should.not.exist(err); - should.exist(exists); - exists.should.be.ok; - done(); - }); - }); - }, 2000); - }); - - it('should check whether record not exist', function (done) { - User.destroyAll(function () { - User.exists(42, function (err, exists) { - should.not.exist(err); - exists.should.not.be.ok; - done(); - }); - }); - }); - - }); - - describe('destroyAll with where option', function () { - - before(seed); - - it('should only delete instances that satisfy the where condition', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.destroyAll({name: 'John Lennon'}, function () { - setTimeout(function () { - User.find({where: {name: 'John Lennon'}}, function (err, data) { - should.not.exist(err); - data.length.should.equal(0); - User.find({where: {name: 'Paul McCartney'}}, function (err, data) { - should.not.exist(err); - data.length.should.equal(1); - done(); - }); - }); - }, 2000); - }); - }, 2000); - }); - - }); - - describe('updateOrCreate', function () { - - beforeEach(seed); - - it('should update existing model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var beatle = {seq: 1, rating: 5}; - User.updateOrCreate(beatle, function (err, instance) { - should.not.exist(err); - should.exist(instance); - //instance.should.eql(beatle); - setTimeout(function () { - User.find({where: {seq: 1}}, function (err, data) { - should.not.exist(err); - //data.length.should.equal(0); - logger(data); - data[0].rating.should.equal(beatle.rating); - done(); - }); - }, 2000); - }); - }, 2000); - }); - - it('should create a new model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var beatlesFan = {seq: 6, name: 'Pulkit Singhal', order: 7, vip: false}; - User.updateOrCreate(beatlesFan, function (err, instance) { - should.not.exist(err); - should.exist(instance); - should.exist(instance.id); - should.exist(instance.seq); - setTimeout(function () { - User.find({where: {seq: instance.seq}}, function (err, data) { - should.not.exist(err); - data[0].seq.should.equal(beatlesFan.seq); - data[0].name.should.equal(beatlesFan.name); - data[0].order.should.equal(beatlesFan.order); - data[0].vip.should.equal(beatlesFan.vip); - done(); - }); - }, 2000); - }); - }, 2000); - }); - }); - - describe('updateAttributes', function () { - - beforeEach(seed); - - it('should update existing model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var updateAttrs = {newField: 1, order: 999}; - User.findById(1, function (err, user) { - should.not.exist(err); - should.exist(user); - //user.id.should.equal(1); - //user.seq.should.equal(1); - should.exist(user.order); - should.not.exist(user.newField); - user.updateAttributes(updateAttrs, function (err, updatedUser) { - should.not.exist(err); - should.exist(updatedUser); - should.exist(updatedUser.order); - updatedUser.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(updatedUser.newField); - updatedUser.newField.should.equal(updateAttrs.newField); - setTimeout(function () { - User.findById(1, function (err, userFetchedAgain) { - logger('333'); - should.not.exist(err); - should.exist(userFetchedAgain); - should.exist(userFetchedAgain.order); - userFetchedAgain.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(userFetchedAgain.newField); - userFetchedAgain.newField.should.equal(updateAttrs.newField); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - - }); - - describe('all', function () { - - before(destroyAccessTokens); - - it('should convert date type fields from string to javascript date object when fetched', function (done) { - this.timeout(4000); - AccessToken.create({ttl: 1209600, created: '2017-01-10T12:12:38.600Z'}, function (err, token) { - should.not.exist(err); - should.exist(token.id); - setTimeout(function () { - AccessToken.findById(token.id, function (err, tokenInstance) { - should.not.exist(err); - should.exist(tokenInstance); - tokenInstance.should.be.an.instanceOf(AccessToken); - tokenInstance.created.should.be.an.instanceOf(Date); - done(); - }); - }, 2000); - }); - }); - - describe('embedsMany relations', function () { - - before(function (done) { - this.timeout = 4000; - Category.destroyAll(function () { - SubCategory.destroyAll(function () { - db.automigrate(['Category'], done); - }); - }); - }); - - it('should create embeded models and return embeded data using findById', function (done) { - this.timeout = 6000; - var category = {category_name: 'Apparels', desc: 'This is a category for apparels'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Jeans'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Jeans'); - setTimeout(function () { - Category.findById(ct.id, function (err, found) { - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Apparels'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Jeans'); - done(); - }); - },2000); - }); - },2000); - }); - }); - - it('should create multiple embeded models and return proper data using findById', function (done) { - this.timeout = 6000; - var category = {category_name: 'Electronics', desc: 'This is a category for electronics'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Mobiles'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Mobiles'); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Laptops'}, function (err, data) { - should.not.exist(err); - should.exist(data.id); - expect(data.subcategory_name).to.equal('Laptops'); - setTimeout(function () { - Category.findById(ct.id, function (err, found) { - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Electronics'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Mobiles'); - expect(found).to.have.deep.property('subCategories[1].subcategory_name','Laptops'); - done(); - }); - },2000); - }); - },2000); - }); - },2000); - }); - }); - - it('should create embeded models and return embeded data using find', function (done) { - this.timeout = 6000; - var category = {category_name: 'Footwear', desc: 'This is a category for footwear'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Sandals'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Sandals'); - setTimeout(function () { - Category.find({where: {category_name: 'Footwear'}}, function (err, found) { - found = found[0]; - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Footwear'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Sandals'); - done(); - }); - },2000); - }); - },2000); - }); - }); - }); - - describe('hasMany relations', function () { - - beforeEach(function (done) { - this.timeout = 4000; - User.destroyAll(function () { - Post.destroyAll(function () { - db.automigrate(['User'], done); - }); - }); - }); - - it('should create related model and check if include filter works', function (done) { - this.timeout = 6000; - var Kamal = { - seq: 0, - name: 'Kamal Khatwani', - email: 'kamal@shoppinpal.com', - role: 'lead', - birthday: new Date('1993-12-08'), - order: 2, - vip: true - }; - - User.create(Kamal, function (err, kamal) { - should.not.exist(err); - should.exist(kamal.id); - should.exist(kamal.seq); - var kamals_post_1 = { - title: 'Kamal New Post', - content: 'First post of kamal khatwani on elasticsearch', - comments: ['First Comment'] - }; - setTimeout(function () { - kamal.posts.create(kamals_post_1, function (err, firstPost) { - should.not.exist(err); - should.exist(firstPost.id); - expect(firstPost.userId).to.equal(0); - setTimeout(function () { - User.find({include: 'posts'}, function (err, userFound) { - userFound = userFound[0].toJSON(); - should.not.exist(err); - should.exist(userFound.posts); - expect(userFound.posts).to.be.instanceOf(Array); - expect(userFound.posts.length).to.equal(1); - done(); - }); - },2000); - }); - },2000); - }); - }); - - it('should create related model and check include filter with specific fields', function (done) { - this.timeout = 6000; - var Kamal = { - seq: 0, - name: 'Kamal Khatwani', - email: 'kamal@shoppinpal.com', - role: 'lead', - birthday: new Date('1993-12-08'), - order: 2, - vip: true - }; - - User.create(Kamal, function (err, kamal) { - should.not.exist(err); - should.exist(kamal.id); - should.exist(kamal.seq); - var kamals_post_1 = { - title: 'Kamal New Post', - content: 'First post of kamal khatwani on elasticsearch', - comments: ['First Comment'] - }; - setTimeout(function () { - kamal.posts.create(kamals_post_1, function (err, firstPost) { - should.not.exist(err); - should.exist(firstPost.id); - expect(firstPost.userId).to.equal(0); - setTimeout(function () { - User.find({include: {relation: 'posts', scope: {fields : ['title']}}}, function (err, userFound) { - userFound = userFound[0].toJSON(); - should.not.exist(err); - should.exist(userFound.posts); - expect(userFound.posts).to.be.instanceOf(Array); - expect(userFound.posts.length).to.equal(1); - expect(userFound.posts[0].title).to.equal('Kamal New Post'); - expect(userFound.posts[0].content).to.equal(undefined); - expect(userFound.posts[0].comments).to.equal(undefined); - done(); - }); - },2000); - }); - },2000); - }); - }); - }); - }); - - describe('save', function () { - - before(destroyPosts); - - it('all return should honor filter.fields, with `_id` as defined id', function (done) { - this.timeout(4000); - - var post = new PostWithId({id:'AAAA' ,title: 'Posts', content: 'all return should honor filter.fields'}); - post.save(function (err, post) { - setTimeout(function () { - PostWithId.all({fields: ['title'], where: {title: 'Posts'}}, function (err, posts) { - should.not.exist(err); - posts.should.have.lengthOf(1); - post = posts[0]; - post.should.have.property('title', 'Posts'); - post.should.have.property('content', undefined); - should.not.exist(post._id); - - done(); - }); - }, 2000); - }); - }); - - it('save should not return _id', function (done) { - this.timeout(4000); - - Post.create({title: 'Post1', content: 'Post content'}, function (err, post) { - post.content = 'AAA'; - setTimeout(function () { - post.save(function (err, p) { - should.not.exist(err); - should.not.exist(p._id); - p.id.should.be.equal(post.id); - p.content.should.be.equal('AAA'); - - done(); - }); - }, 2000); - - }); - }); - - it('save should update the instance with the same id', function (done) { - this.timeout(6000); - - Post.create({title: 'a', content: 'AAA'}, function (err, post) { - post.title = 'b'; - delete post.content; - setTimeout(function () { - post.save(function (err, p) { - should.not.exist(err); - p.id.should.be.equal(post.id); - p.content.should.be.equal(post.content); - should.not.exist(p._id); - setTimeout(function () { - Post.findById(post.id, function (err, p) { - p.id.should.be.eql(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal('b'); - done(); - }); - }, 2000); - }); - }, 2000); - }); - }); - - it('save should update the instance without removing existing properties', function (done) { - this.timeout(6000); - - Post.create({ - title: 'a', - content: 'update the instance without removing existing properties' - }, function (err, post) { - delete post.title; - setTimeout(function () { - post.save(function (err, p) { - - should.not.exist(err); - p.id.should.be.equal(post.id); - p.content.should.be.equal(post.content); - should.not.exist(p._id); - setTimeout(function () { - Post.findById(post.id, function (err, p) { - p.id.should.be.eql(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal('a'); - - done(); - }); - }, 2000); - }); - }, 2000); - - }); - }); - - it('save should create a new instance if it does not exist', function (done) { - this.timeout(6000); - - var post = new Post({id: '123', title: 'Create', content: 'create if does not exist'}); - post.save(post, function (err, p) { - should.not.exist(err); - p.title.should.be.equal(post.title); - p.content.should.be.equal(post.content); - p.id.should.be.equal(post.id); - setTimeout(function () { - Post.findById(p.id, function (err, p) { - p.id.should.be.equal(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal(post.title); - p.id.should.be.equal(post.id); - - done(); - }); - }, 2000); - }); - }); - - it('all return should honor filter.fields', function (done) { - this.timeout(4000); - - var post = new Post({title: 'Fields', content: 'all return should honor filter.fields'}); - post.save(function (err, post) { - setTimeout(function () { - Post.all({fields: ['title'], where: {title: 'Fields'}}, function (err, posts) { - should.not.exist(err); - posts.should.have.lengthOf(1); - post = posts[0]; - post.should.have.property('title', 'Fields'); - post.should.have.property('content', undefined); - should.not.exist(post._id); - should.not.exist(post.id); - - done(); - }); - }, 2000); - }); - }); - - }); - - describe('updateAll', function () { - before(seed); - it('should not be available for elasticsearch version 1.x', function (done) { - this.timeout(6000); - - var userToUpdate = { seq: 10, name: 'Aquid Shahwar', email: 'aquid@shoppinpal.com', role: 'lead', - birthday: new Date('1992-09-21'), order: 11, vip: true - }; - User.create(userToUpdate, function (err, user) { - should.not.exist(err); - should.exist(user); - setTimeout(function () { - expect(function(){ - User.updateAll({seq: user.seq},{order: 10}); - }).to.throw(TypeError); - done(); - },2000); - }); - }) - - }); - - xdescribe('test id fallback when `generated:false`', function () { - - it('should auto generate an id', function (done) { - this.timeout(8000); - Customer.create({name: 'George Harrison', vip: false}, function (err, u) { - logger('user after create', u); - should.not.exist(err); - should.exist(u.id); - should.exist(u.objectId); - setTimeout(function () { - Customer.findById(u.objectId, function (err, u) { - logger('customer after first findById', u); - u.save(function (err, savedCustomer) { - logger('user after save', savedCustomer); - setTimeout(function () { - Customer.findById(u.objectId, function (err, foundUser) { - logger('user after findById', foundUser); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - }); - }); - -}); - -function seed() { - this.timeout(4000); - var beatles = [ - { - seq: 0, - name: 'John Lennon', - email: 'john@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1980-12-08'), - order: 2, - vip: true - }, - { - seq: 1, - name: 'Paul McCartney', - email: 'paul@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1942-06-18'), - order: 1, - vip: true - }, - {seq: 2, name: 'George Harrison', order: 5, vip: false}, - {seq: 3, name: 'Ringo Starr', order: 6, vip: false}, - {seq: 4, name: 'Pete Best', order: 4}, - {seq: 5, name: 'Stuart Sutcliffe', order: 3, vip: true} - ]; - - return User.destroyAll().then(function() { - return User.create(beatles); - }); -} - -function seedCustomers(done) { - this.timeout(4000); - var customers = [ - { - objectId: 'aaa', - name: 'John Lennon', - email: 'john@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1980-12-08'), - order: 2, - vip: true - }, - { - objectId: 'bbb', - name: 'Paul McCartney', - email: 'paul@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1942-06-18'), - order: 1, - vip: true - }, - {objectId: 'ccc', name: 'George Harrison', order: 5, vip: false}, - {objectId: 'ddd', name: 'Ringo Starr', order: 6, vip: false}, - {objectId: 'eee', name: 'Pete Best', order: 4}, - {objectId: 'fff', name: 'Stuart Sutcliffe', order: 3, vip: true} - ]; - - async.series([ - Customer.destroyAll.bind(Customer), - function (cb) { - setTimeout(function () { - async.each(customers, Customer.create.bind(Customer), cb); - }, 2000); - } - ], done); -} - -function destroyAccessTokens(done) { - this.timeout(4000); - AccessToken.destroyAll.bind(AccessToken); - setTimeout(function () { - done(); - }, 2000); -} - -function destroyPosts(done) { - this.timeout(4000); - Post.destroyAll.bind(Post); - PostWithId.destroyAll.bind(PostWithId); - setTimeout(function () { - done(); - }, 2000) -} diff --git a/test/es-v1/datasource-test-v1-plain.json b/test/es-v1/datasource-test-v1-plain.json deleted file mode 100644 index 12e9d99..0000000 --- a/test/es-v1/datasource-test-v1-plain.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "esv1-plain", - "connector": "elasticsearch", - "index": "shakespeare", - "hosts": [ - { - "protocol": "http", - "host": "localhost", - "port": 9201 - } - ], - "apiVersion": "1.7", - "log": "error", - "defaultSize": 50, - "requestTimeout": 30000, - "mappings": [ - { - "name": "User", - "properties": { - "id": {"type": "string", "index" : "not_analyzed"}, - "seq": {"type": "integer"}, - "name" : { - "type" : "multi_field", - "fields" : { - "name" : {"type" : "string", "index" : "not_analyzed"}, - "native" : {"type" : "string", "index" : "analyzed"} - } - }, - "email": {"type": "string", "index" : "not_analyzed"}, - "birthday": {"type": "date"}, - "role": {"type": "string", "index" : "not_analyzed"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "Customer", - "index": "juju", - "type": "consumer", - "properties": { - "objectId": {"type": "string", "index" : "not_analyzed"}, - "name" : { - "type" : "multi_field", - "fields" : { - "name" : {"type" : "string", "index" : "not_analyzed"}, - "native" : {"type" : "string", "index" : "analyzed"} - } - }, - "email": {"type": "string", "index" : "not_analyzed"}, - "birthday": {"type": "date"}, - "role": {"type": "string", "index" : "not_analyzed"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "AccessToken", - "properties": { - "id": { "type": "string", "index": "not_analyzed" }, - "ttl": { "type": "integer" }, - "created": { "type": "date" } - } - } - ] -} diff --git a/test/es-v1/init.js b/test/es-v1/init.js deleted file mode 100644 index 710b40a..0000000 --- a/test/es-v1/init.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -/** - * Why does this file exist? - * - * Individual tests can load the datasource, and avoid repetition, by adding: - * `require('./init.js');` - * of their source code. - */ - -var chai = require('chai'); -global.expect = chai.expect; -global.assert = chai.assert; -global.should = chai.should(); // Why is the function being executed? Because the "should" interface extends Object.prototype to provide a single getter as the starting point for your language assertions. - -global._ = require('lodash'); /*global _:true*/ - -var settings = require('./datasource-test-v1-plain.json'); -global.getSettings = function() { /*global getSettings*/ - return _.cloneDeep(settings); -}; - -var DataSource = require('loopback-datasource-juggler').DataSource; -global.getDataSource = global.getSchema = global.getConnector = function (customSettings) { - (customSettings) /*eslint no-console: ["error", { allow: ["log"] }] */ - ? console.log('\n\tcustomSettings will override global settings for datasource\n'/*, JSON.stringify(customSettings,null,2)*/) - : console.log('\n\twill use global settings for datasource\n'); - var settings = customSettings || getSettings(); - // settings.log = { - // type: 'file', - // level: 'trace', - // path: 'test/es-v1/elasticsearch-v1-'+Date.now()+'.log' - // }; - //console.log('\n\tsettings:\n', JSON.stringify(settings,null,2)); - settings.connector = require('../../'); - return new DataSource(settings); -}; diff --git a/test/es-v2/01.filters.test.js b/test/es-v2/01.filters.test.js deleted file mode 100644 index ae83d57..0000000 --- a/test/es-v2/01.filters.test.js +++ /dev/null @@ -1,725 +0,0 @@ -/*global getSettings getDataSource expect*/ -/*eslint no-console: ["error", { allow: ["trace","log"] }] */ -describe('Connector', function () { - var testConnector; - - before(function () { - require('./init.js'); - var settings = getSettings(); - settings.log = 'error'; - var datasource = getDataSource(settings); - testConnector = datasource.connector; - - datasource.define('MockLoopbackModel', { - // here we want to let elasticsearch auto-populate a field that will be mapped back to loopback as the `id` - id: {type: String, generated: true, id: true} - }); - }); - - it('should configure defaults when building filters', function (done) { - var modelName = 'MockLoopbackModel'; - var defaults = testConnector.addDefaults(modelName); - - expect(defaults.index).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - expect(defaults.type).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - - done(); - }); - - it('should build a query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'title': 'Futuro' - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { // c. here we are testing the bigger picture `should build a query for the WHERE filter` - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should use a NATIVE filter query as-is', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'native': { - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a simple "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - name: 'John Lennon' - } - }, - { - match: { - role: 'lead' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a complex "and" query with "inq" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {inq:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "or" and "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - or: [ - { and: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - should: [ - { - bool: { - must: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "and" and "or" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - { or: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - should: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'id': { - 'nin': [0, 1, 2] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {nin:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - order: { - between: [3, 6] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 3, - lte: 6 - } - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {order: {between:[2,6] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 2, - lte: 6 - } - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build multiple normal where clause query without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: 'lead', - vip: true - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - role: 'lead' - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build two "inq" and one "between" without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: {inq: ['lead']}, - order: {between: [1,6]}, - id: {inq: [2,3,4,5]} - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - role: [ - 'lead' - ] - } - }, - { - range: { - order: { - gte: 1, - lte: 6 - } - } - }, - { - terms: { - _id: [ - 2, - 3, - 4, - 5 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); -}); \ No newline at end of file diff --git a/test/es-v2/02.basic-querying.test.js b/test/es-v2/02.basic-querying.test.js deleted file mode 100644 index 0b66b7e..0000000 --- a/test/es-v2/02.basic-querying.test.js +++ /dev/null @@ -1,1858 +0,0 @@ -require('./init.js'); -var async = require('async'); -var logger = require('debug')('test:es-v2:02.basic-querying.test.js'); -var db, User, Customer, AccessToken, Post, PostWithId, Category, SubCategory; - -/*eslint no-console: "off"*/ -/*global getSchema should*/ -describe('basic-querying', function () { - - this.timeout(30000); - - before(function (done) { - - // turn on additional logging - /*process.env.DEBUG += ',loopback:connector:*'; - console.log('process.env.DEBUG: ' + process.env.DEBUG);*/ - - db = getSchema(); - User = db.define('User', { - seq: {type: Number, index: true, id: true}, - name: {type: String, index: true, sort: true}, - email: {type: String, index: true}, - birthday: {type: Date, index: true}, - role: {type: String, index: true}, - order: {type: Number, index: true, sort: true}, - vip: {type: Boolean} - }); - - Customer = db.define('Customer', - { - objectId: {type: String, id: true, generated: false}, - name: {type: String, index: true, sort: true}, - email: {type: String, index: true}, - birthday: {type: Date, index: true}, - role: {type: String, index: true}, - order: {type: Number, index: true, sort: true}, - vip: {type: Boolean} - }/*, - { - // NOTE: overriding by specifying "datasource specific options" is possible - // but not recommended for index and type because the timing for setting them up - // becomes tricky. It is better to provide them in the `mappings` property - // of datasource..json file - elasticsearch: { - index: 'juju', - type: 'consumer' // could set override here - } - }*/ - ); - - AccessToken = db.define('AccessToken', { - ttl: { - type: Number, - ttl: true, - default: 1209600, - description: "time to live in seconds (2 weeks by default)" - }, - created: { - type: Date - } - }); - - Post = db.define('Post', { - title: {type: String, length: 255}, - content: {type: String}, - comments: [String] - }, { - elasticsearch: { - type: 'PostCollection' // Customize the collection name - }, - forceId: false - }); - - PostWithId = db.define('PostWithId', { - id: {type: String}, - title: {type: String, length: 255}, - content: {type: String} - }); - - Category = db.define('Category', { - category_name: {type: String, index: true, sort: true}, - desc: {type: String, length: 100} - }); - - SubCategory = db.define('SubCategory', { - subcategory_name: {type: String} - }); - - Category.embedsMany(SubCategory, { - options: { - "validate": true, - "forceId": false, - "persistent": true - } - }); - - User.hasMany(Post); - Post.belongsTo(User); - - //TODO: add tests for a model where type doesn't match its name - // Added few test for model Post with type name as PostCollection in `save` block test cases. - - setTimeout(function () { - // no big reason to delay this ... - // just want to give the feel that getSchema and automigrate are sequential actions - db.automigrate(done); - }, 6000); - - }); - - describe('ping', function () { - it('should be able to test connections', function (done) { - db.ping(function (err) { - should.not.exist(err); - done(); - }); - }); - }); - - describe('for a model with string IDs', function () { - - beforeEach(seedCustomers); - - it('should work for findById', function (done) { - this.timeout(4000); - setTimeout(function () { - Customer.findById('aaa', function (err, customer) { - should.exist(customer); - should.not.exist(err); - logger(customer); - done(); - }); - }, 2000); - }); - - it('should work for updateAttributes', function (done) { - this.timeout(6000); - setTimeout(function () { - var updateAttrs = {newField: 1, order: 999}; - Customer.findById('aaa', function (err, customer) { - should.not.exist(err); - should.exist(customer); - should.exist(customer.order); - should.not.exist(customer.newField); - customer.updateAttributes(updateAttrs, function (err, updatedCustomer) { - should.not.exist(err); - should.exist(updatedCustomer); - should.exist(updatedCustomer.order); - updatedCustomer.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(updatedCustomer.newField); - updatedCustomer.newField.should.equal(updateAttrs.newField); - setTimeout(function () { - Customer.findById('aaa', function (err, customerFetchedAgain) { - should.not.exist(err); - should.exist(customerFetchedAgain); - should.exist(customerFetchedAgain.order); - customerFetchedAgain.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(customerFetchedAgain.newField); - customerFetchedAgain.newField.should.equal(updateAttrs.newField); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - }); - - describe('findById', function () { - - before(function (done) { - User.destroyAll(done); - }); - - it('should query by id: not found', function (done) { - // TODO: wait a few seconds for the Users to be destroyed? near-real-time != real-time - User.findById(1, function (err, u) { - should.not.exist(u); - should.not.exist(err); - done(); - }); - }); - - it('should query by id: found', function (done) { - this.timeout(4000); - User.create(function (err, u) { - should.not.exist(err); - should.exist(u.id); - setTimeout(function () { - User.findById(u.id, function (err, u) { - logger('err: ', err); - logger('user: ', u); - should.exist(u); - should.not.exist(err); - u.should.be.an.instanceOf(User); - done(); - }); - }, 2000); - }); - }); - - }); - - describe('custom', function () { - - it('suggests query should work', function (done) { - User.all({ - suggests: { - 'title_suggester': { - text: 'Stu', - term: { - field: 'name' - } - } - } - }, function (err/*, u*/) { - //should.exist(u); - should.not.exist(err); - done(); - }); - }); - - it('native query should work', function (done) { - User.all({ - native: { - query: { - 'match_all': {} - } - } - }, function (err, u) { - should.exist(u); - should.not.exist(err); - done(); - }); - }); - }); - - // TODO: Resolve the discussion around: https://support.strongloop.com/requests/676 - describe('findByIds', function () { - var createdUsers; - before(function (done) { - this.timeout(4000); - var people = [ - {seq: 1, name: 'a', vip: true}, - {seq: 2, name: 'b'}, - {seq: 3, name: 'c'}, - {seq: 4, name: 'd', vip: true}, - {seq: 5, name: 'e'}, - {seq: 6, name: 'f'} - ]; - db.automigrate(['User'], function (err) { - should.not.exist(err); - User.create(people, function (err, users) { - should.not.exist(err); - // Users might be created in parallel and the generated ids can be - // out of sequence - createdUsers = users; - done(); - }); - }); - }); - - it('should query by ids', function (done) { - this.timeout(4000); - setTimeout(function () { - User.findByIds( - [createdUsers[2].id, createdUsers[1].id, createdUsers[0].id], - function (err, users) { - should.exist(users); - should.not.exist(err); - var names = users.map(function (u) { - return u.name; - }); - - // TODO: 1. find code that tries to add sort order and tell it not to do so - // because findByIds isn't meant to work like that - // 2. can get clues from how mongo connector tracks the calling - // method name to accomplish the same thing - - // TODO: Resolve the discussion around: https://support.strongloop.com/requests/676 - /** - * 1) find() by default sorts by id property. - * 2) findByIds() expects the results sorted by the ids as they are passed in the argument. - * i) Connector.prototype.all() should NOT deal with the rules for findByIds() - * as the sorting for findByIds() is done after the connector returned an array of objects. - * ii) Here is how findByIds() implemented: - * i) Build a query with inq for ids from the arg - * ii) Call Model.find() (no ordering is set, connectors will default it to id) - * iii) Sort the results by the order of ids in the arg - * - */ - /*names.should.eql( // NOTE: order doesn't add up, is 2.ii.iii broken? - [createdUsers[2].name, createdUsers[1].name, createdUsers[0].name]);*/ - - // temporary workaround to help tests pass - names.should.include(createdUsers[2].name); - names.should.include(createdUsers[1].name); - names.should.include(createdUsers[0].name); - done(); - }); - }, 2000); - }); - - it('should query by ids and condition', function (done) { - this.timeout(4000); - setTimeout(function () { - User.findByIds([ - createdUsers[0].id, - createdUsers[1].id, - createdUsers[2].id, - createdUsers[3].id], // this helps test "inq" - {where: {vip: true}}, function (err, users) { - should.exist(users); - should.not.exist(err); - var names = users.map(function (u) { - return u.name; - }); - names.should.eql(createdUsers.slice(0, 4).filter(function (u) { - return u.vip; - }).map(function (u) { - return u.name; - })); - done(); - }); - }, 2000); - }); - - }); - - describe('sanity test IDs', function () { - - before(function (done) { - User.destroyAll(done); - }); - - it('should auto generate an id', function (done) { - this.timeout(4000); - User.create(function (err, u) { - should.not.exist(err); - should.exist(u.id); - should.exist(u.seq); - done(); - }); - }); - - it('should use specified id', function (done) { - this.timeout(4000); - User.create({seq: 666}, function (err, u) { - should.not.exist(err); - should.exist(u.id); - should.exist(u.seq); - u.id.should.equal('666'); - u.seq.should.equal('666'); - done(); - }); - }); - - after(function (done) { - db.automigrate(done); - }); - }); - - describe('find', function () { - - before(seed); - - it('should query collection', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.find(function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - }, 2000); - }); - - it('should query limited collection', function (done) { - User.find({limit: 3}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - }); - - it('should query ordered collection with skip & limit', function (done) { - User.find({skip: 1, limit: 4, order: 'seq'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users[0].seq.should.be.eql(1); - users.should.have.lengthOf(4); - done(); - }); - }); - - it('should query ordered collection with offset & limit', function (done) { - User.find({offset: 2, limit: 3, order: 'seq'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users[0].seq.should.be.eql(2); - users.should.have.lengthOf(3); - done(); - }); - }); - - it('should query filtered collection', function (done) { - setTimeout(function () { - User.find({where: {role: 'lead'}}, function (err, users) { - logger('users',users); - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(2); - done(); - }); - },2000); - - }); - - it('should query collection sorted by numeric field', function (done) { - User.find({order: 'order'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.forEach(function (u, i) { - u.order.should.eql(i + 1); - }); - done(); - }); - }); - - it('should query collection desc sorted by numeric field', function (done) { - User.find({order: 'order DESC'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.forEach(function (u, i) { - u.order.should.eql(users.length - i); - }); - done(); - }); - }); - - it('should query collection sorted by string field', function (done) { - User.find({order: 'name'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.shift().name.should.equal('George Harrison'); - users.shift().name.should.equal('John Lennon'); - users.pop().name.should.equal('Stuart Sutcliffe'); - done(); - }); - }); - - it('should query collection desc sorted by string field', function (done) { - User.find({order: 'name DESC'}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.pop().name.should.equal('George Harrison'); - users.pop().name.should.equal('John Lennon'); - users.shift().name.should.equal('Stuart Sutcliffe'); - done(); - }); - }); - - it('should support "and" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "and" with "inq" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {seq: {inq:[] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - },2000); - }); - - it('should support "or" with nested "and" using "inq" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - or: [ - { and: [{seq: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }, function (err, users) { - should.not.exist(err); - should.exist(users); - users.should.have.property('length', 3); - done(); - }); - },2000); - }); - - /** - * Testing if es can support nested queries which the loopback ORM can. - * where: {or: [{ and: [{seq: {inq:[3,4,5] }}, { vip: true }] },{ role: 'lead' }]} - */ - it('should support "or" with nested "and" using "inq" operator that is satisfied with native query', function (done) { - setTimeout(function () { - User.find({ - native: { - 'query': { - 'bool': { - 'should': [ - { - 'bool': { - 'must': [ - { - 'terms': { - '_id': [ - 3, - 4, - 5 - ] - } - }, - { - 'match': { - 'vip': true - } - } - ] - } - } - ] - } - } - } - }, function (err, users) { - should.not.exist(err); - should.exist(users); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "and" operator that is not satisfied', function (done) { - User.find({ - where: { - and: [ - {name: 'John Lennon'}, - {role: 'member'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support "or" that is satisfied', function (done) { - User.find({ - where: { - or: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - done(); - }); - }); - - it('should support "or" operator that is not satisfied', function (done) { - User.find({ - where: { - or: [ - {name: 'XYZ'}, - {role: 'Hello1'} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support date "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gte": new Date('1980-12-08')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support date "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gt": new Date('1980-12-08')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support date "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"gt": new Date('1980-12-07')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support date "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - birthday: {"lt": new Date('1980-12-07')} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should support number "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gte": 3} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 4); - users[0].name.should.equal('George Harrison'); - done(); - }); - }); - - it('should support number "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": 6} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support number "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": 5} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should support number "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - order: {"lt": 2} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - xit('should support number "gt" that is satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - order: {"gt": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - xit('should support number "lt" that is not satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - order: {"lt": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - xit('should support string "gte" that is satisfied by null value', function (done) { - User.find({ - order: 'seq', where: { - name: {"gte": null} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support string "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gte": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 4); - users[0].name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should support string "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gt": 'xyz'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support string "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"gt": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should support string "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - name: {"lt": 'Paul McCartney'} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "gte" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gte": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "gt" that is not satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gt": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 0); - done(); - }); - }); - - it('should support boolean "gt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"gt": false} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 3); - users[0].name.should.equal('John Lennon'); - done(); - }); - }); - - it('should support boolean "lt" that is satisfied', function (done) { - User.find({ - order: 'seq', where: { - vip: {"lt": true} - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - users[0].name.should.equal('George Harrison'); - done(); - }); - }); - - it('should support "inq" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {inq: [0,1,2] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - },2000); - }); - - it('should support "inq" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {inq: [] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(0); - done(); - }); - },2000); - }); - - it('should support "nin" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {nin: [0,1,2] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(3); - done(); - }); - },2000); - }); - - it('should support "nin" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {seq: {nin: [] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support "and" with "nin" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {seq: {nin:[0,1,2] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 1); - done(); - }); - },2000); - }); - - it('should support "between" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {order: {between: [3,6] }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(4); - done(); - }); - },2000); - }); - - it('should support "between" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {order: {between: [3,1] }}}, function (err, users) { - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support "and" with "between" operator that is satisfied', function (done) { - setTimeout(function () { - User.find({ - where: { - and: [ - {order: {between:[2,6] }}, - {vip: true} - ] - } - }, function (err, users) { - should.not.exist(err); - users.should.have.property('length', 2); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {name: {neq: 'John Lennon' }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(5); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is satisfied with undefined property', function (done) { - setTimeout(function () { - User.find({where: {role: {neq: 'lead' }}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(4); - done(); - }); - },2000); - }); - - it('should support "neq" filter that is not satisfied', function (done) { - setTimeout(function () { - User.find({where: {role: {neq: ''}}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(6); - done(); - }); - },2000); - }); - - it('should support multiple comma separated property filter without "and" that is satisfied', function (done) { - setTimeout(function () { - User.find({where: {role: 'lead', vip: true}}, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(2); - done(); - }); - },2000); - }); - - it('should support multiple comma separated "inq" without "and" filter that is satisfied', function (done) { - setTimeout(function () { - User.find( - { - where: { - role: {inq: ['lead']}, - seq: {inq: [0,2,3,4,5]} - } - }, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(1); - done(); - }); - },2000); - }); - - it('should support two "inq" and one "between" without "and" filter that is satisfied', function (done) { - setTimeout(function () { - User.find( - { - where: { - role: {inq: ['lead']}, - order: {between: [1,6]}, - seq: {inq: [2,3,4,5]} - } - }, function (err, users) { - should.exist(users); - should.not.exist(err); - users.should.have.lengthOf(0); - done(); - }); - },2000); - }); - }); - - // TODO: there is no way for us to test the connector code explicitly - // if the underlying juggler performs the same work as well! - // https://support.strongloop.com/requests/679 - // https://github.com/strongloop-community/loopback-connector-elastic-search/issues/5 - describe('find', function () { - - before(seed); - - it('should only include fields as specified', function (done) { - this.timeout(30000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var remaining = 0; - - function sample(fields) { - logger('expect: ', fields); - return { - expect: function (arr) { - remaining++; - User.find({fields: fields}, function (err, users) { - - remaining--; - if (err) { - return done(err); - } - - should.exist(users); - logger(JSON.stringify(users, null, 2)); - - if (remaining === 0) { - done(); - } - - users.forEach(function (user) { - var obj = user.toObject(); - Object.keys(obj) - .forEach(function (key) { - // if the obj has an unexpected value - if (obj[key] !== undefined && arr.indexOf(key) === -1) { - throw new Error('should not include data for key: ' + key); - } - }); - }); - }); - } - }; - } - - sample({email: false}).expect(['id', 'seq', 'name', 'role', 'order', 'birthday', 'vip']); - /*sample({name: true}).expect(['name']); - sample({name: false}).expect(['id', 'seq', 'email', 'role', 'order', 'birthday', 'vip']); - sample({name: false, id: true}).expect(['id']); - sample({id: true}).expect(['id']); - sample('id').expect(['id']); - sample(['id']).expect(['id']); - sample(['email']).expect(['email']);*/ - }, 2000); - }); - - }); - - describe('count', function () { - - before(seed); - - it('should query total count', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.count(function (err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(6); - done(); - }); - }, 2000); - }); - - it('should query filtered count', function (done) { - User.count({role: 'lead'}, function (err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(2); - done(); - }); - }); - }); - - describe('findOne', function () { - - before(seed); - - it('should find first record (default sort by id)', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.all({order: 'id'}, function (err, users) { - User.findOne(function (e, u) { - should.not.exist(e); - should.exist(u); - // NOTE: if `id: true` is not set explicitly when defining a model, there will be trouble! - u.id.toString().should.equal(users[0].id.toString()); - done(); - }); - }); - }, 2000); - }); - - it('should find first record', function (done) { - User.findOne({order: 'order'}, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(1); - u.name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should find last record', function (done) { - User.findOne({order: 'order DESC'}, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(6); - u.name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should find last record in filtered set', function (done) { - User.findOne({ - where: {role: 'lead'}, - order: 'order DESC' - }, function (e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(2); - u.name.should.equal('John Lennon'); - done(); - }); - }); - - it('should work even when find by id', function (done) { - User.findOne(function (e, u) { - //logger(JSON.stringify(u)); - // ESConnector.prototype.all +0ms model User filter {"where":{},"limit":1,"offset":0,"skip":0} - /* - * Ideally, instead of always generating: - * filter {"where":{"id":0},"limit":1,"offset":0,"skip":0} - * the id-literal should be replaced with the actual idName by loopback's core: - * filter {"where":{"seq":0},"limit":1,"offset":0,"skip":0} - * in my opinion. - */ - User.findOne({where: {id: u.id}}, function (err, user) { - should.not.exist(err); - should.exist(user); - done(); - }); - }); - }); - - }); - - describe('exists', function () { - - before(seed); - - it('should check whether record exist', function (done) { - this.timeout(4000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.findOne(function (e, u) { - User.exists(u.id, function (err, exists) { - should.not.exist(err); - should.exist(exists); - exists.should.be.ok; - done(); - }); - }); - }, 2000); - }); - - it('should check whether record not exist', function (done) { - User.destroyAll(function () { - User.exists(42, function (err, exists) { - should.not.exist(err); - exists.should.not.be.ok; - done(); - }); - }); - }); - - }); - - describe('destroyAll with where option', function () { - - before(seed); - - it('should only delete instances that satisfy the where condition', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - User.destroyAll({name: 'John Lennon'}, function () { - setTimeout(function () { - User.find({where: {name: 'John Lennon'}}, function (err, data) { - should.not.exist(err); - data.length.should.equal(0); - User.find({where: {name: 'Paul McCartney'}}, function (err, data) { - should.not.exist(err); - data.length.should.equal(1); - done(); - }); - }); - }, 2000); - }); - }, 2000); - }); - - }); - - describe('updateOrCreate', function () { - - beforeEach(seed); - - it('should update existing model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var beatle = {seq: 1, rating: 5}; - User.updateOrCreate(beatle, function (err, instance) { - should.not.exist(err); - should.exist(instance); - //instance.should.eql(beatle); - setTimeout(function () { - User.find({where: {seq: 1}}, function (err, data) { - should.not.exist(err); - //data.length.should.equal(0); - logger(data); - data[0].rating.should.equal(beatle.rating); - done(); - }); - }, 2000); - }); - }, 2000); - }); - - it('should create a new model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var beatlesFan = {seq: 6, name: 'Pulkit Singhal', order: 7, vip: false}; - User.updateOrCreate(beatlesFan, function (err, instance) { - should.not.exist(err); - should.exist(instance); - should.exist(instance.id); - should.exist(instance.seq); - setTimeout(function () { - User.find({where: {seq: instance.seq}}, function (err, data) { - should.not.exist(err); - data[0].seq.should.equal(beatlesFan.seq); - data[0].name.should.equal(beatlesFan.name); - data[0].order.should.equal(beatlesFan.order); - data[0].vip.should.equal(beatlesFan.vip); - done(); - }); - }, 2000); - }); - }, 2000); - }); - }); - - describe('updateAttributes', function () { - - beforeEach(seed); - - it('should update existing model', function (done) { - this.timeout(6000); - // NOTE: ES indexing then searching isn't real-time ... its near-real-time - setTimeout(function () { - var updateAttrs = {newField: 1, order: 999}; - User.findById(1, function (err, user) { - should.not.exist(err); - should.exist(user); - //user.id.should.equal(1); - //user.seq.should.equal(1); - should.exist(user.order); - should.not.exist(user.newField); - user.updateAttributes(updateAttrs, function (err, updatedUser) { - should.not.exist(err); - should.exist(updatedUser); - should.exist(updatedUser.order); - updatedUser.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(updatedUser.newField); - updatedUser.newField.should.equal(updateAttrs.newField); - setTimeout(function () { - User.findById(1, function (err, userFetchedAgain) { - logger('333'); - should.not.exist(err); - should.exist(userFetchedAgain); - should.exist(userFetchedAgain.order); - userFetchedAgain.order.should.equal(updateAttrs.order); - // TODO: should a new field be added by updateAttributes? - // https://support.strongloop.com/requests/680 - should.exist(userFetchedAgain.newField); - userFetchedAgain.newField.should.equal(updateAttrs.newField); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - - }); - - describe('all', function () { - - before(destroyAccessTokens); - - it('should convert date type fields from string to javascript date object when fetched', function (done) { - this.timeout(4000); - AccessToken.create({ttl: 1209600, created: '2017-01-10T12:12:38.600Z'}, function (err, token) { - should.not.exist(err); - should.exist(token.id); - setTimeout(function () { - AccessToken.findById(token.id, function (err, tokenInstance) { - should.not.exist(err); - should.exist(tokenInstance); - tokenInstance.should.be.an.instanceOf(AccessToken); - tokenInstance.created.should.be.an.instanceOf(Date); - done(); - }); - }, 2000); - }); - }); - - describe('embedsMany relations', function () { - - before(function (done) { - this.timeout = 4000; - Category.destroyAll(function () { - SubCategory.destroyAll(function () { - db.automigrate(['Category'], done); - }); - }); - }); - - it('should create embeded models and return embeded data using findById', function (done) { - this.timeout = 6000; - var category = {category_name: 'Apparels', desc: 'This is a category for apparels'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Jeans'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Jeans'); - setTimeout(function () { - Category.findById(ct.id, function (err, found) { - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Apparels'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Jeans'); - done(); - }); - },2000); - }); - },2000); - }); - }); - - it('should create multiple embeded models and return proper data using findById', function (done) { - this.timeout = 6000; - var category = {category_name: 'Electronics', desc: 'This is a category for electronics'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Mobiles'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Mobiles'); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Laptops'}, function (err, data) { - should.not.exist(err); - should.exist(data.id); - expect(data.subcategory_name).to.equal('Laptops'); - setTimeout(function () { - Category.findById(ct.id, function (err, found) { - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Electronics'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Mobiles'); - expect(found).to.have.deep.property('subCategories[1].subcategory_name','Laptops'); - done(); - }); - },2000); - }); - },2000); - }); - },2000); - }); - }); - - it('should create embeded models and return embeded data using find', function (done) { - this.timeout = 6000; - var category = {category_name: 'Footwear', desc: 'This is a category for footwear'}; - Category.create(category, function (err, ct) { - should.not.exist(err); - should.exist(ct.id); - should.exist(ct.category_name); - should.exist(ct.desc); - setTimeout(function () { - ct.subCategoryList.create({subcategory_name: 'Sandals'}, function (err, sct) { - should.not.exist(err); - should.exist(sct.id); - expect(sct.subcategory_name).to.equal('Sandals'); - setTimeout(function () { - Category.find({where: {category_name: 'Footwear'}}, function (err, found) { - found = found[0]; - should.not.exist(err); - should.exist(found.id); - expect(found.category_name).to.equal('Footwear'); - expect(found.subCategories).to.be.instanceOf(Array); - expect(found).to.have.deep.property('subCategories[0].subcategory_name','Sandals'); - done(); - }); - },2000); - }); - },2000); - }); - }); - }); - - describe('hasMany relations', function () { - - beforeEach(function (done) { - this.timeout = 4000; - User.destroyAll(function () { - Post.destroyAll(function () { - db.automigrate(['User'], done); - }); - }); - }); - - it('should create related model and check if include filter works', function (done) { - this.timeout = 6000; - var Kamal = { - seq: 0, - name: 'Kamal Khatwani', - email: 'kamal@shoppinpal.com', - role: 'lead', - birthday: new Date('1993-12-08'), - order: 2, - vip: true - }; - - User.create(Kamal, function (err, kamal) { - should.not.exist(err); - should.exist(kamal.id); - should.exist(kamal.seq); - var kamals_post_1 = { - title: 'Kamal New Post', - content: 'First post of kamal khatwani on elasticsearch', - comments: ['First Comment'] - }; - setTimeout(function () { - kamal.posts.create(kamals_post_1, function (err, firstPost) { - should.not.exist(err); - should.exist(firstPost.id); - expect(firstPost.userId).to.equal(0); - setTimeout(function () { - User.find({include: 'posts'}, function (err, userFound) { - userFound = userFound[0].toJSON(); - should.not.exist(err); - should.exist(userFound.posts); - expect(userFound.posts).to.be.instanceOf(Array); - expect(userFound.posts.length).to.equal(1); - done(); - }); - },2000); - }); - },2000); - }); - }); - - it('should create related model and check include filter with specific fields', function (done) { - this.timeout = 6000; - var Kamal = { - seq: 0, - name: 'Kamal Khatwani', - email: 'kamal@shoppinpal.com', - role: 'lead', - birthday: new Date('1993-12-08'), - order: 2, - vip: true - }; - - User.create(Kamal, function (err, kamal) { - should.not.exist(err); - should.exist(kamal.id); - should.exist(kamal.seq); - var kamals_post_1 = { - title: 'Kamal New Post', - content: 'First post of kamal khatwani on elasticsearch', - comments: ['First Comment'] - }; - setTimeout(function () { - kamal.posts.create(kamals_post_1, function (err, firstPost) { - should.not.exist(err); - should.exist(firstPost.id); - expect(firstPost.userId).to.equal(0); - setTimeout(function () { - User.find({include: {relation: 'posts', scope: {fields : ['title']}}}, function (err, userFound) { - userFound = userFound[0].toJSON(); - should.not.exist(err); - should.exist(userFound.posts); - expect(userFound.posts).to.be.instanceOf(Array); - expect(userFound.posts.length).to.equal(1); - expect(userFound.posts[0].title).to.equal('Kamal New Post'); - expect(userFound.posts[0].content).to.equal(undefined); - expect(userFound.posts[0].comments).to.equal(undefined); - done(); - }); - },2000); - }); - },2000); - }); - }); - }); - }); - - describe('save', function () { - - before(destroyPosts); - - it('all return should honor filter.fields, with `_id` as defined id', function (done) { - this.timeout(4000); - - var post = new PostWithId({id:'AAAA' ,title: 'Posts', content: 'all return should honor filter.fields'}); - post.save(function (err, post) { - setTimeout(function () { - PostWithId.all({fields: ['title'], where: {title: 'Posts'}}, function (err, posts) { - should.not.exist(err); - posts.should.have.lengthOf(1); - post = posts[0]; - post.should.have.property('title', 'Posts'); - post.should.have.property('content', undefined); - should.not.exist(post._id); - - done(); - }); - }, 2000); - }); - }); - - it('save should not return _id', function (done) { - this.timeout(4000); - - Post.create({title: 'Post1', content: 'Post content'}, function (err, post) { - post.content = 'AAA'; - setTimeout(function () { - post.save(function (err, p) { - should.not.exist(err); - should.not.exist(p._id); - p.id.should.be.equal(post.id); - p.content.should.be.equal('AAA'); - - done(); - }); - }, 2000); - - }); - }); - - it('save should update the instance with the same id', function (done) { - this.timeout(6000); - - Post.create({title: 'a', content: 'AAA'}, function (err, post) { - post.title = 'b'; - delete post.content; - setTimeout(function () { - post.save(function (err, p) { - should.not.exist(err); - p.id.should.be.equal(post.id); - p.content.should.be.equal(post.content); - should.not.exist(p._id); - setTimeout(function () { - Post.findById(post.id, function (err, p) { - p.id.should.be.eql(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal('b'); - done(); - }); - }, 2000); - }); - }, 2000); - }); - }); - - it('save should update the instance without removing existing properties', function (done) { - this.timeout(6000); - - Post.create({ - title: 'a', - content: 'update the instance without removing existing properties' - }, function (err, post) { - delete post.title; - setTimeout(function () { - post.save(function (err, p) { - - should.not.exist(err); - p.id.should.be.equal(post.id); - p.content.should.be.equal(post.content); - should.not.exist(p._id); - setTimeout(function () { - Post.findById(post.id, function (err, p) { - p.id.should.be.eql(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal('a'); - - done(); - }); - }, 2000); - }); - }, 2000); - - }); - }); - - it('save should create a new instance if it does not exist', function (done) { - this.timeout(6000); - - var post = new Post({id: '123', title: 'Create', content: 'create if does not exist'}); - post.save(post, function (err, p) { - should.not.exist(err); - p.title.should.be.equal(post.title); - p.content.should.be.equal(post.content); - p.id.should.be.equal(post.id); - setTimeout(function () { - Post.findById(p.id, function (err, p) { - p.id.should.be.equal(post.id); - should.not.exist(p._id); - p.content.should.be.equal(post.content); - p.title.should.be.equal(post.title); - p.id.should.be.equal(post.id); - - done(); - }); - }, 2000); - }); - }); - - it('all return should honor filter.fields', function (done) { - this.timeout(4000); - - var post = new Post({title: 'Fields', content: 'all return should honor filter.fields'}); - post.save(function (err, post) { - setTimeout(function () { - Post.all({fields: ['title'], where: {title: 'Fields'}}, function (err, posts) { - should.not.exist(err); - posts.should.have.lengthOf(1); - post = posts[0]; - post.should.have.property('title', 'Fields'); - post.should.have.property('content', undefined); - should.not.exist(post._id); - should.not.exist(post.id); - - done(); - }); - }, 2000); - }); - }); - - }); - - describe('updateAll', function () { - before(seed); - it('should update the document', function (done) { - this.timeout(6000); - - var userToUpdate = { seq: 10, name: 'Aquid Shahwar', email: 'aquid@shoppinpal.com', role: 'lead', - birthday: new Date('1992-09-21'), order: 11, vip: true - }; - User.create(userToUpdate, function (err, user) { - should.not.exist(err); - should.exist(user); - setTimeout(function () { - User.updateAll({seq: user.seq},{order: 10}, function (err,update) { - should.not.exist(err); - should.exist(update); - setTimeout(function () { - User.findById(user.seq, function (err, updatedUser) { - should.not.exist(err); - should.exist(updatedUser); - updatedUser.name.should.be.equal('Aquid Shahwar'); - updatedUser.order.should.be.equal(10); - done(); - }); - },2000); - }); - },2000); - }); - }) - - }); - - xdescribe('test id fallback when `generated:false`', function () { - - it('should auto generate an id', function (done) { - this.timeout(8000); - Customer.create({name: 'George Harrison', vip: false}, function (err, u) { - logger('user after create', u); - should.not.exist(err); - should.exist(u.id); - should.exist(u.objectId); - setTimeout(function () { - Customer.findById(u.objectId, function (err, u) { - logger('customer after first findById', u); - u.save(function (err, savedCustomer) { - logger('user after save', savedCustomer); - setTimeout(function () { - Customer.findById(u.objectId, function (err, foundUser) { - logger('user after findById', foundUser); - done(); - }); - }, 2000); - }); - }); - }, 2000); - }); - }); - }); - -}); - -function seed() { - this.timeout(4000); - var beatles = [ - { - seq: 0, - name: 'John Lennon', - email: 'john@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1980-12-08'), - order: 2, - vip: true - }, - { - seq: 1, - name: 'Paul McCartney', - email: 'paul@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1942-06-18'), - order: 1, - vip: true - }, - {seq: 2, name: 'George Harrison', order: 5, vip: false}, - {seq: 3, name: 'Ringo Starr', order: 6, vip: false}, - {seq: 4, name: 'Pete Best', order: 4}, - {seq: 5, name: 'Stuart Sutcliffe', order: 3, vip: true} - ]; - - return User.destroyAll().then(function() { - return User.create(beatles); - }); -} - -function seedCustomers(done) { - this.timeout(4000); - var customers = [ - { - objectId: 'aaa', - name: 'John Lennon', - email: 'john@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1980-12-08'), - order: 2, - vip: true - }, - { - objectId: 'bbb', - name: 'Paul McCartney', - email: 'paul@b3atl3s.co.uk', - role: 'lead', - birthday: new Date('1942-06-18'), - order: 1, - vip: true - }, - {objectId: 'ccc', name: 'George Harrison', order: 5, vip: false}, - {objectId: 'ddd', name: 'Ringo Starr', order: 6, vip: false}, - {objectId: 'eee', name: 'Pete Best', order: 4}, - {objectId: 'fff', name: 'Stuart Sutcliffe', order: 3, vip: true} - ]; - - async.series([ - Customer.destroyAll.bind(Customer), - function (cb) { - setTimeout(function () { - async.each(customers, Customer.create.bind(Customer), cb); - }, 2000); - } - ], done); -} - -function destroyAccessTokens(done) { - this.timeout(4000); - AccessToken.destroyAll.bind(AccessToken); - setTimeout(function () { - done(); - }, 2000); -} - -function destroyPosts(done) { - this.timeout(4000); - Post.destroyAll.bind(Post); - PostWithId.destroyAll.bind(PostWithId); - setTimeout(function () { - done(); - }, 2000) -} diff --git a/test/es-v2/04.add-defaults-refresh-true.test.js b/test/es-v2/04.add-defaults-refresh-true.test.js deleted file mode 100644 index 969b2d6..0000000 --- a/test/es-v2/04.add-defaults-refresh-true.test.js +++ /dev/null @@ -1,97 +0,0 @@ -/*eslint no-console: "off"*/ -/*global getSchema should assert*/ -describe('Add Defaults', function () { - var testConnector, testConnector2, db, db2; - - before(function (done) { - require('./init.js'); - - var settings = getSettings(); - settings.log = 'error'; - db = getDataSource(settings); - - var settings2 = getSettings(); - settings2.refreshOn = ["save", "updateAttributes"]; - db2 = getDataSource(settings2); - - var account = {real_name: {type: String, index: true, sort: true}}; - db2.define("Account", account); - db.define("Account", account); - var bookProps = {real_name: {type: String, index: true, sort: true}}; - var bookSettings = { - "properties": { - "real_name": { - "type": "keyword" - } - }, - "elasticsearch": { - "create": { - "refresh": false - }, - "destroy": { - "refresh": false - }, - "destroyAll": { - "refresh": "wait_for" - } - } - }; - db.define("Book", bookProps, bookSettings); - db2.define("Book", bookProps, bookSettings); - testConnector = db.connector; - testConnector2 = db2.connector; - db.automigrate(done); - }); - - describe('Datasource specific settings', function () { - - it('modifying operations should have refresh true', function () { - (typeof testConnector2.addDefaults('Account', 'create').refresh === 'undefined').should.be.true; - (testConnector2.addDefaults('Account', 'save').refresh === true).should.be.true; - (typeof testConnector2.addDefaults('Account', 'destroy').refresh === 'undefined').should.be.true; - (typeof testConnector2.addDefaults('Account', 'destroyAll').refresh === 'undefined').should.be.true; - (testConnector2.addDefaults('Account', 'updateAttributes').refresh === true).should.be.true; - (typeof testConnector2.addDefaults('Account', 'updateOrCreate').refresh === 'undefined').should.be.true; - }); - - it('create and destroy should have refresh false for model book', function () { - (testConnector2.addDefaults('Book', 'destroy').refresh === false).should.be.true; - (testConnector2.addDefaults('Book', 'create').refresh === false).should.be.true; - (testConnector2.addDefaults('Book', 'save').refresh === true).should.be.true; - (testConnector2.addDefaults('Book', 'destroyAll').refresh === 'wait_for').should.be.true; - (testConnector2.addDefaults('Book', 'updateAttributes').refresh === true).should.be.true; - (typeof testConnector2.addDefaults('Book', 'updateOrCreate').refresh === 'undefined').should.be.true; - }); - - it('should never have a refresh attribute', function () { - (typeof testConnector.addDefaults('Book', 'removeMappings').refresh === 'undefined').should.be.true; - (typeof testConnector.addDefaults('Book', 'buildFilter').refresh === 'undefined').should.be.true; - (typeof testConnector.addDefaults('Book', 'find').refresh === 'undefined').should.be.true; - (typeof testConnector.addDefaults('Book', 'exists').refresh === 'undefined').should.be.true; - (typeof testConnector.addDefaults('Book', 'count').refresh === 'undefined').should.be.true; - }); - }); - - describe('Model specific settings', function () { - - it('modifying operations should have refresh true', function () { - (testConnector.addDefaults('Account', 'create').refresh === true).should.be.true; - (testConnector.addDefaults('Account', 'save').refresh === true).should.be.true; - (testConnector.addDefaults('Account', 'destroy').refresh === true).should.be.true; - (testConnector.addDefaults('Account', 'destroyAll').refresh === true).should.be.true; - (testConnector.addDefaults('Account', 'updateAttributes').refresh === true).should.be.true; - (testConnector.addDefaults('Account', 'updateOrCreate').refresh === true).should.be.true; - - }); - - it('create and destroy should have refresh false for model book', function () { - (testConnector.addDefaults('Book', 'destroy').refresh === false).should.be.true; - (testConnector.addDefaults('Book', 'create').refresh === false).should.be.true; - (testConnector.addDefaults('Book', 'save').refresh === true).should.be.true; - (testConnector.addDefaults('Book', 'destroyAll').refresh === 'wait_for').should.be.true; - (testConnector.addDefaults('Book', 'updateAttributes').refresh === true).should.be.true; - (testConnector.addDefaults('Book', 'updateOrCreate').refresh === true).should.be.true; - }); - - }); -}); \ No newline at end of file diff --git a/test/es-v2/datasource-test-v2-plain.json b/test/es-v2/datasource-test-v2-plain.json deleted file mode 100644 index 8276864..0000000 --- a/test/es-v2/datasource-test-v2-plain.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "esv2-plain", - "connector": "elasticsearch", - "index": "shakespeare", - "hosts": [ - { - "protocol": "http", - "host": "localhost", - "port": 9202 - } - ], - "apiVersion": "2.3", - "log": "error", - "defaultSize": 50, - "requestTimeout": 30000, - "mappings": [ - { - "name": "User", - "properties": { - "id": {"type": "string", "index" : "not_analyzed"}, - "seq": {"type": "integer"}, - "name" : { - "type" : "multi_field", - "fields" : { - "name" : {"type" : "string", "index" : "not_analyzed"}, - "native" : {"type" : "string", "index" : "analyzed"} - } - }, - "email": {"type": "string", "index" : "not_analyzed"}, - "birthday": {"type": "date"}, - "role": {"type": "string", "index" : "not_analyzed"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "Customer", - "index": "juju", - "type": "consumer", - "properties": { - "objectId": {"type": "string", "index" : "not_analyzed"}, - "name" : { - "type" : "multi_field", - "fields" : { - "name" : {"type" : "string", "index" : "not_analyzed"}, - "native" : {"type" : "string", "index" : "analyzed"} - } - }, - "email": {"type": "string", "index" : "not_analyzed"}, - "birthday": {"type": "date"}, - "role": {"type": "string", "index" : "not_analyzed"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "AccessToken", - "properties": { - "id": { "type": "string", "index": "not_analyzed" }, - "ttl": { "type": "integer" }, - "created": { "type": "date" } - } - } - ] -} diff --git a/test/es-v2/init.js b/test/es-v2/init.js deleted file mode 100644 index 6428cad..0000000 --- a/test/es-v2/init.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -/** - * Why does this file exist? - * - * Individual tests can load the datasource, and avoid repetition, by adding: - * `require('./init.js');` - * of their source code. - */ - -var chai = require('chai'); -global.expect = chai.expect; -global.assert = chai.assert; -global.should = chai.should(); // Why is the function being executed? Because the "should" interface extends Object.prototype to provide a single getter as the starting point for your language assertions. - -global._ = require('lodash'); /*global _:true*/ - -var settings = require('./datasource-test-v2-plain.json'); -global.getSettings = function() { /*global getSettings*/ - return _.cloneDeep(settings); -}; - -var DataSource = require('loopback-datasource-juggler').DataSource; -global.getDataSource = global.getSchema = global.getConnector = function (customSettings) { - (customSettings) /*eslint no-console: ["error", { allow: ["log"] }] */ - ? console.log('\n\tcustomSettings will override global settings for datasource\n'/*, JSON.stringify(customSettings,null,2)*/) - : console.log('\n\twill use global settings for datasource\n'); - var settings = customSettings || getSettings(); - // settings.log = { - // type: 'file', - // level: 'trace', - // path: 'test/es-v2/elasticsearch-v2-'+Date.now()+'.log' - // }; - //console.log('\n\tsettings:\n', JSON.stringify(settings,null,2)); - settings.connector = require('../../'); - return new DataSource(settings); -}; diff --git a/test/es-v5/01.filters.test.js b/test/es-v5/01.filters.test.js deleted file mode 100644 index ae83d57..0000000 --- a/test/es-v5/01.filters.test.js +++ /dev/null @@ -1,725 +0,0 @@ -/*global getSettings getDataSource expect*/ -/*eslint no-console: ["error", { allow: ["trace","log"] }] */ -describe('Connector', function () { - var testConnector; - - before(function () { - require('./init.js'); - var settings = getSettings(); - settings.log = 'error'; - var datasource = getDataSource(settings); - testConnector = datasource.connector; - - datasource.define('MockLoopbackModel', { - // here we want to let elasticsearch auto-populate a field that will be mapped back to loopback as the `id` - id: {type: String, generated: true, id: true} - }); - }); - - it('should configure defaults when building filters', function (done) { - var modelName = 'MockLoopbackModel'; - var defaults = testConnector.addDefaults(modelName); - - expect(defaults.index).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - expect(defaults.type).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); - - done(); - }); - - it('should build a query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'title': 'Futuro' - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { // c. here we are testing the bigger picture `should build a query for the WHERE filter` - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should use a NATIVE filter query as-is', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'native': { - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index') - .that.is.a('string'); - expect(filterCriteria).to.have.property('type') - .that.is.a('string') - .that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ - query: { - bool: { - must: [ - { - match: { - title: 'Futuro' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a simple "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {name: 'John Lennon'}, - {role: 'lead'} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - name: 'John Lennon' - } - }, - { - match: { - role: 'lead' - } - } - ] - } - - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a complex "and" query with "inq" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {inq:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "or" and "and" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - or: [ - { and: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - should: [ - { - bool: { - must: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a nested "and" and "or" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - { or: [{id: {inq:[3,4,5] }}, { vip: true }] }, - { role: 'lead' } - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - should: [ - { - terms: { - _id: [ - 3, - 4, - 5 - ] - } - }, - { - match: { - vip: true - } - } - ] - } - }, - { - match: { - role: 'lead' - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - 'where': { - 'id': { - 'nin': [0, 1, 2] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "nin" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {id: {nin:[0,1,2] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - bool: { - must_not: [ - { - terms: { - _id: [ - 0, - 1, - 2 - ] - } - } - ] - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - order: { - between: [3, 6] - } - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 3, - lte: 6 - } - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build a "and" and "between" query for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - and: [ - {order: {between:[2,6] }}, - {vip: true} - ] - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - range: { - order: { - gte: 2, - lte: 6 - } - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build multiple normal where clause query without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: 'lead', - vip: true - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - match: { - role: 'lead' - } - }, - { - match: { - vip: true - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); - - it('should build two "inq" and one "between" without "and" for the WHERE filter', function (done) { - var criteria, size, offset, modelName, modelIdName; - criteria = { - where: { - role: {inq: ['lead']}, - order: {between: [1,6]}, - id: {inq: [2,3,4,5]} - } - }; - size = 100; - offset = 10; - modelName = 'MockLoopbackModel'; - modelIdName = 'id'; - - var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); - expect(filterCriteria).not.to.be.null; - expect(filterCriteria).to.have.property('index').that.is.a('string'); - expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(modelName); - expect(filterCriteria).to.have.property('body') - .that.is.an('object') - .that.deep.equals({ // a. this is really 2 tests in one - sort: [ - // b. `_uid` is an auto-generated field that ElasticSearch populates for us - // when we want to let the backend/system/ES take care of id population - // so if we want to sort by id, without specifying/controlling our own id field, - // then ofcourse the sort must happen on `_uid`, this part of the test, validates that! - '_uid' - ], - query: { - bool: { - must: [ - { - terms: { - role: [ - 'lead' - ] - } - }, - { - range: { - order: { - gte: 1, - lte: 6 - } - } - }, - { - terms: { - _id: [ - 2, - 3, - 4, - 5 - ] - } - } - ] - } - } - }); - expect(filterCriteria).to.have.property('size') - .that.is.a('number'); - expect(filterCriteria).to.have.property('from') - .that.is.a('number'); - - done(); - }); -}); \ No newline at end of file diff --git a/test/es-v5/datasource-test-v5-plain.json b/test/es-v5/datasource-test-v5-plain.json deleted file mode 100644 index 6249bda..0000000 --- a/test/es-v5/datasource-test-v5-plain.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "esv5-plain", - "connector": "elasticsearch", - "index": "shakespeare", - "hosts": [ - { - "protocol": "http", - "host": "localhost", - "port": 9203 - } - ], - "apiVersion": "5.0", - "log": "error", - "defaultSize": 50, - "requestTimeout": 30000, - "mappings": [ - { - "name": "User", - "properties": { - "id": {"type": "keyword"}, - "seq": {"type": "integer"}, - "name" : { - "type" : "keyword", - "fields" : { - "native" : {"type" : "keyword"} - } - }, - "email": {"type": "keyword"}, - "birthday": {"type": "date"}, - "role": {"type": "keyword"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "Customer", - "index": "juju", - "type": "consumer", - "properties": { - "objectId": {"type": "keyword"}, - "name" : { - "type" : "keyword", - "fields" : { - "native" : {"type" : "keyword"} - } - }, - "email": {"type": "keyword"}, - "birthday": {"type": "date"}, - "role": {"type": "keyword"}, - "order": {"type": "integer"}, - "vip": {"type": "boolean"} - } - }, - { - "name": "AccessToken", - "properties": { - "id": { "type": "keyword" }, - "ttl": { "type": "integer" }, - "created": { "type": "date" } - } - } - ] -} diff --git a/test/es-v6/datasource-test-v6-plain.json b/test/es-v6/datasource-test-v6-plain.json index ab04bbf..6e2238b 100644 --- a/test/es-v6/datasource-test-v6-plain.json +++ b/test/es-v6/datasource-test-v6-plain.json @@ -1,56 +1,67 @@ { - "name": "esv6-plain", - "connector": "elasticsearch", - "index": "shakespeare", - "hosts": [{ - "protocol": "http", - "host": "localhost", - "port": 9200 - }], - "apiVersion": "6.0", - "log": "error", + "name": "elasticsearch-example-index-datasource", + "connector": "esv6", + "version": 6, + "index": "example-index", + "configuration": { + "node": "http://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000 + }, "defaultSize": 50, - "requestTimeout": 30000, + "indexSettings": { + "number_of_shards": 2, + "number_of_replicas": 1 + }, "mappingType": "basedata", "mappingProperties": { + "docType": { + "type": "keyword", + "index": true + }, "id": { - "type": "keyword" + "type": "keyword", + "index": true }, "seq": { - "type": "integer" + "type": "integer", + "index": true }, "name": { "type": "keyword", - "fields": { - "native": { - "type": "keyword" - } - } + "index": true }, "email": { - "type": "keyword" + "type": "keyword", + "index": true }, "birthday": { - "type": "date" + "type": "date", + "index": true }, "role": { - "type": "keyword" + "type": "keyword", + "index": true }, "order": { - "type": "integer" + "type": "integer", + "index": true }, "vip": { - "type": "boolean" + "type": "boolean", + "index": true }, "objectId": { - "type": "keyword" + "type": "keyword", + "index": true }, "ttl": { - "type": "integer" + "type": "integer", + "index": true }, "created": { - "type": "date" + "type": "date", + "index": true } - }, - "mappings": [] + } } \ No newline at end of file diff --git a/test/es-v7/01.filters.test.js b/test/es-v7/01.filters.test.js new file mode 100644 index 0000000..d13dfbc --- /dev/null +++ b/test/es-v7/01.filters.test.js @@ -0,0 +1,783 @@ +/*global getSettings getDataSource expect*/ +/*eslint no-console: ["error", { allow: ["trace","log"] }] */ +describe('Connector', function() { + var testConnector; + + before(function() { + require('./init.js'); + var settings = getSettings(); + settings.log = 'error'; + var datasource = getDataSource(settings); + testConnector = datasource.connector; + + datasource.define('MockLoopbackModel', { + // here we want to let elasticsearch auto-populate a field that will be mapped back to loopback as the `id` + id: { type: String, generated: true, id: true } + }); + }); + + it('should configure defaults when building filters', function(done) { + var modelName = 'MockLoopbackModel'; + var defaults = testConnector.addDefaults(modelName); + + expect(defaults.index).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); + expect(defaults.type).to.be.a('string').to.have.length.above(1).to.match(/^[a-z0-9.-_]+$/i); + + done(); + }); + + it('should build a query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + 'where': { + 'title': 'Futuro' + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index') + .that.is.a('string'); + expect(filterCriteria).to.have.property('type') + .that.is.a('string') + .that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { // c. here we are testing the bigger picture `should build a query for the WHERE filter` + bool: { + must: [{ + match: { + title: 'Futuro' + } + }, { + match: { + docType: modelName + } + }] + } + + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should use a NATIVE filter query as-is', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + 'native': { + query: { + bool: { + must: [{ + match: { + title: 'Futuro' + } + }] + } + } + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index') + .that.is.a('string'); + expect(filterCriteria).to.have.property('type') + .that.is.a('string') + .that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ + query: { + bool: { + must: [{ + match: { + title: 'Futuro' + } + }] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a simple "and" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + and: [ + { name: 'John Lennon' }, + { role: 'lead' } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + match: { + name: 'John Lennon' + } + }, + { + match: { + role: 'lead' + } + }, + { + match: { + docType: modelName + } + } + ] + } + + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a complex "and" query with "inq" for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + and: [ + { id: { inq: [0, 1, 2] } }, + { vip: true } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + terms: { + _id: [ + 0, + 1, + 2 + ] + } + }, + { + match: { + vip: true + } + }, + { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a nested "or" and "and" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + or: [ + { and: [{ id: { inq: [3, 4, 5] } }, { vip: true }] }, + { role: 'lead' } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + match: { + docType: modelName + } + }, { + bool: { + should: [{ + bool: { + must: [{ + terms: { + _id: [ + 3, + 4, + 5 + ] + } + }, + { + match: { + vip: true + } + } + ] + } + }, + { + match: { + role: 'lead' + } + } + ] + } + }] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a nested "and" and "or" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + and: [ + { or: [{ id: { inq: [3, 4, 5] } }, { vip: true }] }, + { role: 'lead' } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + bool: { + should: [{ + terms: { + _id: [ + 3, + 4, + 5 + ] + } + }, + { + match: { + vip: true + } + } + ] + } + }, + { + match: { + role: 'lead' + } + }, + { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a "nin" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + 'where': { + 'id': { + 'nin': [0, 1, 2] + } + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + match: { + docType: modelName + } + }, { + bool: { + must_not: [{ + terms: { + _id: [ + 0, + 1, + 2 + ] + } + }] + } + }] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a "and" and "nin" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + and: [ + { id: { nin: [0, 1, 2] } }, + { vip: true } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + bool: { + must_not: [{ + terms: { + _id: [ + 0, + 1, + 2 + ] + } + }] + } + }, + { + match: { + vip: true + } + }, + { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a "between" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + order: { + between: [3, 6] + } + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + range: { + order: { + gte: 3, + lte: 6 + } + } + }, { + match: { + docType: modelName + } + }] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build a "and" and "between" query for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + and: [ + { order: { between: [2, 6] } }, + { vip: true } + ] + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + range: { + order: { + gte: 2, + lte: 6 + } + } + }, + { + match: { + vip: true + } + }, { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build multiple normal where clause query without "and" for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + role: 'lead', + vip: true + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + match: { + role: 'lead' + } + }, + { + match: { + vip: true + } + }, { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); + + it('should build two "inq" and one "between" without "and" for the WHERE filter', function(done) { + var criteria, size, offset, modelName, modelIdName, settings, mappingType; + criteria = { + where: { + role: { inq: ['lead'] }, + order: { between: [1, 6] }, + id: { inq: [2, 3, 4, 5] } + } + }; + size = 100; + offset = 10; + modelName = 'MockLoopbackModel'; + modelIdName = 'id'; + settings = getSettings(); + mappingType = settings.mappingType; + + var filterCriteria = testConnector.buildFilter(modelName, modelIdName, criteria, size, offset); + expect(filterCriteria).not.to.be.null; + expect(filterCriteria).to.have.property('index').that.is.a('string'); + expect(filterCriteria).to.have.property('type').that.is.a('string').that.equals(mappingType); + expect(filterCriteria).to.have.property('body') + .that.is.an('object') + .that.deep.equals({ // a. this is really 2 tests in one + sort: [ + // b. `_id` is an auto-generated field that ElasticSearch populates for us + // when we want to let the backend/system/ES take care of id population + // so if we want to sort by id, without specifying/controlling our own id field, + // then ofcourse the sort must happen on `_id`, this part of the test, validates that! + '_id' + ], + query: { + bool: { + must: [{ + terms: { + role: [ + 'lead' + ] + } + }, + { + range: { + order: { + gte: 1, + lte: 6 + } + } + }, + { + terms: { + _id: [ + 2, + 3, + 4, + 5 + ] + } + }, { + match: { + docType: modelName + } + } + ] + } + } + }); + expect(filterCriteria).to.have.property('size') + .that.is.a('number'); + expect(filterCriteria).to.have.property('from') + .that.is.a('number'); + + done(); + }); +}); \ No newline at end of file diff --git a/test/es-v5/02.basic-querying.test.js b/test/es-v7/02.basic-querying.test.js similarity index 99% rename from test/es-v5/02.basic-querying.test.js rename to test/es-v7/02.basic-querying.test.js index f7ebb20..2e900df 100644 --- a/test/es-v5/02.basic-querying.test.js +++ b/test/es-v7/02.basic-querying.test.js @@ -1,6 +1,6 @@ require('./init.js'); var async = require('async'); -var logger = require('debug')('test:es-v5:02.basic-querying.test.js'); +var logger = require('debug')('test:es-v6:02.basic-querying.test.js'); var db, User, Customer, AccessToken, Post, PostWithId, Category, SubCategory; /*eslint no-console: "off"*/ diff --git a/test/es-v5/03.basic-querying-no-timeout.test.js b/test/es-v7/03.basic-querying-no-timeout.test.js similarity index 100% rename from test/es-v5/03.basic-querying-no-timeout.test.js rename to test/es-v7/03.basic-querying-no-timeout.test.js diff --git a/test/es-v5/04.add-defaults-refresh-true.test.js b/test/es-v7/04.add-defaults-refresh-true.test.js similarity index 100% rename from test/es-v5/04.add-defaults-refresh-true.test.js rename to test/es-v7/04.add-defaults-refresh-true.test.js diff --git a/test/es-v7/datasource-test-v7-plain.json b/test/es-v7/datasource-test-v7-plain.json new file mode 100644 index 0000000..9ea1003 --- /dev/null +++ b/test/es-v7/datasource-test-v7-plain.json @@ -0,0 +1,67 @@ +{ + "name": "elasticsearch-example-index-datasource", + "connector": "esv6", + "version": 7, + "index": "example-index", + "configuration": { + "node": "http://localhost:9200", + "requestTimeout": 30000, + "pingTimeout": 3000 + }, + "defaultSize": 50, + "indexSettings": { + "number_of_shards": 2, + "number_of_replicas": 1 + }, + "mappingType": "basedata", + "mappingProperties": { + "docType": { + "type": "keyword", + "index": true + }, + "id": { + "type": "keyword", + "index": true + }, + "seq": { + "type": "integer", + "index": true + }, + "name": { + "type": "keyword", + "index": true + }, + "email": { + "type": "keyword", + "index": true + }, + "birthday": { + "type": "date", + "index": true + }, + "role": { + "type": "keyword", + "index": true + }, + "order": { + "type": "integer", + "index": true + }, + "vip": { + "type": "boolean", + "index": true + }, + "objectId": { + "type": "keyword", + "index": true + }, + "ttl": { + "type": "integer", + "index": true + }, + "created": { + "type": "date", + "index": true + } + } +} \ No newline at end of file diff --git a/test/es-v5/init.js b/test/es-v7/init.js similarity index 96% rename from test/es-v5/init.js rename to test/es-v7/init.js index 661f6fb..4782223 100644 --- a/test/es-v5/init.js +++ b/test/es-v7/init.js @@ -15,7 +15,7 @@ global.should = chai.should(); // Why is the function being executed? Because th global._ = require('lodash'); /*global _:true*/ -var settings = require('./datasource-test-v5-plain.json'); +var settings = require('./datasource-test-v7-plain.json'); global.getSettings = function() { /*global getSettings*/ return _.cloneDeep(settings); };