Compare commits
72 Commits
Author | SHA1 | Date |
---|---|---|
|
a529986c65 | |
|
c362bcf930 | |
|
64c88ab382 | |
|
4b5ac98379 | |
|
2120e96dbf | |
|
be758fa770 | |
|
b8fd18e217 | |
|
334b705c69 | |
|
4bfa461229 | |
|
4f8a372d2c | |
|
22ee132941 | |
|
1975481cb0 | |
|
c03161dfe0 | |
|
39771be12f | |
|
4ff55feb24 | |
|
0097580537 | |
|
4efca1f052 | |
|
2aff46a79a | |
|
6caf45d39e | |
|
acbe35879b | |
|
e5e4ba93ef | |
|
61f031aeb9 | |
|
59f70ab3ce | |
|
3e7537e1dd | |
|
4245df5c41 | |
|
4108db8945 | |
|
1a7b1be96d | |
|
6e09d0b4bf | |
|
ae64f198ae | |
|
a504e25752 | |
|
e2dad4e39a | |
|
974694b526 | |
|
0c00806f77 | |
|
60c081f893 | |
|
823a20f89d | |
|
af125c50cc | |
|
906979a960 | |
|
d622ce5ed8 | |
|
4ec0ec5341 | |
|
a3d110b78c | |
|
ffaaec8973 | |
|
55cb88f727 | |
|
d6a5c768fd | |
|
fafc0e36f3 | |
|
607fec76be | |
|
816e989b4e | |
|
07d6e1b66e | |
|
1afbe06716 | |
|
8e0c39b47f | |
|
ed20519ce7 | |
|
80f29b9c91 | |
|
94b2eba6ab | |
|
434840522c | |
|
1cf92430c5 | |
|
ab5992c3b9 | |
|
468c4ae0a7 | |
|
94cb7f08a9 | |
|
7af85add04 | |
|
1534626f01 | |
|
1168bb6080 | |
|
45fa573432 | |
|
7259631821 | |
|
d728e57276 | |
|
21ed5a9b93 | |
|
0d687896a9 | |
|
1506a5039e | |
|
352b38128a | |
|
832e990337 | |
|
5db2240348 | |
|
3cf34c4264 | |
|
a1f09dba22 | |
|
625f926a87 |
|
@ -0,0 +1 @@
|
||||||
|
coverage
|
133
.eslintrc
133
.eslintrc
|
@ -1,134 +1,3 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"extends": "loopback"
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
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_
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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.
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
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/
|
||||||
|
|
||||||
|
-->
|
|
@ -0,0 +1,11 @@
|
||||||
|
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.
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!--
|
||||||
|
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)
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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.
|
|
@ -0,0 +1,7 @@
|
||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "8"
|
||||||
|
- "10"
|
||||||
|
- "12"
|
||||||
|
- "14"
|
78
CHANGES.md
78
CHANGES.md
|
@ -1,3 +1,81 @@
|
||||||
|
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
|
2016-09-22, Version 3.0.0
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 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
|
135
CONTRIBUTING.md
135
CONTRIBUTING.md
|
@ -19,133 +19,20 @@ Contributing to `loopback-connector-remote` is easy. In a few simple steps:
|
||||||
* Submit a pull request through Github.
|
* Submit a pull request through Github.
|
||||||
|
|
||||||
|
|
||||||
### Contributor License Agreement ###
|
|
||||||
|
### 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**
|
||||||
|
|
||||||
```
|
```
|
||||||
Individual Contributor License Agreement
|
git commit -s -m "feat: my commit message"
|
||||||
|
|
||||||
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.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Also see the [Contributing to LoopBack](https://loopback.io/doc/en/contrib/code-contrib.html) to get you started.
|
||||||
|
|
||||||
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
|
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
|
||||||
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml
|
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml
|
||||||
|
|
38
Gruntfile.js
38
Gruntfile.js
|
@ -1,9 +1,10 @@
|
||||||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
/*global module:false*/
|
'use strict';
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
// Project configuration.
|
// Project configuration.
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
|
@ -15,51 +16,38 @@ module.exports = function(grunt) {
|
||||||
'* Copyright (c) <%= grunt.template.today("yyyy") %> ' +
|
'* Copyright (c) <%= grunt.template.today("yyyy") %> ' +
|
||||||
'<%= pkg.author.name %>;' +
|
'<%= pkg.author.name %>;' +
|
||||||
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
|
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
|
||||||
// Task configuration.
|
|
||||||
jshint: {
|
|
||||||
options: {
|
|
||||||
jshintrc: true
|
|
||||||
},
|
|
||||||
gruntfile: {
|
|
||||||
src: 'Gruntfile.js'
|
|
||||||
},
|
|
||||||
libTest: {
|
|
||||||
src: ['lib/**/*.js', 'test/**/*.js']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mochaTest: {
|
mochaTest: {
|
||||||
'integration': {
|
'integration': {
|
||||||
src: 'test/integration/*.js',
|
src: 'test/integration/*.js',
|
||||||
options: {
|
options: {
|
||||||
reporter: 'dot'
|
reporter: 'dot',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
'integration-xml': {
|
'integration-xml': {
|
||||||
src: 'test/integration/*.js',
|
src: 'test/integration/*.js',
|
||||||
options: {
|
options: {
|
||||||
reporter: 'xunit',
|
reporter: 'xunit',
|
||||||
captureFile: 'xintegration.xml'
|
captureFile: 'xintegration.xml',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
'unit': {
|
'unit': {
|
||||||
src: 'test/*.js',
|
src: 'test/*.js',
|
||||||
options: {
|
options: {
|
||||||
reporter: 'dot'
|
reporter: 'dot',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
'unit-xml': {
|
'unit-xml': {
|
||||||
src: 'test/*.js',
|
src: 'test/*.js',
|
||||||
options: {
|
options: {
|
||||||
reporter: 'xunit',
|
reporter: 'xunit',
|
||||||
captureFile: 'xunit.xml'
|
captureFile: 'xunit.xml',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// These plugins provide necessary tasks.
|
// These plugins provide necessary tasks.
|
||||||
grunt.loadNpmTasks('grunt-mocha-test');
|
grunt.loadNpmTasks('grunt-mocha-test');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
|
||||||
|
|
||||||
// Default task.
|
// Default task.
|
||||||
grunt.registerTask('default', ['unit', 'integration']);
|
grunt.registerTask('default', ['unit', 'integration']);
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) IBM Corp. 2014,2016. All Rights Reserved.
|
Copyright (c) IBM Corp. 2014,2018. All Rights Reserved.
|
||||||
Node module: loopback-connector-remote
|
Node module: loopback-connector-remote
|
||||||
This project is licensed under the MIT License, full text below.
|
This project is licensed under the MIT License, full text below.
|
||||||
|
|
||||||
|
|
139
README.md
139
README.md
|
@ -1,41 +1,142 @@
|
||||||
# loopback-connector-remote
|
# loopback-connector-remote
|
||||||
|
|
||||||
Remote REST API connector for [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler).
|
**THIS CONNECTOR DOES NOT SUPPORT LOOPBACK 4**
|
||||||
|
|
||||||
- The version range 2.x is compatible with LoopBack v3 and newer.
|
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
|
||||||
- Use the older range 1.x for applications using LoopBack v2.
|
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>**
|
||||||
|
|
||||||
## Quick Explanation
|
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).
|
||||||
|
|
||||||
Use this connector to create a datasource from another Loopback application. Below is a quick example:
|
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.
|
||||||
|
|
||||||
### datasource.json
|
Use loopback-connector-remote:
|
||||||
```json
|
|
||||||
"MyMicroService": {
|
- Version 3.x with LoopBack v3 and later.
|
||||||
"name": "MyMicroService",
|
- Prior versions with LoopBack v2.
|
||||||
"connector": "remote"
|
|
||||||
}
|
## Installation
|
||||||
|
|
||||||
|
In your application root directory, enter:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ npm install loopback-connector-remote --save
|
||||||
```
|
```
|
||||||
Note that you should add a `url` property to point to another remote service.
|
|
||||||
|
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.
|
||||||
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.
|
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 MyMicroService datasource object based on the models/methods exposed from the remote service. Those models will have methods attached that are
|
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 you exposed a remote method from that micro-service called `bar` from the model `foo`,
|
from the model's remote methods. So if the model `foo` exposes a remote method called `bar`,
|
||||||
the connector will automatically generate the following:
|
the connector will automatically generate the following:
|
||||||
|
|
||||||
`app.datasources.MyMicroService.models.foo.bar()`
|
`app.datasources.myRemoteDataSource.models.foo.bar()`
|
||||||
|
|
||||||
### Access it in any model file
|
### Access it in any model file
|
||||||
|
|
||||||
To access the remote Loopback service in a model:
|
To access the remote Loopback service in a model:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
module.exports = function(Message) {
|
module.exports = function(Message) {
|
||||||
|
|
||||||
Message.test = function (cb) {
|
Message.test = function (cb) {
|
||||||
Message.app.datasources.MyMicroService.models.SomeModel.remoteMethodNameHere(function () {});
|
Message.app.datasources.myRemoteDataSource.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
6
index.js
|
@ -1,6 +0,0 @@
|
||||||
// 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');
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
@ -8,8 +8,8 @@
|
||||||
/*!
|
/*!
|
||||||
* Dependencies
|
* Dependencies
|
||||||
*/
|
*/
|
||||||
var relation = require('loopback-datasource-juggler/lib/relation-definition');
|
const relation = require('loopback-datasource-juggler/lib/relation-definition');
|
||||||
var RelationDefinition = relation.RelationDefinition;
|
const RelationDefinition = relation.RelationDefinition;
|
||||||
|
|
||||||
module.exports = RelationMixin;
|
module.exports = RelationMixin;
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ function RelationMixin() {
|
||||||
* @property {Object} model Model object
|
* @property {Object} model Model object
|
||||||
*/
|
*/
|
||||||
RelationMixin.hasMany = function hasMany(modelTo, params) {
|
RelationMixin.hasMany = function hasMany(modelTo, params) {
|
||||||
var def = RelationDefinition.hasMany(this, modelTo, params);
|
const def = RelationDefinition.hasMany(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
@ -139,7 +139,7 @@ RelationMixin.hasMany = function hasMany(modelTo, params) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
RelationMixin.belongsTo = function(modelTo, params) {
|
RelationMixin.belongsTo = function(modelTo, params) {
|
||||||
var def = RelationDefinition.belongsTo(this, modelTo, params);
|
const def = RelationDefinition.belongsTo(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
@ -179,31 +179,31 @@ RelationMixin.belongsTo = function(modelTo, params) {
|
||||||
*/
|
*/
|
||||||
RelationMixin.hasAndBelongsToMany =
|
RelationMixin.hasAndBelongsToMany =
|
||||||
function hasAndBelongsToMany(modelTo, params) {
|
function hasAndBelongsToMany(modelTo, params) {
|
||||||
var def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
const def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.hasOne = function hasOne(modelTo, params) {
|
RelationMixin.hasOne = function hasOne(modelTo, params) {
|
||||||
var def = RelationDefinition.hasOne(this, modelTo, params);
|
const def = RelationDefinition.hasOne(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.referencesMany = function referencesMany(modelTo, params) {
|
RelationMixin.referencesMany = function referencesMany(modelTo, params) {
|
||||||
var def = RelationDefinition.referencesMany(this, modelTo, params);
|
const def = RelationDefinition.referencesMany(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.embedsOne = function embedsOne(modelTo, params) {
|
RelationMixin.embedsOne = function embedsOne(modelTo, params) {
|
||||||
var def = RelationDefinition.embedsOne(this, modelTo, params);
|
const def = RelationDefinition.embedsOne(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
RelationMixin.embedsMany = function embedsMany(modelTo, params) {
|
RelationMixin.embedsMany = function embedsMany(modelTo, params) {
|
||||||
var def = RelationDefinition.embedsMany(this, modelTo, params);
|
const def = RelationDefinition.embedsMany(this, modelTo, params);
|
||||||
this.dataSource.adapter.resolve(this);
|
this.dataSource.adapter.resolve(this);
|
||||||
defineRelationProperty(this, def);
|
defineRelationProperty(this, def);
|
||||||
};
|
};
|
||||||
|
@ -211,10 +211,23 @@ RelationMixin.embedsMany = function embedsMany(modelTo, params) {
|
||||||
function defineRelationProperty(modelClass, def) {
|
function defineRelationProperty(modelClass, def) {
|
||||||
Object.defineProperty(modelClass.prototype, def.name, {
|
Object.defineProperty(modelClass.prototype, def.name, {
|
||||||
get: function() {
|
get: function() {
|
||||||
var that = this;
|
const that = this;
|
||||||
var scope = function() {
|
const scope = function() {
|
||||||
return that['__get__' + def.name].apply(that, arguments);
|
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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.count = function() {
|
scope.count = function() {
|
||||||
return that['__count__' + def.name].apply(that, arguments);
|
return that['__count__' + def.name].apply(that, arguments);
|
||||||
};
|
};
|
||||||
|
@ -231,6 +244,6 @@ function defineRelationProperty(modelClass, def) {
|
||||||
return that['__findById__' + def.name].apply(that, arguments);
|
return that['__findById__' + def.name].apply(that, arguments);
|
||||||
};
|
};
|
||||||
return scope;
|
return scope;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
@ -9,12 +9,14 @@
|
||||||
* Dependencies.
|
* Dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var assert = require('assert');
|
const assert = require('assert');
|
||||||
var remoting = require('strong-remoting');
|
const remoting = require('strong-remoting');
|
||||||
var utils = require('loopback-datasource-juggler/lib/utils');
|
const utils = require('loopback-datasource-juggler/lib/utils');
|
||||||
var jutil = require('loopback-datasource-juggler/lib/jutil');
|
const jutil = require('loopback-datasource-juggler/lib/jutil');
|
||||||
var RelationMixin = require('./relations');
|
const RelationMixin = require('./relations');
|
||||||
var InclusionMixin = require('loopback-datasource-juggler/lib/include');
|
const InclusionMixin = require('loopback-datasource-juggler/lib/include');
|
||||||
|
|
||||||
|
const findMethodNames = ['findById', 'findOne'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the RemoteConnector class.
|
* Export the RemoteConnector class.
|
||||||
|
@ -29,14 +31,14 @@ module.exports = RemoteConnector;
|
||||||
function RemoteConnector(settings) {
|
function RemoteConnector(settings) {
|
||||||
assert(typeof settings ===
|
assert(typeof settings ===
|
||||||
'object',
|
'object',
|
||||||
'cannot initiaze RemoteConnector without a settings object');
|
'cannot initialize RemoteConnector without a settings object');
|
||||||
this.client = settings.client;
|
this.client = settings.client;
|
||||||
this.adapter = settings.adapter || 'rest';
|
this.adapter = settings.adapter || 'rest';
|
||||||
this.protocol = settings.protocol || 'http';
|
this.protocol = settings.protocol || 'http';
|
||||||
this.root = settings.root || '';
|
this.root = settings.root || '';
|
||||||
this.host = settings.host || 'localhost';
|
this.host = settings.host || 'localhost';
|
||||||
this.port = settings.port || 3000;
|
this.port = settings.port || 3000;
|
||||||
this.remotes = remoting.create();
|
this.remotes = remoting.create(settings.options);
|
||||||
this.name = 'remote-connector';
|
this.name = 'remote-connector';
|
||||||
|
|
||||||
if (settings.url) {
|
if (settings.url) {
|
||||||
|
@ -46,9 +48,8 @@ function RemoteConnector(settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle mixins in the define() method
|
// handle mixins in the define() method
|
||||||
var DAO = this.DataAccessObject = function() {
|
const DAO = this.DataAccessObject = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteConnector.prototype.connect = function() {
|
RemoteConnector.prototype.connect = function() {
|
||||||
|
@ -56,64 +57,95 @@ RemoteConnector.prototype.connect = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
RemoteConnector.initialize = function(dataSource, callback) {
|
RemoteConnector.initialize = function(dataSource, callback) {
|
||||||
var connector = dataSource.connector =
|
const connector = dataSource.connector =
|
||||||
new RemoteConnector(dataSource.settings);
|
new RemoteConnector(dataSource.settings);
|
||||||
connector.connect();
|
connector.connect();
|
||||||
process.nextTick(callback);
|
process.nextTick(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
RemoteConnector.prototype.define = function(definition) {
|
RemoteConnector.prototype.define = function(definition) {
|
||||||
var Model = definition.model;
|
const Model = definition.model;
|
||||||
var remotes = this.remotes;
|
const remotes = this.remotes;
|
||||||
|
|
||||||
assert(Model.sharedClass,
|
assert(Model.sharedClass,
|
||||||
'cannot attach ' +
|
'cannot attach ' +
|
||||||
Model.modelName +
|
Model.modelName +
|
||||||
' to a remote connector without a Model.sharedClass');
|
' to a remote connector without a Model.sharedClass');
|
||||||
|
|
||||||
jutil.mixin(Model, RelationMixin);
|
jutil.mixin(Model, RelationMixin);
|
||||||
jutil.mixin(Model, InclusionMixin);
|
jutil.mixin(Model, InclusionMixin);
|
||||||
remotes.addClass(Model.sharedClass);
|
remotes.addClass(Model.sharedClass);
|
||||||
|
|
||||||
this.resolve(Model);
|
this.resolve(Model);
|
||||||
|
this.setupRemotingTypeFor(Model);
|
||||||
};
|
};
|
||||||
|
|
||||||
RemoteConnector.prototype.resolve = function(Model) {
|
RemoteConnector.prototype.resolve = function(Model) {
|
||||||
var remotes = this.remotes;
|
const remotes = this.remotes;
|
||||||
|
|
||||||
Model.sharedClass.methods().forEach(function(remoteMethod) {
|
Model.sharedClass.methods().forEach(function(remoteMethod) {
|
||||||
if (remoteMethod.name !== 'Change' && remoteMethod.name !== 'Checkpoint') {
|
if (remoteMethod.name !== 'Change' && remoteMethod.name !== 'Checkpoint') {
|
||||||
createProxyMethod(Model, remotes, remoteMethod);
|
createProxyMethod(Model, remotes, remoteMethod);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
RemoteConnector.prototype.setupRemotingTypeFor = function(Model) {
|
||||||
|
const remotes = this.remotes;
|
||||||
|
|
||||||
// setup a remoting type converter for this model
|
// setup a remoting type converter for this model
|
||||||
remotes.defineObjectType(Model.modelName, function(data) {
|
remotes.defineObjectType(Model.modelName, function(data) {
|
||||||
return new Model(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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function createProxyMethod(Model, remotes, remoteMethod) {
|
function createProxyMethod(Model, remotes, remoteMethod) {
|
||||||
var scope = remoteMethod.isStatic ? Model : Model.prototype;
|
const scope = remoteMethod.isStatic ? Model : Model.prototype;
|
||||||
var original = scope[remoteMethod.name];
|
const original = scope[remoteMethod.name];
|
||||||
|
|
||||||
function remoteMethodProxy() {
|
function remoteMethodProxy() {
|
||||||
var args = Array.prototype.slice.call(arguments);
|
const args = Array.prototype.slice.call(arguments);
|
||||||
var lastArgIsFunc = typeof args[args.length - 1] === 'function';
|
const lastArgIsFunc = typeof args[args.length - 1] === 'function';
|
||||||
var callback;
|
let callback;
|
||||||
if (lastArgIsFunc) {
|
if (lastArgIsFunc) {
|
||||||
callback = args.pop();
|
callback = args.pop();
|
||||||
} else {
|
} else {
|
||||||
callback = utils.createPromiseCallback();
|
callback = utils.createPromiseCallback();
|
||||||
}
|
}
|
||||||
|
const callbackPromise = callback.promise;
|
||||||
|
|
||||||
|
if (findMethodNames.includes(remoteMethod.name)) {
|
||||||
|
callback = proxy404toNull(callback);
|
||||||
|
}
|
||||||
|
|
||||||
if (remoteMethod.isStatic) {
|
if (remoteMethod.isStatic) {
|
||||||
remotes.invoke(remoteMethod.stringName, args, callback);
|
remotes.invoke(remoteMethod.stringName, args, callback);
|
||||||
} else {
|
} else {
|
||||||
var ctorArgs = [this.id];
|
const ctorArgs = [this.id];
|
||||||
remotes.invoke(remoteMethod.stringName, ctorArgs, args, callback);
|
remotes.invoke(remoteMethod.stringName, ctorArgs, args, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback.promise;
|
return callbackPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxy404toNull(cb) {
|
||||||
|
return function(err, data) {
|
||||||
|
if (err && err.code === 'MODEL_NOT_FOUND') {
|
||||||
|
cb(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(err, data);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
scope[remoteMethod.name] = remoteMethodProxy;
|
scope[remoteMethod.name] = remoteMethodProxy;
|
||||||
|
|
38
package.json
38
package.json
|
@ -1,11 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-connector-remote",
|
"name": "loopback-connector-remote",
|
||||||
"version": "3.0.0",
|
"version": "3.4.1",
|
||||||
"pubishConfig": {
|
|
||||||
"tag": "next"
|
|
||||||
},
|
|
||||||
"description": "Remote REST API connector for Loopback",
|
"description": "Remote REST API connector for Loopback",
|
||||||
"main": "index.js",
|
"main": "lib/remote-connector.js",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"web",
|
"web",
|
||||||
"restful",
|
"restful",
|
||||||
|
@ -13,7 +10,13 @@
|
||||||
"StrongLoop"
|
"StrongLoop"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt"
|
"test": "grunt",
|
||||||
|
"posttest": "npm run lint",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -30,18 +33,21 @@
|
||||||
"homepage": "http://loopback.io",
|
"homepage": "http://loopback.io",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loopback-datasource-juggler": "^3.0.0",
|
"loopback-datasource-juggler": "^3.0.0",
|
||||||
"strong-remoting": "^3.0.0"
|
"strong-remoting": "^3.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"assert": "^1.1.2",
|
"assert": "^1.4.1",
|
||||||
"bluebird": "^3.3.5",
|
"bluebird": "^3.5.1",
|
||||||
"grunt": "^1.0.1",
|
"eslint": "^6.5.1",
|
||||||
|
"eslint-config-loopback": "^13.1.0",
|
||||||
|
"grunt": "^1.0.3",
|
||||||
"grunt-cli": "^1.2.0",
|
"grunt-cli": "^1.2.0",
|
||||||
"grunt-contrib-jshint": "^1.0.0",
|
"grunt-mocha-test": "^0.13.3",
|
||||||
"grunt-mocha-test": "^0.12.7",
|
"loopback": "^3.0.0",
|
||||||
"loopback": "^3.0.0-alpha.5",
|
"mocha": "^5.2.0",
|
||||||
"mocha": "^3.0.2",
|
"sinon": "^6.0.0",
|
||||||
"strong-task-emitter": "^0.0.7"
|
"strong-task-emitter": "^0.0.8"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {}
|
"optionalDependencies": {},
|
||||||
|
"author": "IBM Corp."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var extend = require('util')._extend;
|
const extend = require('util')._extend;
|
||||||
var loopback = require('loopback');
|
const loopback = require('loopback');
|
||||||
var remoteConnector = require('..');
|
const remoteConnector = require('..');
|
||||||
|
|
||||||
exports.createMemoryDataSource = createMemoryDataSource;
|
exports.createMemoryDataSource = createMemoryDataSource;
|
||||||
exports.createModel = createModel;
|
|
||||||
exports.createRemoteDataSource = createRemoteDataSource;
|
exports.createRemoteDataSource = createRemoteDataSource;
|
||||||
exports.createRestAppAndListen = createRestAppAndListen;
|
exports.createRestAppAndListen = createRestAppAndListen;
|
||||||
exports.getUserProperties = getUserProperties;
|
exports.getUserProperties = getUserProperties;
|
||||||
|
|
||||||
function createRestAppAndListen() {
|
function createRestAppAndListen() {
|
||||||
var app = loopback();
|
const app = loopback({localRegistry: true});
|
||||||
|
|
||||||
app.set('host', '127.0.0.1');
|
app.set('host', '127.0.0.1');
|
||||||
app.set('port', 0);
|
app.set('port', 0);
|
||||||
|
|
||||||
app.set('legacyExplorer', false);
|
app.set('legacyExplorer', false);
|
||||||
app.set('remoting', {
|
app.set('remoting', {
|
||||||
errorHandler: { debug: true, log: false },
|
errorHandler: {debug: true, log: false},
|
||||||
context: false,
|
context: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,30 +32,17 @@ function createRestAppAndListen() {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMemoryDataSource() {
|
function createMemoryDataSource(app) {
|
||||||
return loopback.createDataSource({connector: 'memory'});
|
return app.dataSource('db', {connector: 'memory'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRemoteDataSource(remoteApp) {
|
function createRemoteDataSource(app, serverApp) {
|
||||||
return loopback.createDataSource({
|
return app.dataSource('remote', {
|
||||||
url: 'http://' + remoteApp.get('host') + ':' + remoteApp.get('port'),
|
url: 'http://' + serverApp.get('host') + ':' + serverApp.get('port'),
|
||||||
connector: remoteConnector
|
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() {
|
function getUserProperties() {
|
||||||
return {
|
return {
|
||||||
'first': String,
|
'first': String,
|
||||||
|
@ -65,6 +51,6 @@ function getUserProperties() {
|
||||||
'password': String,
|
'password': String,
|
||||||
'gender': String,
|
'gender': String,
|
||||||
'domain': String,
|
'domain': String,
|
||||||
'email': String
|
'email': String,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
var assert = require('assert');
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
var helper = require('../helper');
|
// Node module: loopback-connector-remote
|
||||||
var Promise = require('bluebird');
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
var globalPromiseSetManually = false;
|
'use strict';
|
||||||
var User;
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const helper = require('../helper');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
|
let globalPromiseSetManually = false;
|
||||||
|
let User;
|
||||||
|
|
||||||
describe('promise support', function() {
|
describe('promise support', function() {
|
||||||
before(setGlobalPromise);
|
before(setGlobalPromise);
|
||||||
|
@ -12,21 +19,21 @@ describe('promise support', function() {
|
||||||
|
|
||||||
context('create', function() {
|
context('create', function() {
|
||||||
it('supports promises', function() {
|
it('supports promises', function() {
|
||||||
var retval = User.create();
|
const retval = User.create();
|
||||||
assert(retval && typeof retval.then === 'function');
|
assert(retval && typeof retval.then === 'function');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('find', function() {
|
context('find', function() {
|
||||||
it('supports promises', function() {
|
it('supports promises', function() {
|
||||||
var retval = User.find();
|
const retval = User.find();
|
||||||
assert(retval && typeof retval.then === 'function');
|
assert(retval && typeof retval.then === 'function');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('findById', function() {
|
context('findById', function() {
|
||||||
it('supports promises', function() {
|
it('supports promises', function() {
|
||||||
var retval = User.findById(1);
|
const retval = User.findById(1);
|
||||||
assert(retval && typeof retval.then === 'function');
|
assert(retval && typeof retval.then === 'function');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,12 +47,15 @@ function setGlobalPromise() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createUserModel() {
|
function createUserModel() {
|
||||||
User = helper.createModel({
|
const app = helper.createRestAppAndListen();
|
||||||
parent: 'user',
|
const db = helper.createMemoryDataSource(app);
|
||||||
app: helper.createRestAppAndListen(),
|
|
||||||
datasource: helper.createMemoryDataSource(),
|
User = app.registry.createModel({
|
||||||
properties: helper.getUserProperties()
|
name: 'user',
|
||||||
|
properties: helper.getUserProperties(),
|
||||||
|
options: {forceId: false},
|
||||||
});
|
});
|
||||||
|
app.model(User, {dataSource: db});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetGlobalPromise() {
|
function resetGlobalPromise() {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// 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,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,65 +1,69 @@
|
||||||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
const assert = require('assert');
|
||||||
var helper = require('./helper');
|
const helper = require('./helper');
|
||||||
var TaskEmitter = require('strong-task-emitter');
|
const loopback = require('loopback');
|
||||||
|
const TaskEmitter = require('strong-task-emitter');
|
||||||
|
|
||||||
describe('Model tests', function() {
|
describe('Model tests', function() {
|
||||||
var User;
|
let User;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
User = helper.createModel({
|
const app = helper.createRestAppAndListen();
|
||||||
parent: 'user',
|
const db = helper.createMemoryDataSource(app);
|
||||||
app: helper.createRestAppAndListen(),
|
|
||||||
datasource: helper.createMemoryDataSource(),
|
User = app.registry.createModel({
|
||||||
properties: helper.getUserProperties()
|
name: 'user',
|
||||||
|
properties: helper.getUserProperties(),
|
||||||
|
options: {forceId: false},
|
||||||
});
|
});
|
||||||
|
app.model(User, {dataSource: db});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.validatesPresenceOf(properties...)', function() {
|
describe('Model.validatesPresenceOf(properties...)', function() {
|
||||||
it('should require a model to include a property to be considered valid',
|
it('should require a model to include a property to be considered valid',
|
||||||
function() {
|
function() {
|
||||||
User.validatesPresenceOf('first', 'last', 'age');
|
User.validatesPresenceOf('first', 'last', 'age');
|
||||||
var joe = new User({first: 'joe'});
|
const joe = new User({first: 'joe'});
|
||||||
assert(joe.isValid() === false, 'model should not validate');
|
assert(joe.isValid() === false, 'model should not validate');
|
||||||
assert(joe.errors.last, 'should have a missing last error');
|
assert(joe.errors.last, 'should have a missing last error');
|
||||||
assert(joe.errors.age, 'should have a missing age error');
|
assert(joe.errors.age, 'should have a missing age error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.validatesLengthOf(property, options)', function() {
|
describe('Model.validatesLengthOf(property, options)', function() {
|
||||||
it('should require a property length to be within a specified range',
|
it('should require a property length to be within a specified range',
|
||||||
function() {
|
function() {
|
||||||
User.validatesLengthOf('password', {min: 5, message: {min:
|
User.validatesLengthOf('password', {min: 5, message: {min:
|
||||||
'Password is too short'}});
|
'Password is too short'}});
|
||||||
var joe = new User({password: '1234'});
|
const joe = new User({password: '1234'});
|
||||||
assert(joe.isValid() === false, 'model should not be valid');
|
assert(joe.isValid() === false, 'model should not be valid');
|
||||||
assert(joe.errors.password, 'should have password error');
|
assert(joe.errors.password, 'should have password error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.validatesInclusionOf(property, options)', function() {
|
describe('Model.validatesInclusionOf(property, options)', function() {
|
||||||
it('should require a value for `property` to be in the specified array',
|
it('should require a value for `property` to be in the specified array',
|
||||||
function() {
|
function() {
|
||||||
User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
User.validatesInclusionOf('gender', {in: ['male', 'female']});
|
||||||
var foo = new User({gender: 'bar'});
|
const foo = new User({gender: 'bar'});
|
||||||
assert(foo.isValid() === false, 'model should not be valid');
|
assert(foo.isValid() === false, 'model should not be valid');
|
||||||
assert(foo.errors.gender, 'should have gender error');
|
assert(foo.errors.gender, 'should have gender error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.validatesExclusionOf(property, options)', function() {
|
describe('Model.validatesExclusionOf(property, options)', function() {
|
||||||
it('should require a value for `property` to not exist in the specified ' +
|
it('should require a value for `property` to not exist in the specified ' +
|
||||||
'array', function() {
|
'array', function() {
|
||||||
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
|
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
|
||||||
var foo = new User({domain: 'www'});
|
const foo = new User({domain: 'www'});
|
||||||
var bar = new User({domain: 'billing'});
|
const bar = new User({domain: 'billing'});
|
||||||
var bat = new User({domain: 'admin'});
|
const bat = new User({domain: 'admin'});
|
||||||
assert(foo.isValid() === false);
|
assert(foo.isValid() === false);
|
||||||
assert(bar.isValid() === false);
|
assert(bar.isValid() === false);
|
||||||
assert(bat.isValid() === false);
|
assert(bat.isValid() === false);
|
||||||
|
@ -73,9 +77,9 @@ describe('Model tests', function() {
|
||||||
it('should require a value for `property` to be a specific type of ' +
|
it('should require a value for `property` to be a specific type of ' +
|
||||||
'`Number`', function() {
|
'`Number`', function() {
|
||||||
User.validatesNumericalityOf('age', {int: true});
|
User.validatesNumericalityOf('age', {int: true});
|
||||||
var joe = new User({age: 10.2});
|
const joe = new User({age: 10.2});
|
||||||
assert(joe.isValid() === false);
|
assert(joe.isValid() === false);
|
||||||
var bob = new User({age: 0});
|
const bob = new User({age: 0});
|
||||||
assert(bob.isValid() === true);
|
assert(bob.isValid() === true);
|
||||||
assert(joe.errors.age, 'model should have an age error');
|
assert(joe.errors.age, 'model should have an age error');
|
||||||
});
|
});
|
||||||
|
@ -84,15 +88,15 @@ describe('Model tests', function() {
|
||||||
describe('myModel.isValid()', function() {
|
describe('myModel.isValid()', function() {
|
||||||
it('should validate the model instance', function() {
|
it('should validate the model instance', function() {
|
||||||
User.validatesNumericalityOf('age', {int: true});
|
User.validatesNumericalityOf('age', {int: true});
|
||||||
var user = new User({first: 'joe', age: 'flarg'});
|
const user = new User({first: 'joe', age: 'flarg'});
|
||||||
var valid = user.isValid();
|
const valid = user.isValid();
|
||||||
assert(valid === false);
|
assert(valid === false);
|
||||||
assert(user.errors.age, 'model should have age error');
|
assert(user.errors.age, 'model should have age error');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate the model asynchronously', function(done) {
|
it('should validate the model asynchronously', function(done) {
|
||||||
User.validatesNumericalityOf('age', {int: true});
|
User.validatesNumericalityOf('age', {int: true});
|
||||||
var user = new User({first: 'joe', age: 'flarg'});
|
const user = new User({first: 'joe', age: 'flarg'});
|
||||||
user.isValid(function(valid) {
|
user.isValid(function(valid) {
|
||||||
assert(valid === false);
|
assert(valid === false);
|
||||||
assert(user.errors.age, 'model should have age error');
|
assert(user.errors.age, 'model should have age error');
|
||||||
|
@ -103,63 +107,63 @@ describe('Model tests', function() {
|
||||||
|
|
||||||
describe('Model.create([data], [callback])', function() {
|
describe('Model.create([data], [callback])', function() {
|
||||||
it('should create an instance and save to the attached data source',
|
it('should create an instance and save to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(user instanceof User);
|
assert(user instanceof User);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('model.save([options], [callback])', function() {
|
describe('model.save([options], [callback])', function() {
|
||||||
it('should save an instance of a Model to the attached data source',
|
it('should save an instance of a Model to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
var joe = new User({first: 'Joe', last: 'Bob'});
|
const joe = new User({first: 'Joe', last: 'Bob'});
|
||||||
joe.save(function(err, user) {
|
joe.save(function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(user.id);
|
assert(user.id);
|
||||||
assert(!user.errors);
|
assert(!user.errors);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('model.updateAttributes(data, [callback])', function() {
|
describe('model.updateAttributes(data, [callback])', function() {
|
||||||
it('should save specified attributes to the attached data source',
|
it('should save specified attributes to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
User.create({first: 'joe', age: 100}, function(err, user) {
|
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);
|
if (err) return done(err);
|
||||||
assert.equal(updatedUser.first, 'updatedFirst');
|
assert.equal(user.first, 'joe');
|
||||||
assert.equal(updatedUser.last, 'updatedLast');
|
|
||||||
assert.equal(updatedUser.age, 100);
|
user.updateAttributes({
|
||||||
done();
|
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() {
|
describe('Model.upsert(data, callback)', function() {
|
||||||
it('should update when a record with id=data.id is found, insert otherwise',
|
it('should update when a record with id=data.id is found, insert otherwise',
|
||||||
function(done) {
|
function(done) {
|
||||||
User.upsert({first: 'joe', id: 7}, function(err, user) {
|
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);
|
if (err) return done(err);
|
||||||
assert.equal(updatedUser.first, 'bob');
|
assert.equal(user.first, 'joe');
|
||||||
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() {
|
describe('model.destroy([callback])', function() {
|
||||||
|
@ -184,19 +188,19 @@ describe('Model tests', function() {
|
||||||
|
|
||||||
describe('Model.deleteById(id, [callback])', function() {
|
describe('Model.deleteById(id, [callback])', function() {
|
||||||
it('should delete a model instance from the attached data source',
|
it('should delete a model instance from the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
User.create({first: 'joe', last: 'bob'}, function(err, user) {
|
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);
|
if (err) return done(err);
|
||||||
User.findById(user.id, function(err, notFound) {
|
User.deleteById(user.id, function(err) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert.equal(notFound, null);
|
User.findById(user.id, function(err, notFound) {
|
||||||
done();
|
if (err) return done(err);
|
||||||
|
assert.equal(notFound, null);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.findById(id, callback)', function() {
|
describe('Model.findById(id, callback)', function() {
|
||||||
|
@ -217,21 +221,21 @@ describe('Model tests', function() {
|
||||||
|
|
||||||
describe('Model.count([query], callback)', function() {
|
describe('Model.count([query], callback)', function() {
|
||||||
it('should return the count of Model instances in data source',
|
it('should return the count of Model instances in data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
var taskEmitter = new TaskEmitter();
|
const taskEmitter = new TaskEmitter();
|
||||||
taskEmitter
|
taskEmitter
|
||||||
.task(User, 'create', {first: 'jill', age: 100})
|
.task(User, 'create', {first: 'jill', age: 100})
|
||||||
.task(User, 'create', {first: 'bob', age: 200})
|
.task(User, 'create', {first: 'bob', age: 200})
|
||||||
.task(User, 'create', {first: 'jan'})
|
.task(User, 'create', {first: 'jan'})
|
||||||
.task(User, 'create', {first: 'sam'})
|
.task(User, 'create', {first: 'sam'})
|
||||||
.task(User, 'create', {first: 'suzy'})
|
.task(User, 'create', {first: 'suzy'})
|
||||||
.on('done', function() {
|
.on('done', function() {
|
||||||
User.count({age: {gt: 99}}, function(err, count) {
|
User.count({age: {gt: 99}}, function(err, count) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert.equal(count, 2);
|
assert.equal(count, 2);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,75 +1,88 @@
|
||||||
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
const assert = require('assert');
|
||||||
var helper = require('./helper');
|
const helper = require('./helper');
|
||||||
|
const loopback = require('loopback');
|
||||||
|
|
||||||
describe('RemoteConnector', function() {
|
describe('RemoteConnector', function() {
|
||||||
var ctx = this;
|
let serverApp, clientApp, ServerModel, ClientModel;
|
||||||
|
|
||||||
before(function setupServer(done) {
|
before(function setupServer(done) {
|
||||||
ctx.serverApp = helper.createRestAppAndListen();
|
const app = serverApp = helper.createRestAppAndListen();
|
||||||
ctx.ServerModel = helper.createModel({
|
const db = helper.createMemoryDataSource(app);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.serverApp,
|
ServerModel = app.registry.createModel({
|
||||||
datasource: helper.createMemoryDataSource()
|
name: 'TestModel',
|
||||||
});
|
});
|
||||||
ctx.serverApp.locals.handler.on('listening', function() { done(); });
|
app.model(ServerModel, {dataSource: db});
|
||||||
|
|
||||||
|
app.locals.handler.on('listening', function() { done(); });
|
||||||
});
|
});
|
||||||
|
|
||||||
before(function setupRemoteClient(done) {
|
before(function setupRemoteClient() {
|
||||||
ctx.remoteApp = helper.createRestAppAndListen();
|
const app = clientApp = loopback({localRegistry: true});
|
||||||
ctx.RemoteModel = helper.createModel({
|
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.remoteApp,
|
ClientModel = app.registry.createModel({
|
||||||
datasource: helper.createRemoteDataSource(ctx.serverApp)
|
name: 'TestModel',
|
||||||
});
|
});
|
||||||
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
|
app.model(ClientModel, {dataSource: remoteDs});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function() {
|
after(function() {
|
||||||
ctx.serverApp.locals.handler.close();
|
serverApp.locals.handler.close();
|
||||||
ctx.remoteApp.locals.handler.close();
|
ServerModel = null;
|
||||||
ctx.ServerModel = null;
|
ClientModel = null;
|
||||||
ctx.RemoteModel = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support the save method', function(done) {
|
it('should support the save method', function(done) {
|
||||||
var calledServerCreate = false;
|
let calledServerCreate = false;
|
||||||
|
|
||||||
|
ServerModel.create = function(data, options, cb, callback) {
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
callback = cb;
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
ctx.ServerModel.create = function(data, cb, callback) {
|
|
||||||
calledServerCreate = true;
|
calledServerCreate = true;
|
||||||
data.id = 1;
|
data.id = 1;
|
||||||
if (callback) callback(null, data);
|
if (callback) callback(null, data);
|
||||||
else cb(null, data);
|
else cb(null, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
var m = new ctx.RemoteModel({foo: 'bar'});
|
const m = new ClientModel({foo: 'bar'});
|
||||||
m.save(function(err, instance) {
|
m.save(function(err, instance) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(instance);
|
assert(instance);
|
||||||
assert(instance instanceof ctx.RemoteModel);
|
assert(instance instanceof ClientModel);
|
||||||
assert(calledServerCreate);
|
assert(calledServerCreate);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support aliases', function(done) {
|
it('should support aliases', function(done) {
|
||||||
var calledServerUpsert = false;
|
let calledServerUpsert = false;
|
||||||
ctx.ServerModel.patchOrCreate =
|
ServerModel.patchOrCreate =
|
||||||
ctx.ServerModel.upsert = function(id, cb) {
|
ServerModel.upsert = function(id, options, cb) {
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
calledServerUpsert = true;
|
calledServerUpsert = true;
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.RemoteModel.updateOrCreate({}, function(err, instance) {
|
ClientModel.updateOrCreate({}, function(err, instance) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(instance);
|
assert(instance);
|
||||||
assert(instance instanceof ctx.RemoteModel);
|
assert(instance instanceof ClientModel);
|
||||||
assert(calledServerUpsert, 'server upsert should have been called');
|
assert(calledServerUpsert, 'server upsert should have been called');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -77,49 +90,61 @@ describe('RemoteConnector', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Custom Path', function() {
|
describe('Custom Path', function() {
|
||||||
var ctx = this;
|
let serverApp, clientApp, ServerModel, ClientModel;
|
||||||
|
|
||||||
before(function setupServer(done) {
|
before(function setupServer(done) {
|
||||||
ctx.serverApp = helper.createRestAppAndListen();
|
const app = serverApp = helper.createRestAppAndListen();
|
||||||
ctx.ServerModel = helper.createModel({
|
const db = helper.createMemoryDataSource(app);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.serverApp,
|
ServerModel = app.registry.createModel({
|
||||||
datasource: helper.createMemoryDataSource(),
|
name: 'TestModel',
|
||||||
options: {
|
options: {
|
||||||
http: {path: '/custom'}
|
http: {path: '/custom'},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
ctx.serverApp.locals.handler.on('listening', function() { done(); });
|
app.model(ServerModel, {dataSource: db});
|
||||||
|
|
||||||
|
serverApp.locals.handler.on('listening', function() { done(); });
|
||||||
});
|
});
|
||||||
|
|
||||||
before(function setupRemoteClient(done) {
|
before(function setupRemoteClient() {
|
||||||
ctx.remoteApp = helper.createRestAppAndListen();
|
const app = clientApp = loopback({localRegistry: true});
|
||||||
ctx.RemoteModel = helper.createModel({
|
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.remoteApp,
|
ClientModel = app.registry.createModel({
|
||||||
datasource: helper.createRemoteDataSource(ctx.serverApp),
|
name: 'TestModel',
|
||||||
options: {
|
options: {
|
||||||
dataSource: 'remote',
|
dataSource: 'remote',
|
||||||
http: {path: '/custom'}
|
http: {path: '/custom'},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
|
app.model(ClientModel, {dataSource: remoteDs});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function(done)
|
after(function() {
|
||||||
{
|
serverApp.locals.handler.close();
|
||||||
ctx.serverApp.locals.handler.close();
|
ServerModel = null;
|
||||||
ctx.remoteApp.locals.handler.close();
|
ClientModel = null;
|
||||||
ctx.ServerModel = null;
|
|
||||||
ctx.RemoteModel = null;
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support http.path configuration', function(done) {
|
it('should support http.path configuration', function(done) {
|
||||||
ctx.RemoteModel.create({}, function(err, instance) {
|
ClientModel.create({}, function(err, instance) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(instance);
|
assert(instance);
|
||||||
done();
|
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'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,161 +1,389 @@
|
||||||
// Copyright IBM Corp. 2016. All Rights Reserved.
|
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
|
||||||
// Node module: loopback-connector-remote
|
// Node module: loopback-connector-remote
|
||||||
// This file is licensed under the MIT License.
|
// This file is licensed under the MIT License.
|
||||||
// License text available at https://opensource.org/licenses/MIT
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
const assert = require('assert');
|
||||||
var helper = require('./helper');
|
const helper = require('./helper');
|
||||||
var TaskEmitter = require('strong-task-emitter');
|
const loopback = require('loopback');
|
||||||
|
const TaskEmitter = require('strong-task-emitter');
|
||||||
|
|
||||||
describe('Remote model tests', function() {
|
describe('Remote model tests', function() {
|
||||||
var ctx = this;
|
let serverApp, ServerModel, ServerRelatedModel, ServerModelWithSingleChild,
|
||||||
|
clientApp, ClientModel, ClientRelatedModel, ClientModelWithSingleChild;
|
||||||
|
|
||||||
beforeEach(function(done) {
|
beforeEach(function setupServer(done) {
|
||||||
ctx.serverApp = helper.createRestAppAndListen();
|
const app = serverApp = helper.createRestAppAndListen();
|
||||||
ctx.ServerModel = helper.createModel({
|
const db = helper.createMemoryDataSource(app);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.serverApp,
|
ServerModel = app.registry.createModel({
|
||||||
datasource: helper.createMemoryDataSource(),
|
name: 'TestModel',
|
||||||
properties: helper.userProperties
|
properties: helper.getUserProperties(),
|
||||||
|
options: {
|
||||||
|
forceId: false,
|
||||||
|
relations: {
|
||||||
|
children: {
|
||||||
|
type: 'hasMany',
|
||||||
|
model: 'ChildModel',
|
||||||
|
foreignKey: 'parentId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
ctx.serverApp.locals.handler.on('listening', function() { done(); });
|
|
||||||
|
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(); });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function setupRemoteClient(done) {
|
beforeEach(function setupRemoteClient() {
|
||||||
ctx.remoteApp = helper.createRestAppAndListen();
|
const app = clientApp = loopback({localRegistry: true});
|
||||||
ctx.RemoteModel = helper.createModel({
|
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
|
||||||
parent: 'TestModel',
|
|
||||||
app: ctx.remoteApp,
|
ClientRelatedModel = app.registry.createModel({
|
||||||
datasource: helper.createRemoteDataSource(ctx.serverApp),
|
name: 'ChildModel',
|
||||||
properties: helper.userProperties
|
properties: {
|
||||||
|
note: {type: 'text'},
|
||||||
|
parentId: {type: 'number'},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
|
|
||||||
|
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});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
ctx.serverApp.locals.handler.close();
|
serverApp.locals.handler.close();
|
||||||
ctx.remoteApp.locals.handler.close();
|
ServerModel = null;
|
||||||
ctx.ServerModel = null;
|
ServerRelatedModel = null;
|
||||||
ctx.RemoteModel = null;
|
ClientModel = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.create([data], [callback])', function() {
|
describe('Model.create([data], [callback])', function() {
|
||||||
it('should create an instance and save to the attached data source',
|
it('should create an instance and save to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
ctx.RemoteModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
ClientModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(user instanceof ctx.RemoteModel);
|
assert(user instanceof ClientModel);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('model.save([options], [callback])', function() {
|
describe('model.save([options], [callback])', function() {
|
||||||
it('should save an instance of a Model to the attached data source',
|
it('should save an instance of a Model to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
var joe = new ctx.RemoteModel({first: 'Joe', last: 'Bob'});
|
const joe = new ClientModel({first: 'Joe', last: 'Bob'});
|
||||||
joe.save(function(err, user) {
|
joe.save(function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert(user.id);
|
assert(user.id);
|
||||||
assert(!user.errors);
|
assert(!user.errors);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('model.updateAttributes(data, [callback])', function() {
|
describe('model.updateAttributes(data, [callback])', function() {
|
||||||
it('should save specified attributes to the attached data source',
|
it('should save specified attributes to the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
ctx.ServerModel.create({first: 'joe', age: 100}, function(err, user) {
|
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);
|
if (err) return done(err);
|
||||||
assert.equal(updatedUser.first, 'updatedFirst');
|
assert.equal(user.first, 'joe');
|
||||||
assert.equal(updatedUser.last, 'updatedLast');
|
|
||||||
assert.equal(updatedUser.age, 100);
|
user.updateAttributes({
|
||||||
done();
|
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() {
|
describe('Model.upsert(data, callback)', function() {
|
||||||
it('should update when a record with id=data.id is found, insert otherwise',
|
it('should update when a record with id=data.id is found, insert otherwise',
|
||||||
function(done) {
|
function(done) {
|
||||||
ctx.RemoteModel.upsert({first: 'joe', id: 7}, function(err, user) {
|
ClientModel.upsert({first: 'joe', id: 7}, function(err, user) {
|
||||||
if (err) return done(err);
|
|
||||||
assert.equal(user.first, 'joe');
|
|
||||||
|
|
||||||
ctx.RemoteModel.upsert({first: 'bob', id: 7}, function(err,
|
|
||||||
updatedUser) {
|
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert.equal(updatedUser.first, 'bob');
|
assert.equal(user.first, 'joe');
|
||||||
done();
|
|
||||||
|
ClientModel.upsert({first: 'bob', id: 7}, function(err,
|
||||||
|
updatedUser) {
|
||||||
|
if (err) return done(err);
|
||||||
|
assert.equal(updatedUser.first, 'bob');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model.deleteById(id, [callback])', function() {
|
describe('Model.deleteById(id, [callback])', function() {
|
||||||
it('should delete a model instance from the attached data source',
|
it('should delete a model instance from the attached data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
ctx.ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
|
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);
|
if (err) return done(err);
|
||||||
ctx.RemoteModel.findById(user.id, function(err, notFound) {
|
ClientModel.deleteById(user.id, function(err) {
|
||||||
assert.equal(notFound, null);
|
if (err) return done(err);
|
||||||
assert(err && err.statusCode === 404,
|
ClientModel.findById(user.id, function(err, notFound) {
|
||||||
'should have failed with HTTP 404');
|
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);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Model.findById(id, callback)', function() {
|
it('should return false when there is no model with the given id',
|
||||||
it('should find an instance by id from the attached data source',
|
function(done) {
|
||||||
function(done) {
|
ClientModel.exists('user-id-does-not-exist', function(err, exist) {
|
||||||
ctx.ServerModel.create({first: 'michael', last: 'jordan', id: 23},
|
|
||||||
function(err) {
|
|
||||||
if (err) return done(err);
|
|
||||||
ctx.RemoteModel.findById(23, function(err, user) {
|
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert.equal(user.id, 23);
|
assert.equal(exist, false);
|
||||||
assert.equal(user.first, 'michael');
|
|
||||||
assert.equal(user.last, 'jordan');
|
|
||||||
done();
|
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(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);
|
||||||
|
assert.equal(notFound, null);
|
||||||
|
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() {
|
describe('Model.count([query], callback)', function() {
|
||||||
it('should return the count of Model instances from both data source',
|
it('should return the count of Model instances from both data source',
|
||||||
function(done) {
|
function(done) {
|
||||||
var taskEmitter = new TaskEmitter();
|
const taskEmitter = new TaskEmitter();
|
||||||
taskEmitter
|
taskEmitter
|
||||||
.task(ctx.ServerModel, 'create', {first: 'jill', age: 100})
|
.task(ServerModel, 'create', {first: 'jill', age: 100})
|
||||||
.task(ctx.RemoteModel, 'create', {first: 'bob', age: 200})
|
.task(ClientModel, 'create', {first: 'bob', age: 200})
|
||||||
.task(ctx.RemoteModel, 'create', {first: 'jan'})
|
.task(ClientModel, 'create', {first: 'jan'})
|
||||||
.task(ctx.ServerModel, 'create', {first: 'sam'})
|
.task(ServerModel, 'create', {first: 'sam'})
|
||||||
.task(ctx.ServerModel, 'create', {first: 'suzy'})
|
.task(ServerModel, 'create', {first: 'suzy'})
|
||||||
.on('done', function(err) {
|
.on('done', function(err) {
|
||||||
if (err) return done(err);
|
|
||||||
ctx.RemoteModel.count({age: {gt: 99}}, function(err, count) {
|
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assert.equal(count, 2);
|
ClientModel.count({age: {gt: 99}}, function(err, count) {
|
||||||
done();
|
if (err) return done(err);
|
||||||
|
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});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue