Compare commits

..

No commits in common. "master" and "v3.0.0" have entirely different histories.

26 changed files with 646 additions and 1112 deletions

View File

@ -1 +0,0 @@
coverage

133
.eslintrc
View File

@ -1,3 +1,134 @@
{
"extends": "loopback"
"env": {
"browser": false,
"node": true,
"amd": true
},
"rules": {
"no-alert": 2,
"no-array-constructor": 2,
"no-bitwise": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-comma-dangle": 2,
"no-cond-assign": 2,
"no-console": 1,
"no-constant-condition": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 1,
"no-dupe-keys": 2,
"no-else-return": 2,
"no-empty": 2,
"no-empty-class": 2,
"no-empty-label": 2,
"no-eq-null": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 1,
"no-extra-semi": 2,
"no-extra-strict": 2,
"no-fallthrough": 2,
"no-floating-decimal": 1,
"no-func-assign": 2,
"no-global-strict": 2,
"no-implied-eval": 2,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-mixed-requires": [2, false],
"no-multi-str": 2,
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 1,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-require": 1,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-path-concat": 0,
"no-plusplus": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 2,
"no-self-compare": 0,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-space-before-semi": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undefined": 1,
"no-undef-init": 2,
"no-underscore-dangle": 0,
"no-unreachable": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-use-before-define": 2,
"no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": 2,
"no-wrap-func": 2,
"no-mixed-spaces-and-tabs": [2, false],
"block-scoped-var": 1,
"brace-style": [2, "1tbs"],
"camelcase": 2,
"complexity": [0, 11],
"consistent-return": 2,
"consistent-this": [2, "that"],
"curly": [2, "all"],
"default-case": 1,
"dot-notation": 2,
"eol-last": 2,
"eqeqeq": 2,
"func-names": 0,
"func-style": [2, "declaration"],
"guard-for-in": 1,
"max-depth": [0, 4],
"max-len": [1, 80, 4],
"max-nested-callbacks": [1, 2],
"max-params": [0, 3],
"max-statements": [0, 10],
"handle-callback-err": 1,
"new-cap": 2,
"new-parens": 2,
"one-var": 0,
"quote-props": 1,
"quotes": [2, "single"],
"radix": 2,
"semi": 2,
"sort-vars": 1,
"space-after-keywords": [2, "always"],
"space-in-brackets": [1, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-word-ops": 1,
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 2,
"valid-typeof": 2,
"wrap-iife": 1,
"wrap-regex": 1,
"yoda": [2, "never"]
}
}

View File

@ -1,50 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -1,25 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

View File

@ -1,27 +0,0 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

View File

@ -1,11 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,17 +0,0 @@
<!--
Please provide a high-level description of the changes made by your pull request.
Include references to all related GitHub issues and other pull requests, for example:
Fixes #123
Implements #254
See also #23
-->
## Checklist
- [ ] DCO (Developer Certificate of Origin) [signed in all commits](https://loopback.io/doc/en/contrib/code-contrib.html)
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)

23
.github/stale.yml vendored
View File

@ -1,23 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- critical
- p1
- major
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been closed due to continued inactivity. Thank you for your understanding.
If you believe this to be in error, please contact one of the code owners,
listed in the `CODEOWNERS` file at the top-level of this repository.

1
.npmrc
View File

@ -1 +0,0 @@
package-lock=false

View File

@ -1,7 +0,0 @@
sudo: false
language: node_js
node_js:
- "8"
- "10"
- "12"
- "14"

View File

@ -1,81 +1,3 @@
2019-01-22, Version 3.4.1
=========================
* fix: return null when findById/findOne returns 404 (Jannis Ötjengerdes)
* style: fix linting (virkt25)
2018-06-21, Version 3.4.0
=========================
* move dependencies (Stephen Crawford)
* chore: drop node 4 and update deps (Taranveer Virk)
2018-01-16, Version 3.3.1
=========================
* chore: update copyright notice years (Miroslav Bajtoš)
* Fix duplicate definition of a remote model type (maxim.sharai)
2017-12-12, Version 3.3.0
=========================
* Gruntfile: remove forgotten jshint task (Miroslav Bajtoš)
* Preserve related models from "include" filter (Dimitris)
* Add eslint to npm test, fix linter issues (Miroslav Bajtoš)
* Refactor tests to use local per-app model registry (Miroslav Bajtoš)
2017-12-05, Version 3.2.0
=========================
* Add support for configuring remoting options (Kenny Sabir)
* chore:update license (Diana Lau)
* Move remote connector doc into README (crandmck)
* build: enable Travis CI (Miroslav Bajtoš)
* Add stalebot configuration (Kevin Delisle)
* Update Issue and PR Templates (#76) (Sakib Hasan)
* Add CODEOWNER file (Diana Lau)
* Replicate new issue_template from loopback (Siddhi Pai)
* Replicate issue_template from loopback repo (Siddhi Pai)
* Add "options" arg to stubbed models in tests (Miroslav Bajtoš)
2016-12-21, Version 3.1.1
=========================
* Update package.json for LB3 release (Simon Ho)
* Update paid support URL (Siddhi Pai)
2016-12-05, Version 3.1.0
=========================
* Drop support for Node v0.10 and v0.12 (Miroslav Bajtoš)
* Fix version info in README (Miroslav Bajtoš)
* Update deps to loopback 3.0.0 RC (Miroslav Bajtoš)
2016-09-22, Version 3.0.0
=========================

View File

@ -1,6 +0,0 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners,
# the last matching pattern has the most precendence.
# Core team members from IBM
* @bajtos

View File

@ -19,20 +19,133 @@ Contributing to `loopback-connector-remote` is easy. In a few simple steps:
* Submit a pull request through Github.
### Developer Certificate of Origin
This project uses [DCO](https://developercertificate.org/). Be sure to sign off
your commits using the `-s` flag or adding `Signed-off-By: Name<Email>` in the
commit message.
**Example**
### Contributor License Agreement ###
```
git commit -s -m "feat: my commit message"
```
Individual Contributor License Agreement
Also see the [Contributing to LoopBack](https://loopback.io/doc/en/contrib/code-contrib.html) to get you started.
By signing this Individual Contributor License Agreement
("Agreement"), and making a Contribution (as defined below) to
StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and
agree to the following terms and conditions for Your present and
future Contributions submitted to StrongLoop. Except for the license
granted in this Agreement to StrongLoop and recipients of software
distributed by StrongLoop, You reserve all right, title, and interest
in and to Your Contributions.
1. Definitions
"You" or "Your" shall mean the copyright owner or the individual
authorized by the copyright owner that is entering into this
Agreement with StrongLoop.
"Contribution" shall mean any original work of authorship,
including any modifications or additions to an existing work, that
is intentionally submitted by You to StrongLoop for inclusion in,
or documentation of, any of the products owned or managed by
StrongLoop ("Work"). For purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication
sent to StrongLoop or its representatives, including but not
limited to communication or electronic mailing lists, source code
control systems, and issue tracking systems that are managed by,
or on behalf of, StrongLoop for the purpose of discussing and
improving the Work, but excluding communication that is
conspicuously marked or otherwise designated in writing by You as
"Not a Contribution."
2. You Grant a Copyright License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and recipients of software distributed by
StrongLoop, a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare
derivative works of, publicly display, publicly perform,
sublicense, and distribute Your Contributions and such derivative
works under any license and without any restrictions.
3. You Grant a Patent License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and to recipients of software distributed by
StrongLoop a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this Section)
patent license to make, have made, use, offer to sell, sell,
import, and otherwise transfer the Work under any license and
without any restrictions. The patent license You grant to
StrongLoop under this Section applies only to those patent claims
licensable by You that are necessarily infringed by Your
Contributions(s) alone or by combination of Your Contributions(s)
with the Work to which such Contribution(s) was submitted. If any
entity institutes a patent litigation against You or any other
entity (including a cross-claim or counterclaim in a lawsuit)
alleging that Your Contribution, or the Work to which You have
contributed, constitutes direct or contributory patent
infringement, any patent licenses granted to that entity under
this Agreement for that Contribution or Work shall terminate as
of the date such litigation is filed.
4. You Have the Right to Grant Licenses to StrongLoop
You represent that You are legally entitled to grant the licenses
in this Agreement.
If Your employer(s) has rights to intellectual property that You
create, You represent that You have received permission to make
the Contributions on behalf of that employer, that Your employer
has waived such rights for Your Contributions, or that Your
employer has executed a separate Corporate Contributor License
Agreement with StrongLoop.
5. The Contributions Are Your Original Work
You represent that each of Your Contributions are Your original
works of authorship (see Section 8 (Submissions on Behalf of
Others) for submission on behalf of others). You represent that to
Your knowledge, no other person claims, or has the right to claim,
any right in any intellectual property right related to Your
Contributions.
You also represent that You are not legally obligated, whether by
entering into an agreement or otherwise, in any way that conflicts
with the terms of this Agreement.
You represent that Your Contribution submissions include complete
details of any third-party license or other restriction (including,
but not limited to, related patents and trademarks) of which You
are personally aware and which are associated with any part of
Your Contributions.
6. You Don't Have an Obligation to Provide Support for Your Contributions
You are not expected to provide support for Your Contributions,
except to the extent You desire to provide support. You may provide
support for free, for a fee, or not at all.
6. No Warranties or Conditions
StrongLoop acknowledges that unless required by applicable law or
agreed to in writing, You provide Your Contributions on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR
FITNESS FOR A PARTICULAR PURPOSE.
7. Submission on Behalf of Others
If You wish to submit work that is not Your original creation, You
may submit it to StrongLoop separately from any Contribution,
identifying the complete details of its source and of any license
or other restriction (including, but not limited to, related
patents, trademarks, and license agreements) of which You are
personally aware, and conspicuously marking the work as
"Submitted on Behalf of a Third-Party: [named here]".
8. Agree to Notify of Change of Circumstances
You agree to notify StrongLoop of any facts or circumstances of
which You become aware that would make these representations
inaccurate in any respect. Email us at callback@strongloop.com.
```
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml

View File

@ -1,10 +1,9 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
@ -16,38 +15,51 @@ module.exports = function(grunt) {
'* Copyright (c) <%= grunt.template.today("yyyy") %> ' +
'<%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
// Task configuration.
jshint: {
options: {
jshintrc: true
},
gruntfile: {
src: 'Gruntfile.js'
},
libTest: {
src: ['lib/**/*.js', 'test/**/*.js']
}
},
mochaTest: {
'integration': {
src: 'test/integration/*.js',
options: {
reporter: 'dot',
},
reporter: 'dot'
}
},
'integration-xml': {
src: 'test/integration/*.js',
options: {
reporter: 'xunit',
captureFile: 'xintegration.xml',
},
captureFile: 'xintegration.xml'
}
},
'unit': {
src: 'test/*.js',
options: {
reporter: 'dot',
},
reporter: 'dot'
}
},
'unit-xml': {
src: 'test/*.js',
options: {
reporter: 'xunit',
captureFile: 'xunit.xml',
},
},
},
captureFile: 'xunit.xml'
}
}
}
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-contrib-jshint');
// Default task.
grunt.registerTask('default', ['unit', 'integration']);

View File

@ -1,4 +1,4 @@
Copyright (c) IBM Corp. 2014,2018. All Rights Reserved.
Copyright (c) IBM Corp. 2014,2016. All Rights Reserved.
Node module: loopback-connector-remote
This project is licensed under the MIT License, full text below.

139
README.md
View File

@ -1,142 +1,41 @@
# loopback-connector-remote
**THIS CONNECTOR DOES NOT SUPPORT LOOPBACK 4**
Remote REST API connector for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler).
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible.
Learn more about
<a href="https://loopback.io/doc/en/contrib/Long-term-support.html">LoopBack's long term support policy.</a>**
- The version range 2.x is compatible with LoopBack v3 and newer.
- Use the older range 1.x for applications using LoopBack v2.
The remote connector enables you to use a LoopBack application as a data source via REST.
You can use the remote connector with a LoopBack application, a Node application, or a browser-based application that uses [LoopBack in the client](LoopBack-in-the-client.html).
The connector uses [Strong Remoting](Strong-Remoting.html).
## Quick Explanation
In general, using the remote connector is more convenient than calling into REST API, and enables you to switch the transport later if you need to.
Use this connector to create a datasource from another Loopback application. Below is a quick example:
Use loopback-connector-remote:
- Version 3.x with LoopBack v3 and later.
- Prior versions with LoopBack v2.
## Installation
In your application root directory, enter:
```shell
$ npm install loopback-connector-remote --save
### datasource.json
```json
"MyMicroService": {
"name": "MyMicroService",
"connector": "remote"
}
```
This will install the module and add it as a dependency to the application's [`package.json`](http://loopback.io/doc/en/lb3/package.json.html) file.
## Creating a remote data source
Create a new remote data source with the [datasource generator](http://loopback.io/doc/en/lb3/Data-source-generator.html):
```shell
$ lb datasource
```
When prompted:
* For connector, scroll down and select **other**.
* For connector name without the loopback-connector- prefix, enter **remote**.
This creates an entry in `datasources.json`; Then you need to edit this to add the data source properties, for example:
{% include code-caption.html content="/server/datasources.json" %}
```javascript
...
"myRemoteDataSource": {
"name": "myRemoteDataSource",
"connector": "remote",
"url": "http://localhost:3000/api"
}
...
```
The `url` property specifies the root URL of the LoopBack API.
Note that you should add a `url` property to point to another remote service.
If you do not specify a `url` property, the remote connector will point to it's own host name, port it's running on, etc.
The connector will generate models on the myRemoteDataSource datasource object based on the models/methods exposed from the remote service. Those models will have methods attached that are
from the model's remote methods. So if the model `foo` exposes a remote method called `bar`,
The connector will generate models on the MyMicroService datasource object based on the models/methods exposed from the remote service. Those models will have methods attached that are
from the model's remote methods. So if you exposed a remote method from that micro-service called `bar` from the model `foo`,
the connector will automatically generate the following:
`app.datasources.myRemoteDataSource.models.foo.bar()`
`app.datasources.MyMicroService.models.foo.bar()`
### Access it in any model file
To access the remote Loopback service in a model:
```javascript
module.exports = function(Message) {
Message.test = function (cb) {
Message.app.datasources.myRemoteDataSource.models.
SomeModel.remoteMethodNameHere(function () {});
Message.test = function (cb) {
Message.app.datasources.MyMicroService.models.SomeModel.remoteMethodNameHere(function () {});
cb(null, {});
};
cb(null, {});
};
};
```
## Remote data source properties
<table>
<tbody>
<tr>
<th>Property</th>
<th width="100">Type</th>
<th>Description</th>
</tr>
<tr>
<td>host</td>
<td>String</td>
<td>Hostname of <span>LoopBack</span> application <span>providing remote data source.</span></td>
</tr>
<tr>
<td>port</td>
<td>Number</td>
<td>Port number of <span>LoopBack</span> application providing remote <span>data source</span>.</td>
</tr>
<tr>
<td>root</td>
<td>String</td>
<td>Path to API root of <span>LoopBack application providing remote <span>data source</span>.</span></td>
</tr>
<tr>
<td>url</td>
<td>String</td>
<td>Full URL of <span>LoopBack application providing remote connector.
Use instead of host, port, and root properties.</span>
</td>
</tr>
</tbody>
</table>
## Configuring authentication
The remote connector does not support JSON-based configuration of the authentication credentials (see [issue #3](https://github.com/strongloop/loopback-connector-remote/issues/3)).
You can use the following code as a workaround. It assumes that your data source is called "remote" and the AccessToken id is provided in the variable "token".
```javascript
app.dataSources.remote.connector.remotes.auth = {
bearer: new Buffer(token).toString('base64'),
sendImmediately: true
};
```
## Using with MongoDB connector
When using the MongoDB connector on the server and a remote connector on the client,
use the following `id` property:
```javascript
"id": {
"type": "string",
"generated": true,
"id": true
}
```

6
index.js Normal file
View File

@ -0,0 +1,6 @@
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = require('./lib/remote-connector');

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
@ -8,8 +8,8 @@
/*!
* Dependencies
*/
const relation = require('loopback-datasource-juggler/lib/relation-definition');
const RelationDefinition = relation.RelationDefinition;
var relation = require('loopback-datasource-juggler/lib/relation-definition');
var RelationDefinition = relation.RelationDefinition;
module.exports = RelationMixin;
@ -75,7 +75,7 @@ function RelationMixin() {
* @property {Object} model Model object
*/
RelationMixin.hasMany = function hasMany(modelTo, params) {
const def = RelationDefinition.hasMany(this, modelTo, params);
var def = RelationDefinition.hasMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -139,7 +139,7 @@ RelationMixin.hasMany = function hasMany(modelTo, params) {
*
*/
RelationMixin.belongsTo = function(modelTo, params) {
const def = RelationDefinition.belongsTo(this, modelTo, params);
var def = RelationDefinition.belongsTo(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -179,31 +179,31 @@ RelationMixin.belongsTo = function(modelTo, params) {
*/
RelationMixin.hasAndBelongsToMany =
function hasAndBelongsToMany(modelTo, params) {
const def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
var def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.hasOne = function hasOne(modelTo, params) {
const def = RelationDefinition.hasOne(this, modelTo, params);
var def = RelationDefinition.hasOne(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.referencesMany = function referencesMany(modelTo, params) {
const def = RelationDefinition.referencesMany(this, modelTo, params);
var def = RelationDefinition.referencesMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.embedsOne = function embedsOne(modelTo, params) {
const def = RelationDefinition.embedsOne(this, modelTo, params);
var def = RelationDefinition.embedsOne(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.embedsMany = function embedsMany(modelTo, params) {
const def = RelationDefinition.embedsMany(this, modelTo, params);
var def = RelationDefinition.embedsMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -211,23 +211,10 @@ RelationMixin.embedsMany = function embedsMany(modelTo, params) {
function defineRelationProperty(modelClass, def) {
Object.defineProperty(modelClass.prototype, def.name, {
get: function() {
const that = this;
const scope = function() {
const cachedEntities = that.__cachedRelations &&
that.__cachedRelations[def.name];
if (arguments.length || !cachedEntities) {
return that['__get__' + def.name].apply(that, arguments);
}
// return the cached data
if (Array.isArray(cachedEntities)) {
return cachedEntities.map(data => new def.modelTo(data));
} else {
return new def.modelTo(cachedEntities);
}
var that = this;
var scope = function() {
return that['__get__' + def.name].apply(that, arguments);
};
scope.count = function() {
return that['__count__' + def.name].apply(that, arguments);
};
@ -244,6 +231,6 @@ function defineRelationProperty(modelClass, def) {
return that['__findById__' + def.name].apply(that, arguments);
};
return scope;
},
}
});
}

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
@ -9,14 +9,12 @@
* Dependencies.
*/
const assert = require('assert');
const remoting = require('strong-remoting');
const utils = require('loopback-datasource-juggler/lib/utils');
const jutil = require('loopback-datasource-juggler/lib/jutil');
const RelationMixin = require('./relations');
const InclusionMixin = require('loopback-datasource-juggler/lib/include');
const findMethodNames = ['findById', 'findOne'];
var assert = require('assert');
var remoting = require('strong-remoting');
var utils = require('loopback-datasource-juggler/lib/utils');
var jutil = require('loopback-datasource-juggler/lib/jutil');
var RelationMixin = require('./relations');
var InclusionMixin = require('loopback-datasource-juggler/lib/include');
/**
* Export the RemoteConnector class.
@ -31,14 +29,14 @@ module.exports = RemoteConnector;
function RemoteConnector(settings) {
assert(typeof settings ===
'object',
'cannot initialize RemoteConnector without a settings object');
'cannot initiaze RemoteConnector without a settings object');
this.client = settings.client;
this.adapter = settings.adapter || 'rest';
this.protocol = settings.protocol || 'http';
this.root = settings.root || '';
this.host = settings.host || 'localhost';
this.port = settings.port || 3000;
this.remotes = remoting.create(settings.options);
this.remotes = remoting.create();
this.name = 'remote-connector';
if (settings.url) {
@ -48,8 +46,9 @@ function RemoteConnector(settings) {
}
// handle mixins in the define() method
const DAO = this.DataAccessObject = function() {
var DAO = this.DataAccessObject = function() {
};
}
RemoteConnector.prototype.connect = function() {
@ -57,95 +56,64 @@ RemoteConnector.prototype.connect = function() {
};
RemoteConnector.initialize = function(dataSource, callback) {
const connector = dataSource.connector =
var connector = dataSource.connector =
new RemoteConnector(dataSource.settings);
connector.connect();
process.nextTick(callback);
};
RemoteConnector.prototype.define = function(definition) {
const Model = definition.model;
const remotes = this.remotes;
var Model = definition.model;
var remotes = this.remotes;
assert(Model.sharedClass,
'cannot attach ' +
'cannot attach ' +
Model.modelName +
' to a remote connector without a Model.sharedClass');
jutil.mixin(Model, RelationMixin);
jutil.mixin(Model, InclusionMixin);
remotes.addClass(Model.sharedClass);
this.resolve(Model);
this.setupRemotingTypeFor(Model);
};
RemoteConnector.prototype.resolve = function(Model) {
const remotes = this.remotes;
var remotes = this.remotes;
Model.sharedClass.methods().forEach(function(remoteMethod) {
if (remoteMethod.name !== 'Change' && remoteMethod.name !== 'Checkpoint') {
createProxyMethod(Model, remotes, remoteMethod);
}
});
};
RemoteConnector.prototype.setupRemotingTypeFor = function(Model) {
const remotes = this.remotes;
// setup a remoting type converter for this model
remotes.defineObjectType(Model.modelName, function(data) {
const model = new Model(data);
// process cached relations
if (model.__cachedRelations) {
for (const relation in model.__cachedRelations) {
const relatedModel = model.__cachedRelations[relation];
model.__data[relation] = relatedModel;
}
}
return model;
return new Model(data);
});
};
function createProxyMethod(Model, remotes, remoteMethod) {
const scope = remoteMethod.isStatic ? Model : Model.prototype;
const original = scope[remoteMethod.name];
var scope = remoteMethod.isStatic ? Model : Model.prototype;
var original = scope[remoteMethod.name];
function remoteMethodProxy() {
const args = Array.prototype.slice.call(arguments);
const lastArgIsFunc = typeof args[args.length - 1] === 'function';
let callback;
var args = Array.prototype.slice.call(arguments);
var lastArgIsFunc = typeof args[args.length - 1] === 'function';
var callback;
if (lastArgIsFunc) {
callback = args.pop();
} else {
callback = utils.createPromiseCallback();
}
const callbackPromise = callback.promise;
if (findMethodNames.includes(remoteMethod.name)) {
callback = proxy404toNull(callback);
}
if (remoteMethod.isStatic) {
remotes.invoke(remoteMethod.stringName, args, callback);
} else {
const ctorArgs = [this.id];
var ctorArgs = [this.id];
remotes.invoke(remoteMethod.stringName, ctorArgs, args, callback);
}
return callbackPromise;
}
function proxy404toNull(cb) {
return function(err, data) {
if (err && err.code === 'MODEL_NOT_FOUND') {
cb(null, null);
return;
}
cb(err, data);
};
return callback.promise;
}
scope[remoteMethod.name] = remoteMethodProxy;

View File

@ -1,8 +1,11 @@
{
"name": "loopback-connector-remote",
"version": "3.4.1",
"version": "3.0.0",
"pubishConfig": {
"tag": "next"
},
"description": "Remote REST API connector for Loopback",
"main": "lib/remote-connector.js",
"main": "index.js",
"keywords": [
"web",
"restful",
@ -10,13 +13,7 @@
"StrongLoop"
],
"scripts": {
"test": "grunt",
"posttest": "npm run lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"engines": {
"node": ">=8"
"test": "grunt"
},
"repository": {
"type": "git",
@ -33,21 +30,18 @@
"homepage": "http://loopback.io",
"dependencies": {
"loopback-datasource-juggler": "^3.0.0",
"strong-remoting": "^3.15.0"
"strong-remoting": "^3.0.0"
},
"devDependencies": {
"assert": "^1.4.1",
"bluebird": "^3.5.1",
"eslint": "^6.5.1",
"eslint-config-loopback": "^13.1.0",
"grunt": "^1.0.3",
"assert": "^1.1.2",
"bluebird": "^3.3.5",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-mocha-test": "^0.13.3",
"loopback": "^3.0.0",
"mocha": "^5.2.0",
"sinon": "^6.0.0",
"strong-task-emitter": "^0.0.8"
"grunt-contrib-jshint": "^1.0.0",
"grunt-mocha-test": "^0.12.7",
"loopback": "^3.0.0-alpha.5",
"mocha": "^3.0.2",
"strong-task-emitter": "^0.0.7"
},
"optionalDependencies": {},
"author": "IBM Corp."
"optionalDependencies": {}
}

View File

@ -1,28 +1,29 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Copyright IBM Corp. 2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const extend = require('util')._extend;
const loopback = require('loopback');
const remoteConnector = require('..');
var extend = require('util')._extend;
var loopback = require('loopback');
var remoteConnector = require('..');
exports.createMemoryDataSource = createMemoryDataSource;
exports.createModel = createModel;
exports.createRemoteDataSource = createRemoteDataSource;
exports.createRestAppAndListen = createRestAppAndListen;
exports.getUserProperties = getUserProperties;
function createRestAppAndListen() {
const app = loopback({localRegistry: true});
var app = loopback();
app.set('host', '127.0.0.1');
app.set('port', 0);
app.set('legacyExplorer', false);
app.set('remoting', {
errorHandler: {debug: true, log: false},
errorHandler: { debug: true, log: false },
context: false,
});
@ -32,17 +33,30 @@ function createRestAppAndListen() {
return app;
}
function createMemoryDataSource(app) {
return app.dataSource('db', {connector: 'memory'});
function createMemoryDataSource() {
return loopback.createDataSource({connector: 'memory'});
}
function createRemoteDataSource(app, serverApp) {
return app.dataSource('remote', {
url: 'http://' + serverApp.get('host') + ':' + serverApp.get('port'),
connector: remoteConnector,
function createRemoteDataSource(remoteApp) {
return loopback.createDataSource({
url: 'http://' + remoteApp.get('host') + ':' + remoteApp.get('port'),
connector: remoteConnector
});
}
/**
* Used to create models based on a set of options. May associate or link to an
* app.
*/
function createModel(options) {
var modelOptions = extend({ forceId: false }, options.options);
var Model = loopback.PersistedModel.extend(options.parent, options.properties,
modelOptions);
if (options.app) options.app.model(Model);
if (options.datasource) Model.attachTo(options.datasource);
return Model;
}
function getUserProperties() {
return {
'first': String,
@ -51,6 +65,6 @@ function getUserProperties() {
'password': String,
'gender': String,
'domain': String,
'email': String,
'email': String
};
}

View File

@ -1,16 +1,9 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var assert = require('assert');
var helper = require('../helper');
var Promise = require('bluebird');
'use strict';
const assert = require('assert');
const helper = require('../helper');
const Promise = require('bluebird');
let globalPromiseSetManually = false;
let User;
var globalPromiseSetManually = false;
var User;
describe('promise support', function() {
before(setGlobalPromise);
@ -19,21 +12,21 @@ describe('promise support', function() {
context('create', function() {
it('supports promises', function() {
const retval = User.create();
var retval = User.create();
assert(retval && typeof retval.then === 'function');
});
});
context('find', function() {
it('supports promises', function() {
const retval = User.find();
var retval = User.find();
assert(retval && typeof retval.then === 'function');
});
});
context('findById', function() {
it('supports promises', function() {
const retval = User.findById(1);
var retval = User.findById(1);
assert(retval && typeof retval.then === 'function');
});
});
@ -47,15 +40,12 @@ function setGlobalPromise() {
}
function createUserModel() {
const app = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
User = app.registry.createModel({
name: 'user',
properties: helper.getUserProperties(),
options: {forceId: false},
User = helper.createModel({
parent: 'user',
app: helper.createRestAppAndListen(),
datasource: helper.createMemoryDataSource(),
properties: helper.getUserProperties()
});
app.model(User, {dataSource: db});
}
function resetGlobalPromise() {

View File

@ -1,77 +0,0 @@
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const helper = require('./helper');
const loopback = require('loopback');
const sinon = require('sinon');
const relation = require('loopback-datasource-juggler/lib/relation-definition');
const RelationTypes = relation.RelationTypes;
describe('Models Define Type Tests', function() {
let serverApp, clientApp, remoteDs, defineObjectTypeSpy, ChildModel;
beforeEach('create remote datasource', () => {
serverApp = helper.createRestAppAndListen();
clientApp = loopback({localRegistry: true});
remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
});
beforeEach('spy remote connector', () => {
defineObjectTypeSpy = sinon.spy(remoteDs.connector.remotes,
'defineObjectType');
});
afterEach('restore remote connector', () => {
defineObjectTypeSpy.restore();
});
it('should define a type of a remote model only once (no relations)', () => {
const RemoteModel = clientApp.registry.createModel({
name: 'RemoteModel',
});
clientApp.model(RemoteModel, {dataSource: remoteDs});
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
RemoteModel.modelName,
));
});
describe('when a child model is created', () => {
beforeEach('create a child model', () => {
ChildModel = clientApp.registry.createModel({
name: 'ChildModel',
});
clientApp.model(ChildModel, {dataSource: remoteDs});
});
Object.getOwnPropertyNames(RelationTypes).forEach((relationKey) => {
const relation = RelationTypes[relationKey];
it('should define a type of a remote model only once (' + relation + ')',
() => {
const RemoteModel = clientApp.registry.createModel({
name: 'RemoteModel' + relation,
relations: {
children: {
type: relation,
model: 'ChildModel',
},
},
});
clientApp.model(RemoteModel, {dataSource: remoteDs});
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
RemoteModel.modelName,
));
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
ChildModel.modelName,
));
});
});
});
});

View File

@ -1,69 +1,65 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Copyright IBM Corp. 2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
var assert = require('assert');
var helper = require('./helper');
var TaskEmitter = require('strong-task-emitter');
describe('Model tests', function() {
let User;
var User;
beforeEach(function() {
const app = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
User = app.registry.createModel({
name: 'user',
properties: helper.getUserProperties(),
options: {forceId: false},
User = helper.createModel({
parent: 'user',
app: helper.createRestAppAndListen(),
datasource: helper.createMemoryDataSource(),
properties: helper.getUserProperties()
});
app.model(User, {dataSource: db});
});
describe('Model.validatesPresenceOf(properties...)', function() {
it('should require a model to include a property to be considered valid',
function() {
User.validatesPresenceOf('first', 'last', 'age');
const joe = new User({first: 'joe'});
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');
});
function() {
User.validatesPresenceOf('first', 'last', 'age');
var joe = new User({first: 'joe'});
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');
});
});
describe('Model.validatesLengthOf(property, options)', function() {
it('should require a property length to be within a specified range',
function() {
User.validatesLengthOf('password', {min: 5, message: {min:
function() {
User.validatesLengthOf('password', {min: 5, message: {min:
'Password is too short'}});
const joe = new User({password: '1234'});
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
});
var joe = new User({password: '1234'});
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
});
});
describe('Model.validatesInclusionOf(property, options)', function() {
it('should require a value for `property` to be in the specified array',
function() {
User.validatesInclusionOf('gender', {in: ['male', 'female']});
const foo = new User({gender: 'bar'});
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
});
function() {
User.validatesInclusionOf('gender', {in: ['male', 'female']});
var foo = new User({gender: 'bar'});
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
});
});
describe('Model.validatesExclusionOf(property, options)', function() {
it('should require a value for `property` to not exist in the specified ' +
'array', function() {
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
const foo = new User({domain: 'www'});
const bar = new User({domain: 'billing'});
const bat = new User({domain: 'admin'});
var foo = new User({domain: 'www'});
var bar = new User({domain: 'billing'});
var bat = new User({domain: 'admin'});
assert(foo.isValid() === false);
assert(bar.isValid() === false);
assert(bat.isValid() === false);
@ -77,9 +73,9 @@ describe('Model tests', function() {
it('should require a value for `property` to be a specific type of ' +
'`Number`', function() {
User.validatesNumericalityOf('age', {int: true});
const joe = new User({age: 10.2});
var joe = new User({age: 10.2});
assert(joe.isValid() === false);
const bob = new User({age: 0});
var bob = new User({age: 0});
assert(bob.isValid() === true);
assert(joe.errors.age, 'model should have an age error');
});
@ -88,15 +84,15 @@ describe('Model tests', function() {
describe('myModel.isValid()', function() {
it('should validate the model instance', function() {
User.validatesNumericalityOf('age', {int: true});
const user = new User({first: 'joe', age: 'flarg'});
const valid = user.isValid();
var user = new User({first: 'joe', age: 'flarg'});
var valid = user.isValid();
assert(valid === false);
assert(user.errors.age, 'model should have age error');
});
it('should validate the model asynchronously', function(done) {
User.validatesNumericalityOf('age', {int: true});
const user = new User({first: 'joe', age: 'flarg'});
var user = new User({first: 'joe', age: 'flarg'});
user.isValid(function(valid) {
assert(valid === false);
assert(user.errors.age, 'model should have age error');
@ -107,63 +103,63 @@ describe('Model tests', function() {
describe('Model.create([data], [callback])', function() {
it('should create an instance and save to the attached data source',
function(done) {
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof User);
done();
});
function(done) {
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof User);
done();
});
});
});
describe('model.save([options], [callback])', function() {
it('should save an instance of a Model to the attached data source',
function(done) {
const joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
function(done) {
var joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
});
});
describe('model.updateAttributes(data, [callback])', function() {
it('should save specified attributes to the attached data source',
function(done) {
User.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
function(done) {
User.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast',
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
});
});
});
describe('Model.upsert(data, callback)', function() {
it('should update when a record with id=data.id is found, insert otherwise',
function(done) {
User.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
function(done) {
User.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
});
});
});
describe('model.destroy([callback])', function() {
@ -188,19 +184,19 @@ describe('Model tests', function() {
describe('Model.deleteById(id, [callback])', function() {
it('should delete a model instance from the attached data source',
function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
User.deleteById(user.id, function(err) {
if (err) return done(err);
User.deleteById(user.id, function(err) {
User.findById(user.id, function(err, notFound) {
if (err) return done(err);
User.findById(user.id, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
assert.equal(notFound, null);
done();
});
});
});
});
});
describe('Model.findById(id, callback)', function() {
@ -221,21 +217,21 @@ describe('Model tests', function() {
describe('Model.count([query], callback)', function() {
it('should return the count of Model instances in data source',
function(done) {
const taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', {first: 'jill', age: 100})
.task(User, 'create', {first: 'bob', age: 200})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
});
function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', {first: 'jill', age: 100})
.task(User, 'create', {first: 'bob', age: 200})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
});
});
});
});
});
});

View File

@ -1,88 +1,75 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
var assert = require('assert');
var helper = require('./helper');
describe('RemoteConnector', function() {
let serverApp, clientApp, ServerModel, ClientModel;
var ctx = this;
before(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource()
});
app.model(ServerModel, {dataSource: db});
app.locals.handler.on('listening', function() { done(); });
ctx.serverApp.locals.handler.on('listening', function() { done(); });
});
before(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientModel = app.registry.createModel({
name: 'TestModel',
before(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp)
});
app.model(ClientModel, {dataSource: remoteDs});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
});
after(function() {
serverApp.locals.handler.close();
ServerModel = null;
ClientModel = null;
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
});
it('should support the save method', function(done) {
let calledServerCreate = false;
ServerModel.create = function(data, options, cb, callback) {
if (typeof options === 'function') {
callback = cb;
cb = options;
options = {};
}
var calledServerCreate = false;
ctx.ServerModel.create = function(data, cb, callback) {
calledServerCreate = true;
data.id = 1;
if (callback) callback(null, data);
else cb(null, data);
};
const m = new ClientModel({foo: 'bar'});
var m = new ctx.RemoteModel({foo: 'bar'});
m.save(function(err, instance) {
if (err) return done(err);
assert(instance);
assert(instance instanceof ClientModel);
assert(instance instanceof ctx.RemoteModel);
assert(calledServerCreate);
done();
});
});
it('should support aliases', function(done) {
let calledServerUpsert = false;
ServerModel.patchOrCreate =
ServerModel.upsert = function(id, options, cb) {
if (typeof options === 'function') {
cb = options;
options = {};
}
var calledServerUpsert = false;
ctx.ServerModel.patchOrCreate =
ctx.ServerModel.upsert = function(id, cb) {
calledServerUpsert = true;
cb();
};
ClientModel.updateOrCreate({}, function(err, instance) {
ctx.RemoteModel.updateOrCreate({}, function(err, instance) {
if (err) return done(err);
assert(instance);
assert(instance instanceof ClientModel);
assert(instance instanceof ctx.RemoteModel);
assert(calledServerUpsert, 'server upsert should have been called');
done();
});
@ -90,61 +77,49 @@ describe('RemoteConnector', function() {
});
describe('Custom Path', function() {
let serverApp, clientApp, ServerModel, ClientModel;
var ctx = this;
before(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource(),
options: {
http: {path: '/custom'},
},
http: {path: '/custom'}
}
});
app.model(ServerModel, {dataSource: db});
serverApp.locals.handler.on('listening', function() { done(); });
ctx.serverApp.locals.handler.on('listening', function() { done(); });
});
before(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientModel = app.registry.createModel({
name: 'TestModel',
before(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp),
options: {
dataSource: 'remote',
http: {path: '/custom'},
},
http: {path: '/custom'}
}
});
app.model(ClientModel, {dataSource: remoteDs});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
});
after(function() {
serverApp.locals.handler.close();
ServerModel = null;
ClientModel = null;
after(function(done)
{
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
done();
});
it('should support http.path configuration', function(done) {
ClientModel.create({}, function(err, instance) {
ctx.RemoteModel.create({}, function(err, instance) {
if (err) return done(err);
assert(instance);
done();
});
});
});
describe('RemoteConnector with options', () => {
it('should have the remoting options passed to the remote object', () => {
const app = loopback();
const dataSource = app.dataSource('remote', {
url: 'http://example.com',
connector: require('..'),
options: {'test': 'abc'},
});
assert.deepEqual(dataSource.connector.remotes.options, {test: 'abc'});
});
});

View File

@ -1,389 +1,161 @@
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Copyright IBM Corp. 2016. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
var assert = require('assert');
var helper = require('./helper');
var TaskEmitter = require('strong-task-emitter');
describe('Remote model tests', function() {
let serverApp, ServerModel, ServerRelatedModel, ServerModelWithSingleChild,
clientApp, ClientModel, ClientRelatedModel, ClientModelWithSingleChild;
var ctx = this;
beforeEach(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
beforeEach(function(done) {
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource(),
properties: helper.userProperties
});
ServerModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
ServerRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {forceId: false},
});
app.model(ServerModel, {dataSource: db});
app.model(ServerRelatedModel, {dataSource: db});
app.model(ServerModelWithSingleChild, {dataSource: db});
serverApp.locals.handler.on('listening', function() { done(); });
ctx.serverApp.locals.handler.on('listening', function() { done(); });
});
beforeEach(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {
strict: true,
},
beforeEach(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp),
properties: helper.userProperties
});
ClientModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
ClientModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
app.model(ClientModel, {dataSource: remoteDs});
app.model(ClientRelatedModel, {dataSource: remoteDs});
app.model(ClientModelWithSingleChild, {dataSource: remoteDs});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
});
afterEach(function() {
serverApp.locals.handler.close();
ServerModel = null;
ServerRelatedModel = null;
ClientModel = null;
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
});
describe('Model.create([data], [callback])', function() {
it('should create an instance and save to the attached data source',
function(done) {
ClientModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ClientModel);
done();
});
function(done) {
ctx.RemoteModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ctx.RemoteModel);
done();
});
});
});
describe('model.save([options], [callback])', function() {
it('should save an instance of a Model to the attached data source',
function(done) {
const joe = new ClientModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
function(done) {
var joe = new ctx.RemoteModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
});
});
describe('model.updateAttributes(data, [callback])', function() {
it('should save specified attributes to the attached data source',
function(done) {
ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
function(done) {
ctx.ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast',
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
});
});
});
describe('Model.upsert(data, callback)', function() {
it('should update when a record with id=data.id is found, insert otherwise',
function(done) {
ClientModel.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
function(done) {
ctx.RemoteModel.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
ClientModel.upsert({first: 'bob', id: 7}, function(err,
ctx.RemoteModel.upsert({first: 'bob', id: 7}, function(err,
updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
});
});
});
describe('Model.deleteById(id, [callback])', function() {
it('should delete a model instance from the attached data source',
function(done) {
ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
function(done) {
ctx.ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
ctx.RemoteModel.deleteById(user.id, function(err) {
if (err) return done(err);
ClientModel.deleteById(user.id, function(err) {
if (err) return done(err);
ClientModel.findById(user.id, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
});
});
});
describe('Model.exists(id, callback)', function() {
it('should return true when the model with the given id exists',
function(done) {
ServerModel.create({first: 'max'}, function(err, user) {
if (err) return done(err);
ClientModel.exists(user.id, function(err, exist) {
if (err) return done(err);
assert.equal(exist, true);
ctx.RemoteModel.findById(user.id, function(err, notFound) {
assert.equal(notFound, null);
assert(err && err.statusCode === 404,
'should have failed with HTTP 404');
done();
});
});
});
it('should return false when there is no model with the given id',
function(done) {
ClientModel.exists('user-id-does-not-exist', function(err, exist) {
if (err) return done(err);
assert.equal(exist, false);
done();
});
});
});
});
describe('Model.findById(id, callback)', function() {
it('should return null when an instance does not exist',
function(done) {
ClientModel.findById(23, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
it('should find an instance by id from the attached data source',
function(done) {
ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(done) {
ctx.ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(err) {
if (err) return done(err);
ClientModel.findById(23, function(err, user) {
if (err) return done(err);
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
done();
});
});
});
});
describe('Model.findOne([filter], callback)', function() {
it('should return null when an instance does not exist',
function(done) {
ClientModel.findOne({where: {id: 24}}, function(err, notFound) {
if (err) return done(err);
ctx.RemoteModel.findById(23, function(err, user) {
if (err) return done(err);
assert.equal(notFound, null);
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
done();
});
});
it('should find an instance from the attached data source',
function(done) {
ServerModel.create({first: 'keanu', last: 'reeves', id: 24},
function(err) {
if (err) return done(err);
ClientModel.findOne({where: {id: 24}}, function(err, user) {
if (err) return done(err);
assert.equal(user.id, 24);
assert.equal(user.first, 'keanu');
assert.equal(user.last, 'reeves');
done();
});
});
});
});
});
describe('Model.count([query], callback)', function() {
it('should return the count of Model instances from both data source',
function(done) {
const taskEmitter = new TaskEmitter();
taskEmitter
.task(ServerModel, 'create', {first: 'jill', age: 100})
.task(ClientModel, 'create', {first: 'bob', age: 200})
.task(ClientModel, 'create', {first: 'jan'})
.task(ServerModel, 'create', {first: 'sam'})
.task(ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(ctx.ServerModel, 'create', {first: 'jill', age: 100})
.task(ctx.RemoteModel, 'create', {first: 'bob', age: 200})
.task(ctx.RemoteModel, 'create', {first: 'jan'})
.task(ctx.ServerModel, 'create', {first: 'sam'})
.task(ctx.ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
if (err) return done(err);
ctx.RemoteModel.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
ClientModel.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
});
assert.equal(count, 2);
done();
});
});
});
describe('Model find with include filter', function() {
let hasManyParent, hasManyChild, hasOneParent, hasOneChild;
beforeEach(givenSampleData);
it('should return also the included requested models', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(hasManyParent.id, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const user = returnedUser.toJSON();
assert.equal(user.id, parentId);
assert.equal(user.first, hasManyParent.first);
assert(Array.isArray(user.children));
assert.equal(user.children.length, 1);
assert.deepEqual(user.children[0], hasManyChild.toJSON());
});
});
it('should return cachedRelated entity without call', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(parentId, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const children = returnedUser.children();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasManyParent.first);
assert(Array.isArray(children));
assert.equal(children.length, 1);
assert(children[0] instanceof ClientRelatedModel);
assert.deepEqual(children[0].toJSON(), hasManyChild.toJSON());
});
});
it('should also work for single (non array) relations', function() {
const parentId = hasOneParent.id;
return ClientModelWithSingleChild.findById(parentId, {include: 'child'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModelWithSingleChild);
const child = returnedUser.child();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasOneParent.first);
assert(child instanceof ClientRelatedModel);
assert.deepEqual(child.toJSON(), hasOneChild.toJSON());
});
});
function givenSampleData() {
return ServerModel.create({first: 'eiste', last: 'kopries'})
.then(parent => {
hasManyParent = parent;
return ServerRelatedModel.create({
note: 'mitsos',
parentId: parent.id,
id: 11,
});
})
.then(child => {
hasManyChild = child;
return ServerModelWithSingleChild.create({
first: 'mipos',
last: 'tora',
id: 12,
});
})
.then(parent => {
hasOneParent = parent;
return ServerRelatedModel.create({
note: 'mitsos3',
parentId: parent.id,
id: 13,
});
})
.then(child => {
hasOneChild = child;
});
}
});
describe('Model.updateAll([where], [data])', () => {
it('returns the count of updated instances in data source', async () => {
await ServerModel.create({first: 'baby', age: 1});
await ServerModel.create({first: 'grandma', age: 80});
const result = await ClientModel.updateAll(
{age: {lt: 6}},
{last: 'young'},
);
assert.deepEqual(result, {count: 1});
});
});
});