Compare commits

...

72 Commits

Author SHA1 Message Date
Diana Lau a529986c65
Merge pull request #106 from strongloop/docs/end-of-life
docs: end of life
2021-03-05 19:59:17 -05:00
Miroslav Bajtoš c362bcf930
docs: end of life
LB3 has reached end of life and this connector does not support LB4.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2021-03-03 09:23:27 +01:00
Miroslav Bajtoš 64c88ab382
Merge pull request #105 from strongloop/node14
chore: add Node.js 14 to CI
2020-09-11 11:58:46 +02:00
Diana Lau 4b5ac98379 chore: add Node.js 14 to CI
Signed-off-by: Diana Lau <dhmlau@ca.ibm.com>
2020-09-10 15:15:24 -04:00
Diana Lau 2120e96dbf
Merge pull request #104 from strongloop/dco
chore: switch to DCO
2020-08-31 13:25:21 -04:00
Diana Lau be758fa770 chore: switch to DCO
Signed-off-by: Diana Lau <dhmlau@ca.ibm.com>
2020-08-20 21:22:09 -04:00
Agnes Lin b8fd18e217
Merge pull request #103 from strongloop/copyright
chore: update copyright year
2020-02-10 14:05:41 -05:00
Diana Lau 334b705c69 chore: update copyright year 2020-02-10 13:49:27 -05:00
Nora 4bfa461229
Merge pull request #101 from strongloop/chore/improve-issue-templates
chore: improve issue and PR templates
2019-11-21 09:46:27 -05:00
Nora 4f8a372d2c chore: improve issue and PR templates 2019-11-19 11:25:06 -05:00
Nora 22ee132941
Merge pull request #102 from strongloop/fix-comma-dangle
chore: fix eslint violations
2019-11-19 11:24:39 -05:00
Nora 1975481cb0 chore: fix eslint violations
Fix comma-dangle violations
2019-11-15 21:03:12 -05:00
Miroslav Bajtoš c03161dfe0
Merge pull request #100 from strongloop/fix/loopback-remote-invoke-updateAll
Add a test to verify PersistedModel updateAll
2019-10-07 11:12:09 +02:00
Miroslav Bajtoš 39771be12f
Add a test to verify PersistedModel updateAll
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 10:49:57 +02:00
Miroslav Bajtoš 4ff55feb24
Merge pull request #99 from strongloop/update-eslint
Update eslint & config to latest
2019-10-04 10:04:32 +02:00
Miroslav Bajtoš 0097580537
Update eslint & config to latest
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-03 10:54:13 +02:00
Miroslav Bajtoš 4efca1f052
Merge pull request #95 from angfal/fix/remote_exists
Add tests to check Model.exists() working
2019-08-23 10:27:56 +02:00
Maxim Sharai 2aff46a79a
Add tests to verify Model.exists() is working 2019-08-23 10:16:02 +02:00
Nora 6caf45d39e
Merge pull request #97 from strongloop/add-node-12
Drop support for Node.js 6 and add Node.js 12 to CI
2019-07-25 17:46:11 -04:00
Nora acbe35879b add node 12 to CI 2019-07-25 15:39:50 -04:00
Nora e5e4ba93ef drop support for node 6 2019-07-25 15:39:33 -04:00
Diana Lau 61f031aeb9
Merge pull request #96 from strongloop/copyrights
chore: update copyrights years
2019-05-08 11:47:25 -04:00
Agnes Lin 59f70ab3ce chore: update copyrights years 2019-05-07 13:34:15 -04:00
Miroslav Bajtoš 3e7537e1dd
3.4.1
* fix: return null when findById/findOne returns 404 (Jannis Ötjengerdes)
 * style: fix linting (virkt25)
2019-01-22 16:47:50 +01:00
Miroslav Bajtoš 4245df5c41
Merge pull request #94 from angfal/404_error_catch
Catch error and return null when findById/findOne doesn't find an entity
2019-01-22 16:46:58 +01:00
Jannis Ötjengerdes 4108db8945
fix: return null when findById/findOne returns 404
Co-authored-by: Maxim Sharai <maxim.sharai@tispr.com>
Co-authored-by: Jannis Ötjengerdes <joetjengerdes@rightmart.de>
2019-01-21 16:32:41 +01:00
virkt25 1a7b1be96d style: fix linting
- update eslint and eslint config
- fix linting
2018-08-24 09:14:41 -04:00
Taranveer Virk 6e09d0b4bf 3.4.0
* move dependencies (Stephen Crawford)
 * chore: drop node 4 and update deps (Taranveer Virk)
2018-06-21 02:13:08 -04:00
Taranveer Virk ae64f198ae
Merge pull request #89 from srcrawford/master
Move eslint dependencies to devDependencies
2018-06-21 02:10:10 -04:00
Stephen Crawford a504e25752 move dependencies 2018-06-21 00:46:37 +01:00
Taranveer Virk e2dad4e39a
Merge pull request #88 from strongloop/drop-node
chore: drop node 4 and update deps
2018-06-20 13:33:36 -04:00
Taranveer Virk 974694b526 chore: drop node 4 and update deps
Signed-off-by: Taranveer Virk <taranveer@virk.cc>
2018-06-20 10:29:51 -04:00
Miroslav Bajtoš 0c00806f77
3.3.1
* chore: update copyright notice years (Miroslav Bajtoš)
 * Fix duplicate definition of a remote model type (maxim.sharai)
2018-01-16 15:55:45 +01:00
Miroslav Bajtoš 60c081f893
chore: update copyright notice years 2018-01-16 15:55:09 +01:00
Miroslav Bajtoš 823a20f89d
Merge pull request #85 from angfal/#81
Fix duplicate definition of a remote model type
2018-01-16 15:52:48 +01:00
maxim.sharai af125c50cc
Fix duplicate definition of a remote model type
Before this commit, when a remote model had relations, the model was
registered an additional time per each relation. As a result,
the following warnings were printed to the console

   Warning: overriding remoting type $MODEL_NAME

This commit fixes registration of models with strong-remoting to avoid
those warnings.
2018-01-16 15:15:45 +01:00
Miroslav Bajtoš 906979a960
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-12 17:26:04 +01:00
Miroslav Bajtoš d622ce5ed8
Merge pull request #78 from mitsos1os/issue-42
Support retrieved related models from remote loopback service
2017-12-12 13:08:55 +01:00
Miroslav Bajtoš 4ec0ec5341
Gruntfile: remove forgotten jshint task 2017-12-12 09:39:46 +01:00
Dimitris a3d110b78c
Preserve related models from "include" filter
Before this change, when making a remote call with "include" filter
(for example `findById(11, {include:['children']})`), the related
models were removed from the result.

This commit fixes the implementation to correctly preserve related
models and also to cast them to correct model instances.
2017-12-12 09:39:37 +01:00
Miroslav Bajtoš ffaaec8973
Merge pull request #82 from strongloop/code-cleanup
Code cleanup
2017-12-11 15:43:40 +01:00
Miroslav Bajtoš 55cb88f727
Add eslint to npm test, fix linter issues 2017-12-11 14:51:48 +01:00
Miroslav Bajtoš d6a5c768fd
Refactor tests to use local per-app model registry
Refactor tests to stop sharing global models between different test
suites and use local per-app model registry instead.

Also clean up all test code to use `const` and `let` instead of `var`.
2017-12-11 14:45:43 +01:00
Miroslav Bajtoš fafc0e36f3
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š)
2017-12-05 14:43:32 +01:00
Miroslav Bajtoš 607fec76be
Merge pull request #77 from agriwebb/pass-args-connector
Add support for configuring remoting options
2017-12-05 14:42:53 +01:00
Kenny Sabir 816e989b4e
Add support for configuring remoting options
Allow remote-connector users to provide "options" property in the
datasource configuration, this "options" object is then passed down to
RemoteObjects and allows e.g. configuration of pass-through
authorization, where the remoting connector passes the access token
used to make the incoming request down to the backend service invoked.
2017-12-05 14:22:10 +01:00
Diana Lau 07d6e1b66e
Merge pull request #80 from strongloop/license
chore:update license
2017-11-10 22:52:33 -05:00
Diana Lau 1afbe06716 chore:update license 2017-11-09 13:33:09 -05:00
crandmck 8e0c39b47f Move remote connector doc into README 2017-11-07 14:25:19 -08:00
Miroslav Bajtoš ed20519ce7 Merge pull request #79 from strongloop/add-travis
build: enable Travis CI
2017-10-17 14:37:26 +02:00
Miroslav Bajtoš 80f29b9c91
build: enable Travis CI 2017-10-17 14:25:32 +02:00
Kevin Delisle 94b2eba6ab Add stalebot configuration 2017-08-22 13:14:17 -04:00
Sakib Hasan 434840522c Update Issue and PR Templates (#76)
* update issue template

* update pr template
2017-08-16 14:25:29 -04:00
Miroslav Bajtoš 1cf92430c5 Merge pull request #73 from strongloop/replicate-issue-template
Replicate issue_template from loopback repo
2017-07-27 09:04:00 +02:00
Miroslav Bajtoš ab5992c3b9 Merge pull request #75 from strongloop/add-codeowner
Add CODEOWNERS file
2017-07-27 09:02:09 +02:00
Diana Lau 468c4ae0a7 Add CODEOWNER file 2017-07-24 19:47:21 -04:00
Siddhi Pai 94cb7f08a9 Replicate new issue_template from loopback 2017-02-15 15:29:23 -08:00
Siddhi Pai 7af85add04 Replicate issue_template from loopback repo 2017-02-13 10:27:33 -08:00
Miroslav Bajtoš 1534626f01 Merge pull request #71 from strongloop/fix/computed-options-arg
Add "options" arg to stubbed models in tests
2016-12-22 11:13:26 +01:00
Miroslav Bajtoš 1168bb6080 Add "options" arg to stubbed models in tests 2016-12-22 10:56:11 +01:00
Miroslav Bajtoš 45fa573432 3.1.1
* Update package.json for LB3 release (Simon Ho)
 * Update paid support URL (Siddhi Pai)
2016-12-21 16:03:43 +01:00
Miroslav Bajtoš 7259631821 Merge pull request #69 from strongloop/release/3.x
Update package.json for LB3 release
2016-12-21 10:46:15 +01:00
Simon Ho d728e57276 Update package.json for LB3 release 2016-12-21 00:46:48 -08:00
Simon Ho 21ed5a9b93 Merge pull request #68 from strongloop/update-support-URL
Replicate .github from loopback repo
2016-12-06 23:15:19 -08:00
Siddhi Pai 0d687896a9 Update paid support URL 2016-12-06 03:08:27 -08:00
Miroslav Bajtoš 1506a5039e 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-12-05 10:16:31 +01:00
Miroslav Bajtoš 352b38128a Merge pull request #67 from strongloop/drop-support-node-0x
Drop support for Node v0.10 and v0.12
2016-11-15 15:05:12 +01:00
Miroslav Bajtoš 832e990337 Drop support for Node v0.10 and v0.12 2016-11-15 14:19:17 +01:00
Miroslav Bajtoš 5db2240348 Merge pull request #66 from strongloop/fix/readme
Fix version info in README
2016-10-17 13:38:26 +02:00
Miroslav Bajtoš 3cf34c4264 Fix version info in README 2016-10-17 13:05:53 +02:00
Miroslav Bajtoš a1f09dba22 Merge pull request #64 from strongloop/update-lb-3-rc
Update deps to loopback 3.0.0 RC
2016-09-22 14:04:39 +02:00
Miroslav Bajtoš 625f926a87 Update deps to loopback 3.0.0 RC 2016-09-22 13:41:56 +02:00
26 changed files with 1117 additions and 651 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
coverage

133
.eslintrc
View File

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

50
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -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_

View File

@ -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.

27
.github/ISSUE_TEMPLATE/Question.md vendored Normal file
View File

@ -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/
-->

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -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.

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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)

23
.github/stale.yml vendored Normal file
View File

@ -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.

1
.npmrc Normal file
View File

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

7
.travis.yml Normal file
View File

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

View File

@ -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
=========================

6
CODEOWNERS Normal file
View File

@ -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

View File

@ -19,133 +19,20 @@ Contributing to `loopback-connector-remote` is easy. In a few simple steps:
* 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
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.
git commit -s -m "feat: my commit message"
```
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 Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml

View File

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

View File

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

139
README.md
View File

@ -1,41 +1,142 @@
# 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.
- Use the older range 1.x for applications using LoopBack v2.
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible.
Learn more about
<a href="https://loopback.io/doc/en/contrib/Long-term-support.html">LoopBack's long term support policy.</a>**
## 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
```json
"MyMicroService": {
"name": "MyMicroService",
"connector": "remote"
}
Use loopback-connector-remote:
- Version 3.x with LoopBack v3 and later.
- Prior versions with LoopBack v2.
## Installation
In your application root directory, enter:
```shell
$ npm install loopback-connector-remote --save
```
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.
The connector will generate models on the MyMicroService datasource object based on the models/methods exposed from the remote service. Those models will have methods attached that are
from the model's remote methods. So if you exposed a remote method from that micro-service called `bar` from the model `foo`,
The connector will generate models on the myRemoteDataSource datasource object based on the models/methods exposed from the remote service. Those models will have methods attached that are
from the model's remote methods. So if the model `foo` exposes a remote method called `bar`,
the connector will automatically generate the following:
`app.datasources.MyMicroService.models.foo.bar()`
`app.datasources.myRemoteDataSource.models.foo.bar()`
### Access it in any model file
To access the remote Loopback service in a model:
```javascript
module.exports = function(Message) {
Message.test = function (cb) {
Message.app.datasources.MyMicroService.models.SomeModel.remoteMethodNameHere(function () {});
Message.test = function (cb) {
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
}
```

View File

@ -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');

View File

@ -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
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
@ -8,8 +8,8 @@
/*!
* Dependencies
*/
var relation = require('loopback-datasource-juggler/lib/relation-definition');
var RelationDefinition = relation.RelationDefinition;
const relation = require('loopback-datasource-juggler/lib/relation-definition');
const RelationDefinition = relation.RelationDefinition;
module.exports = RelationMixin;
@ -75,7 +75,7 @@ function RelationMixin() {
* @property {Object} model Model object
*/
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);
defineRelationProperty(this, def);
};
@ -139,7 +139,7 @@ RelationMixin.hasMany = function hasMany(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);
defineRelationProperty(this, def);
};
@ -179,31 +179,31 @@ RelationMixin.belongsTo = function(modelTo, params) {
*/
RelationMixin.hasAndBelongsToMany =
function hasAndBelongsToMany(modelTo, params) {
var def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
const def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
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);
defineRelationProperty(this, def);
};
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);
defineRelationProperty(this, def);
};
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);
defineRelationProperty(this, def);
};
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);
defineRelationProperty(this, def);
};
@ -211,10 +211,23 @@ RelationMixin.embedsMany = function embedsMany(modelTo, params) {
function defineRelationProperty(modelClass, def) {
Object.defineProperty(modelClass.prototype, def.name, {
get: function() {
var that = this;
var scope = function() {
return that['__get__' + def.name].apply(that, arguments);
const that = this;
const scope = function() {
const cachedEntities = that.__cachedRelations &&
that.__cachedRelations[def.name];
if (arguments.length || !cachedEntities) {
return that['__get__' + def.name].apply(that, arguments);
}
// return the cached data
if (Array.isArray(cachedEntities)) {
return cachedEntities.map(data => new def.modelTo(data));
} else {
return new def.modelTo(cachedEntities);
}
};
scope.count = function() {
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 scope;
}
},
});
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
));
});
});
});
});

View File

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

View File

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

View File

