├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── circle.yml ├── conf.json ├── dist └── js-data-cloud-datastore.d.ts ├── mocha.start.js ├── package.json ├── rollup.config.js └── src └── index.js /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA] 13 | (https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a [corporate CLA] 16 | (https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 27 | 1. Fork the desired repo, develop and test your code changes. 28 | 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the 29 | [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 30 | recommended coding standards for this organization. You can run `npm run jshint` to match our JavaScript coding standards. 31 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 32 | 1. Submit a pull request! 33 | 34 | ## Support 35 | 36 | [Find out how to Get Support](http://js-data.io/docs/support). 37 | 38 | ## Community 39 | 40 | [Explore the Community](http://js-data.io/docs/community). 41 | 42 | ### Have write access? 43 | 44 | To cut a release: 45 | 46 | 1. Checkout master 47 | 1. Bump version in `package.json` appropriately 48 | 1. Run `npm run release` 49 | 1. Update `CHANGELOG.md` appropriately 50 | 1. Commit and push changes, including the `dist/` folder 51 | 1. Make a GitHub release 52 | - set tag name to version 53 | - set release name to version 54 | - set release body to changelog entry for the version 55 | - attach the files in the `dist/` folder 56 | 1. `npm publish .` 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | (delete this line) GitHub Issues are NOT for support questions. 2 | (delete this line) GitHub Issues ARE for bug reports, feature requests, and other issues. 3 | (delete this line) Find out how to Get Support here: http://js-data.io/docs/support. 4 | 5 | 6 | 7 | Thanks! 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # (it's a good idea to open an issue first for discussion) 2 | 3 | - [ ] - `npm test` succeeds 4 | - [ ] - Pull request has been squashed into 1 commit 5 | - [ ] - I did NOT commit changes to `dist/` 6 | - [ ] - Code coverage does not decrease (if any source code was changed) 7 | - [ ] - Appropriate JSDoc comments were updated in source code (if applicable) 8 | - [ ] - Approprate changes to js-data.io docs have been suggested ("Suggest Edits" button) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | dist/*.map 3 | node_modules/ 4 | coverage/ 5 | doc/ 6 | junit/ 7 | .nyc_output/ 8 | *.log -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of js-data-cloud-datastore authors for copyright 2 | # purposes. This file is distinct from the CONTRIBUTORS files. See the latter 3 | # for an explanation. 4 | # 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # 8 | # The email address is not required for organizations. 9 | # 10 | Google Inc. 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##### 1.0.0-rc.1 - 23 August 2016 2 | 3 | ###### Breaking changes 4 | - Now depends on `js-data@3.0.0-rc.4` or greater 5 | - Switched from `gcloud` to `@google-cloud/datastore` 6 | 7 | ###### Backwards compatible changes 8 | - Updated other dependencies 9 | 10 | ##### 1.0.0-beta.2 - 28 May 2016 11 | 12 | Updated dependencies 13 | 14 | ##### 1.0.0-beta.1 - 10 May 2016 15 | 16 | ###### Breaking changes 17 | - Now depends on js-data 3.0.0-beta.5 18 | - Now you must import like this: 19 | 20 | ```js 21 | // CommonJS 22 | var JSDataCloudDatastore = require('js-data-cloud-datastore') 23 | var CloudDatastoreAdapter = JSDataCloudDatastore.CloudDatastoreAdapter 24 | var adapter = new CloudDatastoreAdapter({...}) 25 | ``` 26 | 27 | ```js 28 | // ES2015 modules 29 | import {CloudDatastoreAdapter} from 'js-data-cloud-datastore' 30 | const adapter = new CloudDatastoreAdapter({...}) 31 | ``` 32 | 33 | ###### Other 34 | - Upgraded other dependencies 35 | 36 | ##### 1.0.0-alpha.3 - 10 March 2016 37 | 38 | ###### Other 39 | - Moved more common adapter functionality into js-data-adapter 40 | 41 | ##### 1.0.0-alpha.2 - 08 March 2016 42 | 43 | ###### Other 44 | - Now using js-data-adapter 45 | - Now using js-data-repo-tools 46 | 47 | ##### 1.0.0-alpha.1 - 29 February 2016 48 | 49 | - Initial release 50 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file lists people. For 3 | # example, Google employees are listed here but not in AUTHORS, because Google 4 | # holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | Jason Dobry 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | js-data logo 2 | 3 | # js-data-cloud-datastore 4 | 5 | [![Slack Status][sl_b]][sl_l] 6 | [![npm version][npm_b]][npm_l] 7 | [![Circle CI][circle_b]][circle_l] 8 | [![npm downloads][dn_b]][dn_l] 9 | [![Coverage Status][cov_b]][cov_l] 10 | 11 | Google Cloud Datastore adapter for [js-data](http://www.js-data.io/). 12 | 13 | __Note:__ This adapter is in beta, and uses the 3.0 beta version of js-data. 14 | 15 | To get started, visit __[http://js-data.io](http://www.js-data.io/docs/js-data-cloud-datastore)__. 16 | 17 | ## Links 18 | 19 | * [Quick start](http://www.js-data.io/docs/home#quick-start) - Get started in 5 minutes 20 | * [Guides and Tutorials](http://www.js-data.io/docs/home) - Learn how to use JSData 21 | * [`CloudDatastoreAdapter` Guide](http://www.js-data.io/docs/js-data-cloud-datastore) - Learn how to use `CloudDatastoreAdapter` 22 | * [API Reference Docs](http://api.js-data.io) - Explore components, methods, options, etc. 23 | * [Community & Support](http://js-data.io/docs/community) - Find solutions and chat with the community 24 | * [General Contributing Guide](http://js-data.io/docs/contributing) - Give back and move the project forward 25 | * [Contributing to js-data-cloud-datastore](https://github.com/js-data/js-data-cloud-datastore/blob/master/.github/CONTRIBUTING.md) 26 | 27 | ## License 28 | 29 | Apache Version 2.0 30 | 31 | Copyright (c) 2016 js-data-cloud-datastore project authors 32 | 33 | * [LICENSE](https://github.com/GoogleCloudPlatform/js-data-cloud-datastore/blob/master/LICENSE) 34 | * [AUTHORS](https://github.com/GoogleCloudPlatform/js-data-cloud-datastore/blob/master/AUTHORS) 35 | * [CONTRIBUTORS](https://github.com/GoogleCloudPlatform/js-data-cloud-datastore/blob/master/CONTRIBUTORS) 36 | 37 | [sl_b]: http://slack.js-data.io/badge.svg 38 | [sl_l]: http://slack.js-data.io 39 | [npm_b]: https://img.shields.io/npm/v/js-data-cloud-datastore.svg?style=flat 40 | [npm_l]: https://www.npmjs.org/package/js-data-cloud-datastore 41 | [circle_b]: https://img.shields.io/circleci/project/GoogleCloudPlatform/js-data-cloud-datastore.svg?style=flat 42 | [circle_l]: https://circleci.com/gh/GoogleCloudPlatform/js-data-cloud-datastore 43 | [dn_b]: https://img.shields.io/npm/dm/js-data-cloud-datastore.svg?style=flat 44 | [dn_l]: https://www.npmjs.org/package/js-data-cloud-datastore 45 | [cov_b]: https://img.shields.io/codecov/c/github/GoogleCloudPlatform/js-data-cloud-datastore.svg?style=flat 46 | [cov_l]: https://codecov.io/github/GoogleCloudPlatform/js-data-cloud-datastore 47 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | branches: 3 | ignore: 4 | - gh-pages 5 | machine: 6 | node: 7 | version: 5.7.0 8 | dependencies: 9 | pre: 10 | - npm i -g npm codecov nyc 11 | - npm i js-data@^3.0.0-beta.8 gcloud 12 | test: 13 | pre: 14 | - echo $KEYFILE > key.json 15 | post: 16 | - nyc report --reporter=lcov | codecov 17 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "includePattern": ".*js$" 4 | }, 5 | "plugins": ["plugins/markdown"], 6 | "opts": { 7 | "template": "./node_modules/ink-docstrap/template", 8 | "destination": "./doc/", 9 | "recurse": true, 10 | "verbose": true, 11 | "readme": "./README.md", 12 | "package": "./package.json" 13 | }, 14 | "templates": { 15 | "theme": "jsdata", 16 | "systemName": "js-data-cloud-datastore", 17 | "copyright": "js-data-cloud-datastore Copyright © 2014-2016 js-data-cloud-datastore project authors", 18 | "outputSourceFiles": true, 19 | "linenums": true, 20 | "footer": "" 21 | } 22 | } -------------------------------------------------------------------------------- /dist/js-data-cloud-datastore.d.ts: -------------------------------------------------------------------------------- 1 | import {Adapter} from 'js-data-adapter' 2 | 3 | interface IDict { 4 | [key: string]: any; 5 | } 6 | interface IBaseAdapter extends IDict { 7 | debug?: boolean, 8 | raw?: boolean 9 | } 10 | interface IBaseCloudDatastoreAdapter extends IBaseAdapter { 11 | gcloudOpts?: IDict 12 | gcloud?: any 13 | } 14 | export class CloudDatastoreAdapter extends Adapter { 15 | static extend(instanceProps?: IDict, classProps?: IDict): typeof CloudDatastoreAdapter 16 | constructor(opts?: IBaseCloudDatastoreAdapter) 17 | } 18 | export interface OPERATORS { 19 | '==': Function 20 | '===': Function 21 | '!=': Function 22 | '!==': Function 23 | '>': Function 24 | '>=': Function 25 | '<': Function 26 | '<=': Function 27 | } 28 | export interface version { 29 | full: string 30 | minor: string 31 | major: string 32 | patch: string 33 | alpha: string | boolean 34 | beta: string | boolean 35 | } -------------------------------------------------------------------------------- /mocha.start.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*global assert:true */ 16 | 17 | 'use strict'; 18 | 19 | // prepare environment for js-data-adapter-tests 20 | import 'babel-polyfill'; 21 | 22 | import * as JSData from 'js-data'; 23 | import JSDataAdapterTests from './node_modules/js-data-adapter/dist/js-data-adapter-tests'; 24 | import * as JSDataCloudDatastore from './src/index'; 25 | 26 | const assert = global.assert = JSDataAdapterTests.assert; 27 | global.sinon = JSDataAdapterTests.sinon; 28 | 29 | const datastoreOpts = { 30 | projectId: process.env.GCLOUD_PROJECT 31 | }; 32 | 33 | if (process.env.KEYFILE_PATH) { 34 | datastoreOpts.keyFilename = process.env.KEYFILE_PATH; 35 | } 36 | 37 | /** 38 | * Why a slow adapter? Datastore is eventually consistent, so this reduces the 39 | * flakiness of the tests. 40 | */ 41 | class SlowAdapter extends JSDataCloudDatastore.CloudDatastoreAdapter { 42 | create (...args) { 43 | return super.create(...args).then((result) => { 44 | return new JSData.utils.Promise((resolve) => { 45 | setTimeout(() => resolve(result), 500); 46 | }); 47 | }); 48 | } 49 | createMany (...args) { 50 | return super.createMany(...args).then((result) => { 51 | return new JSData.utils.Promise((resolve) => { 52 | setTimeout(() => resolve(result), 500); 53 | }); 54 | }); 55 | } 56 | update (...args) { 57 | return super.update(...args).then((result) => { 58 | return new JSData.utils.Promise((resolve) => { 59 | setTimeout(() => resolve(result), 500); 60 | }); 61 | }); 62 | } 63 | updateAll (...args) { 64 | return super.updateAll(...args).then((result) => { 65 | return new JSData.utils.Promise((resolve) => { 66 | setTimeout(() => resolve(result), 500); 67 | }); 68 | }); 69 | } 70 | updateMany (...args) { 71 | return super.updateMany(...args).then((result) => { 72 | return new JSData.utils.Promise((resolve) => { 73 | setTimeout(() => resolve(result), 500); 74 | }); 75 | }); 76 | } 77 | } 78 | 79 | JSDataAdapterTests.init({ 80 | debug: false, 81 | JSData: JSData, 82 | Adapter: SlowAdapter, 83 | adapterConfig: { 84 | debug: false, 85 | datastoreOpts: datastoreOpts 86 | }, 87 | xfeatures: [ 88 | 'findBelongsToNested', 89 | 'findBelongsToHasManyNested', 90 | 'findHasManyLocalKeys', 91 | 'findHasManyForeignKeys', 92 | 'findAllInOp', 93 | 'findAllLikeOp', 94 | 'findAllBelongsTo', 95 | 'findAllBelongsToNested', 96 | 'findAllBelongsToHasMany', 97 | 'findAllBelongsToHasManyNested', 98 | 'findAllGroupedWhere', 99 | 'filterOnRelations' 100 | ] 101 | }); 102 | 103 | describe('exports', function () { 104 | it('should have correct exports', function () { 105 | assert(JSDataCloudDatastore.CloudDatastoreAdapter); 106 | assert(JSDataCloudDatastore.OPERATORS); 107 | assert(JSDataCloudDatastore.OPERATORS['==']); 108 | assert(JSDataCloudDatastore.version); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-data-cloud-datastore", 3 | "description": "Google Cloud Datastore adapter for js-data.", 4 | "version": "1.0.0-rc.1", 5 | "homepage": "https://github.com/GoogleCloudPlatform/js-data-cloud-datastore", 6 | "license": "Apache-2.0", 7 | "author": "js-data-cloud-datastore project authors", 8 | "contributors": [ 9 | "Jason Dobry " 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/GoogleCloudPlatform/js-data-cloud-datastore.git" 14 | }, 15 | "main": "./dist/js-data-cloud-datastore.js", 16 | "typings": "./dist/js-data-cloud-datastore.d.ts", 17 | "files": [ 18 | "dist/", 19 | "src/", 20 | "AUTHORS", 21 | "CONTRIBUTORS" 22 | ], 23 | "keywords": [ 24 | "data", 25 | "datastore", 26 | "store", 27 | "database", 28 | "adapter", 29 | "cloud" 30 | ], 31 | "semistandard": { 32 | "parser": "babel-eslint", 33 | "globals": [ 34 | "describe", 35 | "it", 36 | "sinon", 37 | "assert", 38 | "before", 39 | "after", 40 | "beforeEach", 41 | "afterEach" 42 | ], 43 | "ignore": [ 44 | "dist/" 45 | ] 46 | }, 47 | "babel": { 48 | "presets": [ 49 | "es2015" 50 | ] 51 | }, 52 | "scripts": { 53 | "lint": "semistandard \"**/*.js\"", 54 | "bundle": "rollup -c rollup.config.js -f cjs -o dist/js-data-cloud-datastore.js -m dist/js-data-cloud-datastore.js.map src/index.js && repo-tools write-version dist/js-data-cloud-datastore.js", 55 | "doc": "jsdoc -c conf.json src node_modules/js-data-adapter/src", 56 | "build": "npm run lint && npm run bundle", 57 | "mocha": "mocha -t 20000 -R dot -r babel-core/register -r babel-polyfill mocha.start.js", 58 | "cover": "nyc --require babel-core/register --require babel-polyfill --cache mocha -t 20000 -R dot mocha.start.js && nyc report --reporter=html", 59 | "test": "npm run build && npm run cover", 60 | "release": "npm test && npm run doc && repo-tools updates && repo-tools changelog" 61 | }, 62 | "dependencies": { 63 | "@google-cloud/datastore": "^0.1.1", 64 | "js-data-adapter": "~0.8.2" 65 | }, 66 | "peerDependencies": { 67 | "js-data": "^3.0.0-rc.4" 68 | }, 69 | "devDependencies": { 70 | "js-data-repo-tools": "0.5.6", 71 | "semistandard": "8.0.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | var babel = require('rollup-plugin-babel'); 2 | 3 | module.exports = { 4 | external: [ 5 | 'gcloud', 6 | 'js-data', 7 | 'js-data-adapter' 8 | ], 9 | plugins: [ 10 | babel({ 11 | babelrc: false, 12 | presets: [ 13 | 'es2015-rollup' 14 | ], 15 | exclude: 'node_modules/**' 16 | }) 17 | ] 18 | }; 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | import { utils } from 'js-data'; 18 | import Datastore from '@google-cloud/datastore'; 19 | import { 20 | Adapter, 21 | reserved 22 | } from 'js-data-adapter'; 23 | 24 | const DATASTORE_DEFAULTS = { 25 | projectId: process.env.GCLOUD_PROJECT 26 | }; 27 | 28 | const equal = function (query, field, value) { 29 | return query.filter(field, '=', value); 30 | }; 31 | 32 | /** 33 | * Default predicate functions for the filtering operators. 34 | * 35 | * @name module:js-data-cloud-datastore.OPERATORS 36 | * @property {function} == Equality operator. 37 | * @property {function} > "Greater than" operator. 38 | * @property {function} >= "Greater than or equal to" operator. 39 | * @property {function} < "Less than" operator. 40 | * @property {function} <= "Less than or equal to" operator. 41 | */ 42 | export const OPERATORS = { 43 | '==': equal, 44 | '===': equal, 45 | '>': function (query, field, value) { 46 | return query.filter(field, '>', value); 47 | }, 48 | '>=': function (query, field, value) { 49 | return query.filter(field, '>=', value); 50 | }, 51 | '<': function (query, field, value) { 52 | return query.filter(field, '<', value); 53 | }, 54 | '<=': function (query, field, value) { 55 | return query.filter(field, '<=', value); 56 | } 57 | }; 58 | 59 | /** 60 | * CloudDatastoreAdapter class. 61 | * 62 | * @example 63 | * // Use Container instead of DataStore on the server 64 | * import {Container} from 'js-data' 65 | * import {CloudDatastoreAdapter} from 'js-data-cloud-datastore' 66 | * 67 | * // Create a store to hold your Mappers 68 | * const store = new Container() 69 | * 70 | * // Create an instance of CloudDatastoreAdapter with default settings 71 | * const adapter = new CloudDatastoreAdapter() 72 | * 73 | * // Mappers in "store" will use the CloudDatastore adapter by default 74 | * store.registerAdapter('datastore', adapter, { default: true }) 75 | * 76 | * // Create a Mapper that maps to a "user" table 77 | * store.defineMapper('user') 78 | * 79 | * @class CloudDatastoreAdapter 80 | * @extends Adapter 81 | * @param {object} [opts] Configuration options. 82 | * @param {boolean} [opts.debug=false] See {@link Adapter#debug}. 83 | * @param {function} [opts.datastore] See {@link CloudDatastoreAdapter#datastore}. 84 | * @param {object} [opts.datastoreOpts] See {@link CloudDatastoreAdapter#datastoreOpts}. 85 | * Ignored if you provide a pre-configured datastore instance. 86 | * @param {boolean} [opts.raw=false] See {@link Adapter#raw}. 87 | */ 88 | export function CloudDatastoreAdapter (opts) { 89 | utils.classCallCheck(this, CloudDatastoreAdapter); 90 | opts || (opts = {}); 91 | 92 | // Setup non-enumerable properties 93 | Object.defineProperties(this, { 94 | /** 95 | * Instance of Datastore used by this adapter. Use this directly when 96 | * you need to write custom queries. 97 | * 98 | * @name CloudDatastoreAdapter#datastore 99 | * @type {object} 100 | */ 101 | datastore: { 102 | writable: true, 103 | value: undefined 104 | } 105 | }); 106 | 107 | Adapter.call(this, opts); 108 | 109 | /** 110 | * Options to be passed to a new Datastore instance, if one wasn't provided. 111 | * 112 | * @name CloudDatastoreAdapter#datastoreOpts 113 | * @type {object} 114 | * @default {} 115 | * @property {string} projectId Google Cloud Platform project id. 116 | */ 117 | this.datastoreOpts || (this.datastoreOpts = {}); 118 | utils.fillIn(this.datastoreOpts, DATASTORE_DEFAULTS); 119 | 120 | /** 121 | * Override the default predicate functions for the specified operators. 122 | * 123 | * @name CloudDatastoreAdapter#operators 124 | * @type {object} 125 | * @default {} 126 | */ 127 | this.operators || (this.operators = {}); 128 | utils.fillIn(this.operators, OPERATORS); 129 | 130 | this.datastore || (this.datastore = Datastore(this.datastoreOpts)); 131 | } 132 | 133 | Adapter.extend({ 134 | constructor: CloudDatastoreAdapter, 135 | 136 | /** 137 | * Apply the specified selection query to the provided Datastore query. 138 | * 139 | * @method CloudDatastoreAdapter#filterQuery 140 | * @param {object} mapper The mapper. 141 | * @param {object} [query] Selection query. 142 | * @param {object} [query.where] Filtering criteria. 143 | * @param {string|Array} [query.orderBy] Sorting criteria. 144 | * @param {string|Array} [query.sort] Same as `query.sort`. 145 | * @param {number} [query.limit] Limit results. 146 | * @param {number} [query.skip] Offset results. 147 | * @param {number} [query.offset] Same as `query.skip`. 148 | * @param {object} [opts] Configuration options. 149 | * @param {object} [opts.operators] Override the default predicate functions 150 | * for specified operators. 151 | */ 152 | filterQuery (dsQuery, query, opts) { 153 | query = utils.plainCopy(query || {}); 154 | opts || (opts = {}); 155 | opts.operators || (opts.operators = {}); 156 | query.where || (query.where = {}); 157 | query.orderBy || (query.orderBy = query.sort); 158 | query.orderBy || (query.orderBy = []); 159 | query.skip || (query.skip = query.offset); 160 | 161 | // Transform non-keyword properties to "where" clause configuration 162 | utils.forOwn(query, (config, keyword) => { 163 | if (reserved.indexOf(keyword) === -1) { 164 | if (utils.isObject(config)) { 165 | query.where[keyword] = config; 166 | } else { 167 | query.where[keyword] = { 168 | '==': config 169 | }; 170 | } 171 | delete query[keyword]; 172 | } 173 | }); 174 | 175 | // Apply filter 176 | if (Object.keys(query.where).length !== 0) { 177 | utils.forOwn(query.where, (criteria, field) => { 178 | if (!utils.isObject(criteria)) { 179 | query.where[field] = { 180 | '==': criteria 181 | }; 182 | } 183 | 184 | utils.forOwn(criteria, (value, operator) => { 185 | let isOr = false; 186 | let _operator = operator; 187 | if (_operator && _operator[0] === '|') { 188 | _operator = _operator.substr(1); 189 | isOr = true; 190 | } 191 | const predicateFn = this.getOperator(_operator, opts); 192 | if (predicateFn) { 193 | if (isOr) { 194 | throw new Error(`Operator ${operator} not supported!`); 195 | } else { 196 | dsQuery = predicateFn(dsQuery, field, value); 197 | } 198 | } else { 199 | throw new Error(`Operator ${operator} not supported!`); 200 | } 201 | }); 202 | }); 203 | } 204 | 205 | // Apply sort 206 | if (query.orderBy) { 207 | if (utils.isString(query.orderBy)) { 208 | query.orderBy = [ 209 | [query.orderBy, 'asc'] 210 | ]; 211 | } 212 | query.orderBy.forEach((clause) => { 213 | if (utils.isString(clause)) { 214 | clause = [clause, 'asc']; 215 | } 216 | dsQuery = clause[1].toUpperCase() === 'DESC' ? dsQuery.order(clause[0], { descending: true }) : dsQuery.order(clause[0]); 217 | }); 218 | } 219 | 220 | // Apply skip/offset 221 | if (query.skip) { 222 | dsQuery = dsQuery.offset(+query.skip); 223 | } 224 | 225 | // Apply limit 226 | if (query.limit) { 227 | dsQuery = dsQuery.limit(+query.limit); 228 | } 229 | 230 | return dsQuery; 231 | }, 232 | 233 | _count (mapper, query, opts) { 234 | opts || (opts = {}); 235 | query || (query = {}); 236 | 237 | return new utils.Promise((resolve, reject) => { 238 | let dsQuery = this.datastore.createQuery(this.getKind(mapper, opts)); 239 | dsQuery = this.filterQuery(dsQuery, query, opts).select('__key__'); 240 | this.datastore.runQuery(dsQuery, (err, entities) => { 241 | if (err) { 242 | return reject(err); 243 | } 244 | return resolve([entities ? entities.length : 0, {}]); 245 | }); 246 | }); 247 | }, 248 | 249 | /** 250 | * Internal method used by CloudDatastoreAdapter#_create and 251 | * CloudDatastoreAdapter#_createMany. 252 | * 253 | * @method CloudDatastoreAdapter#_createHelper 254 | * @private 255 | * @param {object} mapper The mapper. 256 | * @param {(Object|Object[])} records The record or records to be created. 257 | * @return {Promise} 258 | */ 259 | _createHelper (mapper, records) { 260 | const singular = !utils.isArray(records); 261 | if (singular) { 262 | records = [records]; 263 | } 264 | records = utils.plainCopy(records); 265 | return new utils.Promise((resolve, reject) => { 266 | let apiResponse; 267 | const idAttribute = mapper.idAttribute; 268 | const incompleteKey = this.datastore.key([mapper.name]); 269 | 270 | const transaction = this.datastore.transaction(); 271 | transaction.run((err) => { 272 | if (err) { 273 | return reject(err); 274 | } 275 | // Allocate ids 276 | transaction.allocateIds(incompleteKey, records.length, (err, keys) => { 277 | if (err) { 278 | return reject(err); 279 | } 280 | const entities = records.map((_record, i) => { 281 | utils.set(_record, idAttribute, keys[i].path[1]); 282 | return { 283 | key: keys[i], 284 | data: _record 285 | }; 286 | }); 287 | // Save records 288 | transaction.save(entities); 289 | apiResponse = { 290 | created: singular ? 1 : entities.length 291 | }; 292 | transaction.commit((err) => { 293 | if (err) { 294 | return reject(err); 295 | } 296 | 297 | // The transaction completed successfully. 298 | return resolve([singular ? records[0] : records, apiResponse]); 299 | }); 300 | }); 301 | }); 302 | }); 303 | }, 304 | 305 | /** 306 | * Create a new record. Internal method used by Adapter#create. 307 | * 308 | * @method CloudDatastoreAdapter#_create 309 | * @private 310 | * @param {object} mapper The mapper. 311 | * @param {object} props The record to be created. 312 | * @param {object} [opts] Configuration options. 313 | * @return {Promise} 314 | */ 315 | _create (mapper, props, opts) { 316 | return this._createHelper(mapper, props, opts); 317 | }, 318 | 319 | /** 320 | * Create multiple records in a single batch. Internal method used by 321 | * Adapter#createMany. 322 | * 323 | * @method CloudDatastoreAdapter#_createMany 324 | * @private 325 | * @param {object} mapper The mapper. 326 | * @param {object} props The records to be created. 327 | * @param {object} [opts] Configuration options. 328 | * @return {Promise} 329 | */ 330 | _createMany (mapper, props, opts) { 331 | return this._createHelper(mapper, props, opts); 332 | }, 333 | 334 | /** 335 | * Destroy the record with the given primary key. Internal method used by 336 | * Adapter#destroy. 337 | * 338 | * @method CloudDatastoreAdapter#_destroy 339 | * @private 340 | * @param {object} mapper The mapper. 341 | * @param {(string|number)} id Primary key of the record to destroy. 342 | * response object. 343 | * @return {Promise} 344 | */ 345 | _destroy (mapper, id) { 346 | return new utils.Promise((resolve, reject) => { 347 | this.datastore.delete(this.datastore.key([mapper.name, id]), (err, apiResponse) => { 348 | return err ? reject(err) : resolve([undefined, apiResponse]); 349 | }); 350 | }); 351 | }, 352 | 353 | /** 354 | * Destroy the records that match the selection query. Internal method used by 355 | * Adapter#destroyAll. 356 | * 357 | * @method CloudDatastoreAdapter#_destroyAll 358 | * @private 359 | * @param {object} mapper the mapper. 360 | * @param {object} [query] Selection query. 361 | * @return {Promise} 362 | */ 363 | _destroyAll (mapper, query, opts) { 364 | return new utils.Promise((resolve, reject) => { 365 | let dsQuery = this.datastore.createQuery(this.getKind(mapper, opts)); 366 | dsQuery = this.filterQuery(dsQuery, query, opts); 367 | dsQuery = dsQuery.select('__key__'); 368 | this.datastore.runQuery(dsQuery, (err, entities) => { 369 | if (err) { 370 | return reject(err); 371 | } 372 | const keys = entities.map((entity) => entity.key); 373 | this.datastore.delete(keys, (err, apiResponse) => { 374 | if (err) { 375 | return reject(err); 376 | } 377 | resolve([undefined, apiResponse]); 378 | }); 379 | }); 380 | }); 381 | }, 382 | 383 | /** 384 | * Retrieve the record with the given primary key. Internal method used by 385 | * Adapter#find. 386 | * 387 | * @method CloudDatastoreAdapter#_find 388 | * @private 389 | * @param {object} mapper The mapper. 390 | * @param {(string|number)} id Primary key of the record to retrieve. 391 | * @param {object} [opts] Configuration options. 392 | * @return {Promise} 393 | */ 394 | _find (mapper, id, opts) { 395 | return new utils.Promise((resolve, reject) => { 396 | const key = this.datastore.key([this.getKind(mapper, opts), id]); 397 | this.datastore.get(key, (err, entity) => { 398 | return err ? reject(err) : resolve([entity ? entity.data : undefined, {}]); 399 | }); 400 | }); 401 | }, 402 | 403 | /** 404 | * Retrieve the records that match the selection query. Internal method used 405 | * by Adapter#findAll. 406 | * 407 | * @method CloudDatastoreAdapter#_findAll 408 | * @private 409 | * @param {object} mapper The mapper. 410 | * @param {object} [query] Selection query. 411 | * @param {object} [opts] Configuration options. 412 | * @return {Promise} 413 | */ 414 | _findAll (mapper, query, opts) { 415 | return new utils.Promise((resolve, reject) => { 416 | let dsQuery = this.datastore.createQuery(this.getKind(mapper, opts)); 417 | dsQuery = this.filterQuery(dsQuery, query, opts); 418 | this.datastore.runQuery(dsQuery, (err, entities) => { 419 | if (err) { 420 | return reject(err); 421 | } 422 | return resolve([entities ? entities.map((entity) => entity.data) : [], {}]); 423 | }); 424 | }); 425 | }, 426 | 427 | _sum (mapper, field, query, opts) { 428 | if (!utils.isString(field)) { 429 | throw new Error('field must be a string!'); 430 | } 431 | opts || (opts = {}); 432 | query || (query = {}); 433 | const canSelect = !Object.keys(query).length; 434 | 435 | return new utils.Promise((resolve, reject) => { 436 | let dsQuery = this.datastore.createQuery(this.getKind(mapper, opts)); 437 | dsQuery = this.filterQuery(dsQuery, query, opts); 438 | if (canSelect) { 439 | dsQuery = dsQuery.select(field); 440 | } 441 | this.datastore.runQuery(dsQuery, (err, entities) => { 442 | if (err) { 443 | return reject(err); 444 | } 445 | const sum = entities.reduce((sum, entity) => sum + (entity.data[field] || 0), 0); 446 | return resolve([sum, {}]); 447 | }); 448 | }); 449 | }, 450 | 451 | /** 452 | * Internal method used by CloudDatastoreAdapter#_update and 453 | * CloudDatastoreAdapter#_updateAll and CloudDatastoreAdapter#_updateMany. 454 | * 455 | * @method CloudDatastoreAdapter#_updateHelper 456 | * @private 457 | * @param {object} mapper The mapper. 458 | * @param {(Object|Object[])} records The record or records to be updated. 459 | * @param {(Object|Object[])} props The updates to apply to the record(s). 460 | * @param {object} [opts] Configuration options. 461 | * @return {Promise} 462 | */ 463 | _updateHelper (mapper, records, props, opts) { 464 | const singular = !utils.isArray(records); 465 | if (singular) { 466 | records = [records]; 467 | props = [props]; 468 | } 469 | return new utils.Promise((resolve, reject) => { 470 | if (!records.length) { 471 | return resolve([singular ? undefined : [], {}]); 472 | } 473 | const idAttribute = mapper.idAttribute; 474 | const entities = []; 475 | const _records = []; 476 | records.forEach((record, i) => { 477 | if (!record) { 478 | return; 479 | } 480 | const id = utils.get(record, idAttribute); 481 | if (!utils.isUndefined(id)) { 482 | utils.deepMixIn(record, props[i]); 483 | entities.push({ 484 | method: 'update', 485 | key: this.datastore.key([this.getKind(mapper, opts), id]), 486 | data: record 487 | }); 488 | _records.push(record); 489 | } 490 | }); 491 | if (!_records.length) { 492 | return resolve([singular ? undefined : [], {}]); 493 | } 494 | this.datastore.save(entities, (err, apiResponse) => { 495 | return err ? reject(err) : resolve([singular ? _records[0] : _records, apiResponse]); 496 | }); 497 | }); 498 | }, 499 | 500 | /** 501 | * Apply the given update to the record with the specified primary key. 502 | * Internal method used by Adapter#update. 503 | * 504 | * @method CloudDatastoreAdapter#_update 505 | * @private 506 | * @param {object} mapper The mapper. 507 | * @param {(string|number)} id The primary key of the record to be updated. 508 | * @param {object} props The update to apply to the record. 509 | * @param {object} [opts] Configuration options. 510 | * @return {Promise} 511 | */ 512 | _update (mapper, id, props, opts) { 513 | props || (props = {}); 514 | return this._find(mapper, id, opts).then((result) => { 515 | if (result[0]) { 516 | props = utils.plainCopy(props); 517 | return this._updateHelper(mapper, result[0], props, opts); 518 | } 519 | throw new Error('Not Found'); 520 | }); 521 | }, 522 | 523 | /** 524 | * Apply the given update to all records that match the selection query. 525 | * Internal method used by Adapter#updateAll. 526 | * 527 | * @method CloudDatastoreAdapter#_updateAll 528 | * @private 529 | * @param {object} mapper The mapper. 530 | * @param {object} props The update to apply to the selected records. 531 | * @param {object} [query] Selection query. 532 | * @param {object} [opts] Configuration options. 533 | * @return {Promise} 534 | */ 535 | _updateAll (mapper, props, query, opts) { 536 | props || (props = {}); 537 | return this._findAll(mapper, query, opts).then((result) => { 538 | let [records] = result; 539 | records = records.filter((record) => record); 540 | if (records.length) { 541 | props = utils.plainCopy(props); 542 | return this._updateHelper(mapper, records, records.map(() => props), opts); 543 | } 544 | return [[], {}]; 545 | }); 546 | }, 547 | 548 | /** 549 | * Update the given records in a single batch. Internal method used by 550 | * Adapter#updateMany. 551 | * 552 | * @method CloudDatastoreAdapter#_updateMany 553 | * @private 554 | * @param {object} mapper The mapper. 555 | * @param {Object[]} records The records to update. 556 | * @param {object} [opts] Configuration options. 557 | * @return {Promise} 558 | */ 559 | _updateMany (mapper, records, opts) { 560 | records || (records = []); 561 | const idAttribute = mapper.idAttribute; 562 | const tasks = records.map((record) => this._find(mapper, utils.get(record, idAttribute), opts)); 563 | return utils.Promise.all(tasks).then((results) => { 564 | let _records = results.map((result) => result[0]); 565 | _records.forEach((record, i) => { 566 | if (!record) { 567 | records[i] = undefined; 568 | } 569 | }); 570 | _records = _records.filter((record) => record); 571 | records = records.filter((record) => record); 572 | if (_records.length) { 573 | records = utils.plainCopy(records); 574 | return this._updateHelper(mapper, _records, records, opts); 575 | } 576 | return [[], {}]; 577 | }); 578 | }, 579 | 580 | loadBelongsTo (mapper, def, records, __opts) { 581 | if (utils.isObject(records) && !utils.isArray(records)) { 582 | return Adapter.prototype.loadBelongsTo.call(this, mapper, def, records, __opts); 583 | } 584 | throw new Error('findAll with belongsTo not supported!'); 585 | }, 586 | 587 | loadHasMany (mapper, def, records, __opts) { 588 | if (utils.isObject(records) && !utils.isArray(records)) { 589 | return Adapter.prototype.loadHasMany.call(this, mapper, def, records, __opts); 590 | } 591 | throw new Error('findAll with hasMany not supported!'); 592 | }, 593 | 594 | loadHasOne (mapper, def, records, __opts) { 595 | if (utils.isObject(records) && !utils.isArray(records)) { 596 | return Adapter.prototype.loadHasOne.call(this, mapper, def, records, __opts); 597 | } 598 | throw new Error('findAll with hasOne not supported!'); 599 | }, 600 | 601 | loadHasManyLocalKeys () { 602 | throw new Error('find/findAll with hasMany & localKeys not supported!'); 603 | }, 604 | 605 | loadHasManyForeignKeys () { 606 | throw new Error('find/findAll with hasMany & foreignKeys not supported!'); 607 | }, 608 | 609 | /** 610 | * Resolve the Cloud Datastore kind for the specified Mapper with the given 611 | * options. 612 | * 613 | * @method CloudDatastoreAdapter#getKind 614 | * @param {object} mapper The mapper. 615 | * @param {object} [opts] Configuration options. 616 | * @param {object} [opts.kind] Datastore kind. 617 | * @return {string} The kind. 618 | */ 619 | getKind (mapper, opts) { 620 | opts || (opts = {}); 621 | return utils.isUndefined(opts.kind) ? (utils.isUndefined(mapper.kind) ? mapper.name : mapper.kind) : opts.kind; 622 | }, 623 | 624 | /** 625 | * Resolve the predicate function for the specified operator based on the 626 | * given options and this adapter's settings. 627 | * 628 | * @method CloudDatastoreAdapter#getOperator 629 | * @param {string} operator The name of the operator. 630 | * @param {object} [opts] Configuration options. 631 | * @param {object} [opts.operators] Override the default predicate functions 632 | * for specified operators. 633 | * @return {*} The predicate function for the specified operator. 634 | */ 635 | getOperator (operator, opts) { 636 | opts || (opts = {}); 637 | opts.operators || (opts.operators = {}); 638 | let ownOps = this.operators || {}; 639 | return utils.isUndefined(opts.operators[operator]) ? ownOps[operator] || OPERATORS[operator] : opts.operators[operator]; 640 | } 641 | }); 642 | 643 | /** 644 | * Details of the current version of the `js-data-cloud-datastore` module. 645 | * 646 | * @example ES2015 modules import 647 | * import {version} from 'js-data-cloud-datastore' 648 | * console.log(version.full) 649 | * 650 | * @example CommonJS import 651 | * var version = require('js-data-cloud-datastore').version 652 | * console.log(version.full) 653 | * 654 | * @name module:js-data-cloud-datastore.version 655 | * @type {object} 656 | * @property {string} version.full The full semver value. 657 | * @property {number} version.major The major version number. 658 | * @property {number} version.minor The minor version number. 659 | * @property {number} version.patch The patch version number. 660 | * @property {(string|boolean)} version.alpha The alpha version value, 661 | * otherwise `false` if the current version is not alpha. 662 | * @property {(string|boolean)} version.beta The beta version value, 663 | * otherwise `false` if the current version is not beta. 664 | */ 665 | export const version = '<%= version %>'; 666 | 667 | /** 668 | * {@link CloudDatastoreAdapter} class. 669 | * 670 | * @example ES2015 modules import 671 | * import {CloudDatastoreAdapter} from 'js-data-cloud-datastore' 672 | * const adapter = new CloudDatastoreAdapter() 673 | * 674 | * @example CommonJS import 675 | * var CloudDatastoreAdapter = require('js-data-cloud-datastore').CloudDatastoreAdapter 676 | * var adapter = new CloudDatastoreAdapter() 677 | * 678 | * @name module:js-data-cloud-datastore.CloudDatastoreAdapter 679 | * @see CloudDatastoreAdapter 680 | * @type {Constructor} 681 | */ 682 | 683 | /** 684 | * Registered as `js-data-cloud-datastore` in NPM. 685 | * 686 | * @example Install from NPM 687 | * npm i --save js-data-cloud-datastore@rc js-data@rc @google-cloud/datastore 688 | * 689 | * @example ES2015 modules import 690 | * import {CloudDatastoreAdapter} from 'js-data-cloud-datastore' 691 | * const adapter = new CloudDatastoreAdapter() 692 | * 693 | * @example CommonJS import 694 | * var CloudDatastoreAdapter = require('js-data-cloud-datastore').CloudDatastoreAdapter 695 | * var adapter = new CloudDatastoreAdapter() 696 | * 697 | * @module js-data-cloud-datastore 698 | */ 699 | --------------------------------------------------------------------------------