@ -1,161 +1,389 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var assert = require('assert');
var helper = require('./helper');
var TaskEmitter = require('strong-task-emitter');
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
describe('Remote model tests', function() {
var ctx = this;
let serverApp, ServerModel, ServerRelatedModel, ServerModelWithSingleChild,
clientApp, ClientModel, ClientRelatedModel, ClientModelWithSingleChild;
beforeEach(function(done) {
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource(),
properties: helper.userProperties
beforeEach(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
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) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp),
properties: helper.userProperties
beforeEach(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {
strict: true,
},
});
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() {
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
serverApp.locals.handler.close();
ServerModel = null;
ServerRelatedModel = null;
ClientModel = null;
});
describe('Model.create([data], [callback])', function() {
it('should create an instance and save to the attached data source',
function(done) {
ctx.RemoteModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ctx.RemoteModel);
done();
function(done) {
ClientModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ClientModel);
done();
});
});
});
});
describe('model.save([options], [callback])', function() {
it('should save an instance of a Model to the attached data source',
function(done) {
var joe = new ctx.RemoteModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
function(done) {
const joe = new ClientModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
});
});
});
describe('model.updateAttributes(data, [callback])', function() {
it('should save specified attributes to the attached data source',
function(done) {
ctx.ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
}, function(err, updatedUser) {
function(done) {
ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast',
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
});
});
});
});
describe('Model.upsert(data, callback)', function() {
it('should update when a record with id=data.id is found, insert otherwise',
function(done) {
ctx.RemoteModel.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) {
function(done) {
ClientModel.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
assert.equal(user.first, 'joe');
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() {
it('should delete a model instance from the attached data source',
function(done) {
ctx.ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
ctx.RemoteModel.deleteById(user.id, function(err) {
function(done) {
ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
ctx.RemoteModel.findById(user.id, function(err, notFound) {
assert.equal(notFound, null);
assert(err && err.statusCode === 404,
'should have failed with HTTP 404');
ClientModel.deleteById(user.id, function(err) {
if (err) return done(err);
ClientModel.findById(user.id, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
});
});
});
describe('Model.exists(id, callback)', function() {
it('should return true when the model with the given id exists',
function(done) {
ServerModel.create({first: 'max'}, function(err, user) {
if (err) return done(err);
ClientModel.exists(user.id, function(err, exist) {
if (err) return done(err);
assert.equal(exist, true);
done();
});
});
});
});
});
describe('Model.findById(id, callback)', function() {
it('should find an instance by id from the attached data source',
function(done) {
ctx.ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(err) {
if (err) return done(err);
ctx.RemoteModel.findById(23, function(err, user) {
it('should return false when there is no model with the given id',
function(done) {
ClientModel.exists('user-id-does-not-exist', function(err, exist) {
if (err) return done(err);
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
assert.equal(exist, false);
done();
});
});
});
});
describe('Model.findById(id, callback)', function() {
it('should return null when an instance does not exist',
function(done) {
ClientModel.findById(23, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
it('should find an instance by id from the attached data source',
function(done) {
ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(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() {
it('should return the count of Model instances from both data source',
function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(ctx.ServerModel, 'create', {first: 'jill', age: 100})
.task(ctx.RemoteModel, 'create', {first: 'bob', age: 200})
.task(ctx.RemoteModel, 'create', {first: 'jan'})
.task(ctx.ServerModel, 'create', {first: 'sam'})
.task(ctx.ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
if (err) return done(err);
ctx.RemoteModel.count({age: {gt: 99}}, function(err, count) {
function(done) {
const taskEmitter = new TaskEmitter();
taskEmitter
.task(ServerModel, 'create', {first: 'jill', age: 100})
.task(ClientModel, 'create', {first: 'bob', age: 200})
.task(ClientModel, 'create', {first: 'jan'})
.task(ServerModel, 'create', {first: 'sam'})
.task(ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
if (err) return done(err);
assert.equal(count, 2);
done();
ClientModel.count({age: {gt: 99}}, function(err, count) {
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});
});
});
});