├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.md ├── TODO.md ├── lerna.json ├── package.json ├── packages ├── apollo-client │ ├── .babelrc │ ├── CHANGELOG.md │ ├── README.md │ ├── lwc.config.json │ ├── package.json │ ├── src │ │ ├── client.ts │ │ ├── error-helpers.ts │ │ ├── index.ts │ │ ├── mutation.ts │ │ └── query.ts │ └── tsconfig.json └── sample-app │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ ├── jest.config.js │ ├── lwc.config.json │ ├── package.json │ ├── public │ ├── js │ │ └── .gitkeep │ └── template.html │ ├── scripts │ ├── rollup.apollo.config.js │ ├── rollup.config.js │ ├── synthetic-shadow.js │ └── wdio.conf.js │ ├── src-libs │ └── apollo-client.js │ ├── src-server │ ├── books.js │ ├── graphql.js │ └── server.js │ ├── src │ ├── app │ │ ├── about │ │ │ ├── about.html │ │ │ └── about.js │ │ ├── body │ │ │ ├── body.html │ │ │ └── body.js │ │ ├── footer │ │ │ ├── footer.html │ │ │ └── footer.js │ │ └── header │ │ │ ├── header.html │ │ │ └── header.js │ ├── form │ │ ├── input.html │ │ └── input.js │ ├── main.js │ ├── query │ │ ├── app │ │ │ ├── app.html │ │ │ └── app.js │ │ ├── bookdialog │ │ │ ├── bookdialog.html │ │ │ └── bookdialog.js │ │ └── booklist │ │ │ ├── booklist.html │ │ │ └── booklist.js │ └── rad │ │ ├── README.md │ │ ├── dialog │ │ └── dialog.js │ │ ├── form-field │ │ └── form-field.js │ │ └── input │ │ ├── input.html │ │ └── input.js │ └── test-integration │ └── specs │ └── greeting │ ├── Greeting.spec.js │ └── GreetingPage.js ├── scripts ├── license-header.js └── update-header.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/.DS_Store 4 | **/target 5 | lerna-debug.log 6 | .classpath 7 | .project 8 | .settings 9 | .sfdx 10 | coverage 11 | *.iml 12 | jest-report 13 | yarn-error.log 14 | .idea 15 | 16 | **/*.tgz 17 | 18 | 19 | **/package-lock.json 20 | package-losk.json:wq 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.1.1](https://github.com/LWC-Essentials/apollo-client/compare/v0.1.0...v0.1.1) (2020-04-24) 7 | 8 | **Note:** Version bump only for package @lwce/apollo 9 | 10 | 11 | 12 | 13 | 14 | # 0.1.0 (2020-04-13) 15 | 16 | **Note:** Version bump only for package @lwce/apollo 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | * Using welcoming and inclusive language 39 | * Being respectful of differing viewpoints and experiences 40 | * Gracefully accepting constructive criticism 41 | * Focusing on what is best for the community 42 | * Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | * The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | * Personal attacks, insulting/derogatory comments, or trolling 49 | * Public or private harassment 50 | * Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | * Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | * Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Salesforce.com, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @lwce/apollo-client 2 | 3 | [![npm version](https://img.shields.io/npm/v/@lwce/apollo-client?style=flat)](https://www.npmjs.com/package/@lwce/apollo-client) 4 | 5 | 6 | `@lwce/apollo-client` is an Apollo GraphQL client for Lightning Web Components 7 | 8 | **Note #1:** *Some of the components use the LWC wire adapter API, which is subject to change (see: https://github.com/salesforce/lwc-rfcs/blob/master/text/0103-wire-adapters.md). The library currently uses the LWC wire adapter API 1.1.x.* 9 | 10 | **Note #2:** *This library is under development and can change any time until it reaches version 1.0. Contributions as ideas or code are obviously welcome!* 11 | 12 | The project is a mono-repo where each feature is defined in its own package. As of today the available packages are the apollo-client library and the sample application. 13 | 14 | 15 | ## Running the sample Application 16 | 17 | The project contains a sample application that shows how to use the library. It has both a server side part, running NodeJS/express and the Apollo Server, as well as a client side part made with LWC and the Apollo client. 18 | 19 | To run the application: 20 | 21 | ``` 22 | yarn build 23 | yarn serve 24 | ``` 25 | 26 | Here is a video of the demo application: 27 | 28 | [![](http://img.youtube.com/vi/1TQdtL0cdwE/0.jpg)](http://www.youtube.com/watch?v=1TQdtL0cdwE "LWC Apollo Client") 29 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # apollo-client TODOs 2 | 3 | ## Add unit tests 4 | 5 | Yeah, lots to be done... 6 | 7 | 8 | ## Add better support for Typescript 9 | 10 | Make sure that the right types are used in the library. 11 | 12 | Use ApolloClient generator to generate the query types. 13 | 14 | 15 | ## useQuery 16 | 17 | Add support for fetchMore. 18 | 19 | Add an option for nor using observable but direct queries, that are not monitored. 20 | 21 | 22 | ## useMutation 23 | 24 | Helpers for dealing with the cache after a mutation. 25 | 26 | 27 | ## useSubscription 28 | 29 | Create useSubscription. 30 | 31 | Demo for subscriptions. 32 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.11.0", 3 | "exact": true, 4 | "version": "0.1.1", 5 | "npmClient": "yarn", 6 | "command": { 7 | "init": { 8 | "exact": true 9 | }, 10 | "version": { 11 | "conventionalCommits": true, 12 | "message": "chore(release): publish %s @skip-ci@" 13 | } 14 | }, 15 | "useWorkspaces": true 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lwce/apollo", 3 | "version": "0.2.0", 4 | "description": "Apollo client integration for Lightning Web Components", 5 | "private": true, 6 | "license": "BSD-3-Clause", 7 | "config": { 8 | "lwcVersion": "1.6.0" 9 | }, 10 | "scripts": { 11 | "build": "lerna run build --stream", 12 | "build:app": "lerna run build --scope sample-app --stream", 13 | "build-publish": "lerna run build-publish --stream", 14 | "clean": "lerna run clean --stream", 15 | "start": "yarn start:lwc", 16 | "start:watch": "yarn start:lwc:watch", 17 | "stop": "yarn stop:lwc", 18 | "start:lwc": "lerna run start --scope sample-app --stream", 19 | "start:lwc:watch": "lerna run start:watch --scope sample-app --stream", 20 | "stop:lwc": "lerna run stop --scope sample-app --stream", 21 | "update-license-headers": "node ./scripts/license-header.js" 22 | }, 23 | "workspaces": { 24 | "packages": [ 25 | "packages/apollo-client", 26 | "packages/sample-app" 27 | ] 28 | }, 29 | "engines": { 30 | "yarn": ">=1.10.1", 31 | "node": ">=10.6.0" 32 | }, 33 | "devDependencies": { 34 | "rollup": "^1.7.4", 35 | "rollup-plugin-cleanup": "^3.1.1", 36 | "rollup-plugin-compat": "^0.22.0", 37 | "rollup-plugin-node-resolve": "^4.2.4", 38 | "rollup-plugin-replace": "^2.1.0", 39 | "rollup-plugin-terser": "^4.0.4", 40 | "rollup-plugin-typescript": "^1.0.0", 41 | "tslib": "^1.10.0", 42 | "typescript": "~3.6.2", 43 | "lerna": "^3.17.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/apollo-client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /packages/apollo-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.1.1](https://github.com/LWC-Essentials/apollo-client/compare/v0.1.0...v0.1.1) (2020-04-24) 7 | 8 | **Note:** Version bump only for package @lwce/apollo-client 9 | 10 | 11 | 12 | 13 | 14 | # 0.1.0 (2020-04-13) 15 | 16 | **Note:** Version bump only for package @lwce/apollo-client 17 | -------------------------------------------------------------------------------- /packages/apollo-client/README.md: -------------------------------------------------------------------------------- 1 | # @lwce/apollo-client 2 | 3 | Apollo GraphQL client for Lightning Web Components 4 | 5 | This library is largely inspired from the Apollo React hooks, although because it uses wire-adapters, there are some differences. 6 | 7 | ***This library is currently in an experimental form and may change at any time*** 8 | 9 | 10 | ## Use of wire adapters - Warning 11 | 12 | Wire adapters are not yet fully documented in the open source version of LWC because their implementation might change over time. If it feels safe to use wire adapters from a consumer standpoint, the implementors might have to adapt their code when these changes will happen. 13 | 14 | 15 | ## Establishing the connection to the GraphQL server 16 | 17 | The connection is established through the use of an Apollo client instance. Typically, as GraphQL aims to provide a single endpoint, an application should have one Apollo client created. It is important as such a client is also used to maintain a cache for the data. 18 | 19 | If the Apollo client can be passed with each wire adapter through the `client` property, the `@lwce/apollo-client` library also maintains a global Apollo client instance as a convenience. It can be accessed through its `setClient/getClient` functions: 20 | 21 | ```javascript 22 | // Register the global Apollo client when the application starts 23 | setClient(new ApolloClient({ 24 | uri: 'http://mygraphqlserver.com/graphql' 25 | })); 26 | ``` 27 | 28 | 29 | ## Running a query 30 | 31 | A query can be executed using the `useQuery` wire adapter like bellow: 32 | 33 | ```javascript 34 | const MYQUERY = gql` 35 | { 36 | books(limit: 5) { 37 | id 38 | title 39 | } 40 | } 41 | ` 42 | ... 43 | @wire(useQuery, { 44 | query: MYQUERY 45 | }) books; 46 | ``` 47 | 48 | Because of the current LWC compiler behavior, it is advised to isolate the GraphQL in a constant and then reference this constant. This generates more optimized code, as the LWC will repeat the query several times if not in a constant. 49 | 50 | Note: to make the wire adapter react on a variable value change, the whole `variables` object has to be replaced, as a change in a property is not detected (even when `@track`). For example, changing the offset should be done with code like: 51 | 52 | ```javascript 53 | variables = { 54 | offset: 0, 55 | limit: 10 56 | } 57 | 58 | @wire(useQuery, { 59 | query: MYQUERY, 60 | variables: '$variables' 61 | }) books; 62 | 63 | // Right way to update an object 64 | handleFirst() { 65 | this.variables = { 66 | ...this.variables, 67 | offset: 0 68 | } 69 | } 70 | 71 | // // This does not trigger a wire adapter change! 72 | // handleFirst() { 73 | // this.variables.offset = 0; 74 | // } 75 | ``` 76 | 77 | 78 | 79 | ### Query Results 80 | 81 | The resulting data has the following members: 82 | 83 | ```javascript 84 | { 85 | loading, // A boolean that indicates if the query is being loaded 86 | data, // The data returned, can be null or undefined 87 | error, // The data if any, can be null or undefined 88 | initialized, // A boolean that indicates if the query has been executed at least once 89 | 90 | client, // The apollo client being used 91 | fetch, // Execute the query with optional variables 92 | } 93 | ``` 94 | 95 | Note that the wire adapter keeps watching for data changes using the Apollo `watchQuery` observable. If it detects changes, then it will update the component appropriately. 96 | 97 | 98 | ### Query Options 99 | 100 | The options are the one being used by the Apollo client [watchQuery()](https://www.apollographql.com/docs/react/api/apollo-client/#ApolloClient.watchQuery), with a new option: 101 | 102 | ```javascript 103 | { 104 | client, // The apollo client instance. If empty, it uses the globally registered one. 105 | lazy, // Indicates that the query should not be executed right away, but later calling fetch() 106 | } 107 | ``` 108 | 109 | The most notable existing options for the client are: 110 | 111 | - `query`: the GraphQL query to emit 112 | - `variables`: the list of variables for the query 113 | 114 | 115 | ### Refetching the Query 116 | 117 | Once initialized, the result provides a `fetch(variables)` function that can be used to execute the query again. In case of a `lazy` query, it must be called to get the data (the `initialized` can be checked to see if it already happened). 118 | 119 | Note that the React Hooks library named this function `refetch`, but it is named `fetch` here for consistency with other LWC projects. 120 | 121 | 122 | ### Lazy mode 123 | 124 | The GraphQL request is automatically emitted when the wire adapter configuration is available, which means when the component is connected to the DOM. This works well for data needed by the component to display right away, typically a query. But, sometimes, the request should be executed manually. It applies to data requested on demand (list for a pop-up, ...). The `fetch` method returns a `Promise` that will be resolved when the request is completed. At that time, the `@wire` member is updated with the latest data. Note that the Promise is also resolved when the update failed: just access the error member of the `@wire` member to check the status. 125 | 126 | Here an example of a request executed on demand: 127 | ```javascript 128 | @wire(useQuery, { 129 | query: USERS, 130 | lazy: true 131 | }) users; 132 | 133 | readUsers() { 134 | this.users.fetch().then( () => { 135 | // Notification that the data has been updated 136 | // Do something with the @wire data member... 137 | }) 138 | } 139 | ``` 140 | 141 | To support that, the wire adapter offers a `lazy` mode. When set to `true`, the request is only executed with an explicit call to a `fetch()` method, provided as part of the @wire variable. 142 | 143 | 144 | ## Executing a mutation 145 | 146 | Mutations use the `useMutation` wire adapter: 147 | 148 | ```javascript 149 | const MY_MUTATION = gql` 150 | mutation updateBook($id: String!, $book: BookInput!) { 151 | updateBook(id: $id, book: $book) { 152 | id 153 | title 154 | } 155 | } 156 | ` 157 | ... 158 | @wire(useMutation, { 159 | query: MY_MUTATION 160 | }) updateBook; 161 | ``` 162 | 163 | Similarly to lazy queries, the requests is not executed when the component is initialized but must be executed explicitly. For this, the wire adapter will initialize the result and make available a `mutate` method to call: 164 | 165 | ```javascript 166 | const variables = { 167 | id, 168 | book: { 169 | title: "new title", 170 | author: "myself, 171 | ... 172 | } 173 | }; 174 | this.bookCreate.mutate({ 175 | variables 176 | }); 177 | ``` 178 | 179 | the `mutate` method also returns a promise to enable notification once a mutation has succeeded: 180 | ```javascript 181 | const variables = { 182 | id, 183 | book: { 184 | title: "new title", 185 | author: "myself, 186 | ... 187 | } 188 | }; 189 | this.bookDelete.mutate({ 190 | variables 191 | }).then(() => { 192 | //some notification or alert or variable change 193 | this.showDeleteSuccessAlert = true; 194 | }); 195 | ``` 196 | 197 | 198 | 199 | ### Mutation Results 200 | 201 | The resulting data has the following members: 202 | 203 | ```javascript 204 | { 205 | loading, // A boolean that indicates if the mutation is being executed 206 | data, // The data returned, can be null or undefined 207 | error, // The data if any, can be null or undefined 208 | initialized, // A boolean that indicates if the query has been called once 209 | 210 | client, // The apollo client being used 211 | mutate, // The function to call for mutating the data 212 | } 213 | ``` 214 | 215 | 216 | ### Mutation Options 217 | 218 | The options are the one being used by the Apollo client [mutate()](https://www.apollographql.com/docs/react/api/apollo-client/#ApolloClient.mutate), with the following options added: 219 | 220 | ```javascript 221 | { 222 | client, // The apollo client instance. If empty, it uses the globally registered one. 223 | } 224 | ``` 225 | 226 | The options are a merge between the global options defined at the `@wire` level, and the ones passed to the `mutate` method (the later overrides the former). 227 | 228 | 229 | ### Error Helpers 230 | 231 | Easily convert GraphQL error responses into a human readable string: 232 | 233 | ```js 234 | import { getErrorString } from '@lwce/apollo-client'; 235 | 236 | @wire(useQuery, { query: QUERY, lazy: false, variables: '$variables'}) 237 | update(response) { 238 | if (response.initialized) { 239 | if (response.error) { 240 | console.error(getErrorString(response.error)); 241 | } else { 242 | this.data = response.data; 243 | } 244 | } 245 | } 246 | ``` -------------------------------------------------------------------------------- /packages/apollo-client/lwc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "expose": [] 3 | } 4 | -------------------------------------------------------------------------------- /packages/apollo-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lwce/apollo-client", 3 | "version": "0.2.0", 4 | "description": "Apollo client integration for Lightning Web Components", 5 | "license": "BSD-3-Clause", 6 | "author": "Philippe Riand", 7 | "bugs": "https://github.com/LWC-Essentials/apollo-client/issues", 8 | "main": "dist/es6/index.js", 9 | "typings": "dist/types/index.d.ts", 10 | "scripts": { 11 | "build": "tsc", 12 | "pack": "yarn build && yarn pack", 13 | "build-publish": "yarn build && yarn pack && yarn publish --access public", 14 | "clean": "rm ./*.tgz" 15 | }, 16 | "files": [ 17 | "dist/", 18 | "types/" 19 | ], 20 | "devDependencies": { 21 | "@lwc/engine": "^1.6.0", 22 | "@lwc/wire-service": "^1.6.0" 23 | }, 24 | "dependencies": { 25 | "apollo-client": "^2.6.4" 26 | }, 27 | "homepage": "https://github.com/LWC-Essentials/apollo-client", 28 | "keywords": [ 29 | "lwc", 30 | "wire adapter", 31 | "graphql", 32 | "apollo" 33 | ], 34 | "engines": { 35 | "yarn": ">=1.10.1", 36 | "node": ">=10.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/apollo-client/src/client.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | // 8 | // Reference the Apollo client to use 9 | // 10 | import { ApolloClient } from "apollo-client"; 11 | 12 | const APOLLO_KEY = "__lwcapolloc_client__"; 13 | 14 | export function getClient() { 15 | return (window as any)[APOLLO_KEY]; 16 | } 17 | 18 | export function setClient(client: ApolloClient) { 19 | return (window as any)[APOLLO_KEY] = client; 20 | } 21 | -------------------------------------------------------------------------------- /packages/apollo-client/src/error-helpers.ts: -------------------------------------------------------------------------------- 1 | export function getErrorString(error: any): string | undefined { 2 | if (error) { 3 | if (Array.isArray(error)) { 4 | let s = ""; 5 | for (let i of (error as [])) { 6 | if (s) s += "\n"; 7 | s += getErrorString(i); 8 | } 9 | return s; 10 | } 11 | 12 | if (typeof error !== 'string') { 13 | error = error.message; 14 | } 15 | 16 | return error; 17 | } 18 | return undefined; 19 | } -------------------------------------------------------------------------------- /packages/apollo-client/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | export { getClient, setClient } from './client'; 8 | export { useQuery } from './query'; 9 | export { useMutation } from './mutation'; 10 | export { getErrorString } from './error-helpers'; -------------------------------------------------------------------------------- /packages/apollo-client/src/mutation.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { getClient } from './client'; 8 | import ApolloClient, { MutationOptions, OperationVariables } from 'apollo-client'; 9 | 10 | // 11 | // useMutation 12 | // 13 | // React useMutation reference: 14 | // https://www.apollographql.com/docs/react/api/react-hooks/#usemutation 15 | // 16 | // 17 | interface DataCallback { 18 | (value: any): void; 19 | } 20 | 21 | export interface Options extends MutationOptions { 22 | client: ApolloClient; 23 | } 24 | 25 | interface Result { 26 | loading: boolean; 27 | data?: any; 28 | error?: string; 29 | initialized: boolean; 30 | 31 | client: ApolloClient, 32 | mutate?: (v:any) => void; 33 | } 34 | 35 | export function errorString(error?: any): string|undefined { 36 | if(error) { 37 | if(Array.isArray(error)) { 38 | let s = ""; 39 | for( let i of (error as []) ) { 40 | if(s) s+= "\n"; 41 | s+=errorString(i); 42 | } 43 | return s; 44 | } 45 | if(typeof error!=='string') { 46 | error = error.toString(); 47 | } 48 | return error; 49 | } 50 | return undefined; 51 | } 52 | 53 | export class useMutation { 54 | dataCallback: DataCallback; 55 | 56 | apolloOptions: MutationOptions|undefined 57 | connected:boolean = true 58 | pendingResult: Result 59 | 60 | constructor(dataCallback: DataCallback) { 61 | this.dataCallback = dataCallback; 62 | this.pendingResult = { 63 | client: undefined as unknown as ApolloClient, // Trick - when sent to the component, it will be defined 64 | loading:false, 65 | data: undefined, 66 | error: undefined, 67 | initialized: false, 68 | mutate: this.mutate.bind(this) 69 | }; 70 | } 71 | 72 | update(config: Record) { 73 | const {client,...props} = config; 74 | this.apolloOptions = >{...props}; 75 | this.pendingResult.client = client||getClient(); 76 | this.sendUpdate(); 77 | } 78 | 79 | connect() { 80 | this.connected = true; 81 | this.sendUpdate(); 82 | } 83 | 84 | disconnect() { 85 | this.connected = false; 86 | } 87 | 88 | 89 | sendUpdate() { 90 | if(this.connected) { 91 | // Make a copy to make the notification effective and prevent changes (ex: client instance) 92 | const o = Object.assign({},this.pendingResult); 93 | this.dataCallback(o); 94 | } 95 | } 96 | 97 | mutate(options: MutationOptions) { 98 | const mergedOptions = {...this.apolloOptions, ...options}; 99 | this.pendingResult.loading = true; 100 | this.sendUpdate(); 101 | // It is not necessary to return the Promise here, as the caller can use th on...() events 102 | return this.pendingResult.client.mutate(mergedOptions).then( ({ data, errors }) => { 103 | Object.assign( this.pendingResult, { 104 | loading: false, 105 | data, 106 | error:errors, 107 | initialized: true, 108 | }); 109 | this.sendUpdate(); 110 | }).catch( error => { 111 | Object.assign( this.pendingResult, { 112 | loading: false, 113 | data: undefined, 114 | error:error, 115 | initialized: true, 116 | }); 117 | this.sendUpdate(); 118 | }); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /packages/apollo-client/src/query.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { getClient } from './client'; 8 | import { ApolloClient, WatchQueryOptions, ObservableQuery, OperationVariables, ApolloQueryResult } from "apollo-client"; 9 | 10 | // 11 | // useQuery/useLazyQuery 12 | // 13 | // React useQuery reference: 14 | // https://www.apollographql.com/docs/react/api/react-hooks/#usequery 15 | // React useLazyQuery reference: 16 | // https://www.apollographql.com/docs/react/api/react-hooks/#uselazyquery 17 | // 18 | 19 | interface DataCallback { 20 | (value: any): void; 21 | } 22 | 23 | export interface Options extends WatchQueryOptions { 24 | client: ApolloClient; 25 | lazy: boolean; 26 | } 27 | 28 | interface Result { 29 | loading: boolean; 30 | data?: any; 31 | error?: string; 32 | initialized: boolean; 33 | 34 | client: ApolloClient; 35 | fetch?: (v:any) => void; 36 | } 37 | 38 | export class useQuery { 39 | dataCallback: DataCallback; 40 | 41 | connected: boolean = false; 42 | apolloOptions: WatchQueryOptions|undefined; 43 | observableQuery: ObservableQuery,OperationVariables>|undefined; 44 | subscription: ZenObservable.Subscription|undefined; 45 | pendingResult: Result; 46 | 47 | constructor(dataCallback: DataCallback) { 48 | this.dataCallback = dataCallback; 49 | this.pendingResult = { 50 | client: undefined as unknown as ApolloClient, // Trick - when sent to the component, it will be defined 51 | loading:false, 52 | data: undefined, 53 | error: undefined, 54 | initialized: false, 55 | fetch: this.fetch.bind(this) 56 | } 57 | } 58 | 59 | update(config: Record) { 60 | const {client,lazy, ...props} = config; 61 | this.apolloOptions = props; 62 | this.pendingResult.client = client||getClient(); 63 | if(!lazy) { 64 | this.fetch(); 65 | } else { 66 | this.sendUpdate(); 67 | } 68 | } 69 | 70 | connect() { 71 | this.connected = true; 72 | this.sendUpdate(); 73 | } 74 | 75 | disconnect() { 76 | this.connected = false; 77 | if(this.subscription) { 78 | this.subscription.unsubscribe(); 79 | } 80 | } 81 | 82 | 83 | sendUpdate() { 84 | if(this.connected) { 85 | // Make a copy to make the notification effective and prevent changes (ex: client instance) 86 | const o = Object.assign({},this.pendingResult); 87 | this.dataCallback(o); 88 | } 89 | } 90 | 91 | fetch(options?: Options) { 92 | const mergedOptions = {...this.apolloOptions, ...options}; 93 | this.pendingResult.loading = true; 94 | this.sendUpdate(); 95 | try { 96 | if(this.subscription) { 97 | this.subscription.unsubscribe(); 98 | } 99 | this.observableQuery = this.pendingResult.client.watchQuery(mergedOptions); 100 | this.subscription = this.observableQuery.subscribe(({ data, loading, errors }) => { 101 | Object.assign(this.pendingResult, { 102 | loading, 103 | data, 104 | error: errors, 105 | initialized: true 106 | }) 107 | this.sendUpdate(); 108 | }); 109 | } catch (error) { 110 | Object.assign(this.pendingResult, { 111 | loading: false, 112 | data: undefined, 113 | error, 114 | initialized: true 115 | }) 116 | this.sendUpdate(); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/apollo-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | 4 | "compilerOptions": { 5 | "module": "es6", 6 | "outDir": "dist/es6", 7 | "declarationDir": "dist/types", 8 | 9 | "lib": ["dom", "es2015", "es2016", "es2017", "es2018"] 10 | }, 11 | 12 | "include": ["src/"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/sample-app/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [{*.json,.*.yml}] 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /packages/sample-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "@salesforce/eslint-config-lwc/recommended" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sample-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/screenshots/ 3 | errorShots/ 4 | 5 | *.log 6 | .DS_Store 7 | .vscode 8 | public/js/*.js 9 | -------------------------------------------------------------------------------- /packages/sample-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # Jest coverage 2 | coverage/ 3 | 4 | # Default lib folder 5 | lib/ 6 | 7 | # Default dist folder 8 | dist/ 9 | 10 | # Default resources folder 11 | src/resources/ 12 | -------------------------------------------------------------------------------- /packages/sample-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "overrides": [ 6 | { 7 | "files": "**/*.html", 8 | "options": { "parser": "lwc" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/sample-app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0 (2020-04-13) 7 | 8 | **Note:** Version bump only for package sample-app 9 | -------------------------------------------------------------------------------- /packages/sample-app/README.md: -------------------------------------------------------------------------------- 1 | # Sample Application 2 | 3 | The sample application shows how to use the @lwce/apollo-client library to connect to a GraphQL server and execute CRUD operations. To run the sample application, execute the following commands from the root project: 4 | 5 | ``` 6 | yarn build 7 | yarn start 8 | ``` 9 | 10 | Or to run in watch mode (development mode): 11 | 12 | ``` 13 | yarn start 14 | ``` 15 | 16 | Then launch your browser with the following URL: 17 | [http://localhost:3001/](http://localhost:3001/) 18 | 19 | [![](http://img.youtube.com/vi/1TQdtL0cdwE/0.jpg)](http://www.youtube.com/watch?v=1TQdtL0cdwE "LWC Apollo Client") 20 | 21 | 22 | ## Main application 23 | 24 | The application shows CRUD operations on a virtual book database. The database is maintained in memory by the server, and exposed through an Apollo server instance. The GraphQL schema exposes queries and mutations to read, update, create and delete books. 25 | 26 | 27 | ### Apollo Client Cache 28 | 29 | The application uses the default Apollo client `InMemoryCache`. As such, and because the update mutation returns the book id and all the changed fields, the cache is automatically updated. Note that there is still a potential issue, as the books are sorted by title on the server and changing the title locally may lead to a bad ordering in the client. 30 | 31 | For both create and delete mutations, the client cache is fully discarded to display fresh data. Although this is accurate from a data management standpoint, it can probably be optimized - any idea is welcome! 32 | 33 | 34 | ### RAD Library 35 | 36 | The sample application contains some widgets that help building a UI using LWC and Bootstrap. This is experimental and will drastically change over time, so avoid relying on it! 37 | -------------------------------------------------------------------------------- /packages/sample-app/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | require('./src-server/server').start(); 8 | -------------------------------------------------------------------------------- /packages/sample-app/jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | module.exports = { 8 | preset: '@lwc/jest-preset', 9 | moduleNameMapper: { 10 | '^(todo)/(.+)$': '/src/$1/$2/$2' 11 | }, 12 | testPathIgnorePatterns: [ 13 | '/node_modules/', 14 | '/test/specs/' 15 | ] 16 | }; 17 | -------------------------------------------------------------------------------- /packages/sample-app/lwc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": [ 3 | { 4 | "npm": "@lwce/router" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/sample-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "description": "Sample LWC Apollo Client application", 4 | "version": "0.2.0", 5 | "private": true, 6 | "author": "Philippe Riand", 7 | "bugs": "https://github.com/LWC-Essentials/apollo-client/issues", 8 | "dependencies": { 9 | "apollo-boost": "^0.4.4", 10 | "apollo-client": "^2.6.4", 11 | "apollo-server": "^2.9.7", 12 | "apollo-server-express": "^2.9.7", 13 | "compression": "^1.7.4", 14 | "express": "~4.16.3", 15 | "graphql": "^14.5.8", 16 | "rollup-plugin-commonjs": "^10.1.0", 17 | "rollup-plugin-node-builtins": "^2.1.2", 18 | "rollup-plugin-node-globals": "^1.4.0", 19 | "useragent": "~2.3.0" 20 | }, 21 | "devDependencies": { 22 | "@lwc/compiler": "^1.6.0", 23 | "@lwc/engine": "^1.6.0", 24 | "@lwc/rollup-plugin": "^1.6.0", 25 | "@lwc/synthetic-shadow": "^1.6.0", 26 | "@lwc/wire-service": "^1.6.0", 27 | "@lwce/apollo-client" : "^0.2.0", 28 | "@lwce/router" : "^0.2.5", 29 | "@lwc/jest-preset": "2.2.0", 30 | 31 | "@salesforce/eslint-config-lwc": "~0.3.0", 32 | "@wdio/cli": "~5.9.3", 33 | "@wdio/local-runner": "~5.9.3", 34 | "@wdio/mocha-framework": "~5.9.3", 35 | "@wdio/selenium-standalone-service": "~5.9.3", 36 | "@wdio/spec-reporter": "~5.9.3", 37 | "@wdio/sync": "~5.9.3", 38 | "babel-eslint": "^10.0.1", 39 | "concurrently": "~4.0.1", 40 | "cross-env": "^6.0.3", 41 | "eslint": "^5.10.0", 42 | "jest": "~24.8.0", 43 | "lint-staged": "^9.4", 44 | "npm-run-all": "^4.1.5", 45 | "rollup": "^1.26.0", 46 | "rollup-plugin-compat": "0.21.5", 47 | "rollup-plugin-copy": "^3.1.0", 48 | "rollup-plugin-node-resolve": "^5.2.0", 49 | "rollup-plugin-replace": "~2.1.0", 50 | "rollup-plugin-terser": "^5.1.2" 51 | }, 52 | "engines": { 53 | "node": ">=10.13.0", 54 | "npm": ">=6.4.1", 55 | "yarn": ">=1.9.4" 56 | }, 57 | "homepage": "https://github.com/LWC-Essentials/apollo-client", 58 | "keywords": [ 59 | "lwc" 60 | ], 61 | "license": "BSD 3-Clause", 62 | "lint-staged": { 63 | "**/*.{css,html,js,json,md,ts,yaml,yml}": [ 64 | "prettier --write" 65 | ], 66 | "./src/**/*.js": [ 67 | "eslint" 68 | ], 69 | "*": [ 70 | "git add" 71 | ] 72 | }, 73 | "scripts": { 74 | "build": "yarn build:apollo && cross-env rollup -c ./scripts/rollup.config.js", 75 | "build:production": "cross-env NODE_ENV=production rollup -c ./scripts/rollup.config.js", 76 | "build:apollo": "rollup -c ./scripts/rollup.apollo.config.js && cross-env NODE_ENV=production rollup -c ./scripts/rollup.apollo.config.js", 77 | "lint": "eslint ./src/**/*.js", 78 | "prettier": "prettier --write '**/*.{css,html,js,json,md,ts,yaml,yml}'", 79 | "prettier:verify": "prettier --list-different '**/*.{css,html,js,json,md,ts,yaml,yml}'", 80 | "serve": "node index.js", 81 | "start": "concurrently --kill-others \"yarn build --watch\" \"yarn serve\"", 82 | "test": "yarn test:unit && yarn test:integration", 83 | "test:integration": "wdio ./scripts/wdio.conf.js", 84 | "test:unit": "jest", 85 | "update-license-headers": "node ./scripts/license-header.js" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/sample-app/public/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LWC-Essentials/apollo-client/7ef10e0a23519d3fda7fd6f048485a058a754fab/packages/sample-app/public/js/.gitkeep -------------------------------------------------------------------------------- /packages/sample-app/public/template.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | LWC GraphQL Examples 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/sample-app/scripts/rollup.apollo.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | const path = require( 'path' ); 8 | const resolve = require( 'rollup-plugin-node-resolve' ); 9 | const builtins = require( 'rollup-plugin-node-builtins' ); 10 | const globals = require( 'rollup-plugin-node-globals' ); 11 | const commonjs = require( 'rollup-plugin-commonjs' ); 12 | 13 | const input = path.resolve( __dirname, '../src-libs/apollo-client.js' ); 14 | const outputDir = path.resolve(__dirname, '../public/js'); 15 | 16 | const { terser } = require('rollup-plugin-terser'); 17 | 18 | const env = process.env.NODE_ENV || 'development'; 19 | const isProduction = env === 'production'; 20 | 21 | const plugins = [ 22 | builtins(), 23 | globals(), 24 | resolve( { 25 | customResolveOptions: { 26 | moduleDirectory: 'node_modules' 27 | } 28 | } ), 29 | commonjs(), 30 | isProduction && terser() 31 | ]; 32 | 33 | module.exports = [ { 34 | input, 35 | output: { 36 | file: path.join(outputDir, 'apollo-client') + (isProduction ? ".min.js" : ".js"), 37 | format: 'iife', 38 | }, 39 | plugins 40 | } ]; 41 | -------------------------------------------------------------------------------- /packages/sample-app/scripts/rollup.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | 11 | // Rollup with external depenceies, like apollo-boost 12 | // https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency 13 | 14 | const replace = require('rollup-plugin-replace'); 15 | const lwcCompiler = require('@lwc/rollup-plugin'); 16 | const compat = require('rollup-plugin-compat'); 17 | const { terser } = require('rollup-plugin-terser'); 18 | const resolve = require('rollup-plugin-node-resolve'); 19 | //const commonjs = require('rollup-plugin-commonjs'); 20 | 21 | const syntheticShadow = require('./synthetic-shadow'); 22 | 23 | const env = process.env.NODE_ENV || 'development'; 24 | 25 | const input = path.resolve(__dirname, '../src/main.js'); 26 | const outputDir = path.resolve(__dirname, '../public/js'); 27 | 28 | function rollupConfig({ target }) { 29 | const isCompat = target === 'es5'; 30 | const isProduction = env === 'production'; 31 | 32 | return { 33 | input, 34 | output: { 35 | file: path.join(outputDir, (isCompat ? 'lwc-components-compat' : 'lwc-components') + (isProduction ? ".min.js" : ".js")), 36 | format: 'iife', 37 | }, 38 | plugins: [ 39 | resolve({ 40 | mainFields: ['module', 'main'], 41 | browser: true, 42 | }), 43 | isCompat && syntheticShadow(), 44 | lwcCompiler(), 45 | replace({ 'process.env.NODE_ENV': JSON.stringify(env) }), 46 | isCompat && compat(), 47 | isProduction && terser() 48 | ].filter(Boolean) 49 | } 50 | } 51 | 52 | module.exports = [ 53 | //rollupConfig({ target: 'es5' }), 54 | rollupConfig({ target: 'es2017' }) 55 | ]; 56 | -------------------------------------------------------------------------------- /packages/sample-app/scripts/synthetic-shadow.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | const SYNTHETIC_IMPORT = 'import "@lwc/synthetic-shadow";'; 8 | 9 | module.exports = function() { 10 | let input; 11 | return { 12 | name: 'synthetic-shadow', 13 | options(rollupOpts) { 14 | input = rollupOpts.input; 15 | }, 16 | transform(src, id) { 17 | if (id === input) { 18 | src = SYNTHETIC_IMPORT + src; 19 | } 20 | 21 | return { code: src, map: null }; 22 | }, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/sample-app/scripts/wdio.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | const Server = require('../server'); 8 | const isCI = !!process.env.ENV_CI; 9 | let HTTPServer; 10 | 11 | exports.config = { 12 | runner: 'local', 13 | specs: ['./test-integration/specs/**/*.spec.js'], 14 | maxInstances: 10, 15 | capabilities: [{ 16 | maxInstances: 5, 17 | browserName: 'chrome', 18 | 'goog:chromeOptions': { 19 | args: ['headless', 'disable-gpu'], 20 | }, 21 | }], 22 | sync: true, 23 | logLevel: 'silent', 24 | coloredLogs: true, 25 | bail: 0, 26 | screenshotPath: './errorShots/', 27 | baseUrl: 'http://localhost', 28 | waitforTimeout: 10000, 29 | connectionRetryTimeout: 90000, 30 | connectionRetryCount: 3, 31 | services: isCI ? [] : ['selenium-standalone'], 32 | framework: 'mocha', 33 | reporters: ['spec'], 34 | mochaOpts: { 35 | ui: 'bdd' 36 | }, 37 | onPrepare () { 38 | return Server.start().then(server => { 39 | HTTPServer = server; 40 | }); 41 | }, 42 | onComplete() { 43 | HTTPServer.close(); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /packages/sample-app/src-libs/apollo-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import gql from 'graphql-tag'; 8 | import ApolloClient from "apollo-boost"; 9 | 10 | // EXPOSE ApolloClient as a global. 11 | // Since lwc rollup compiler cannot handle graphql *.mjs files 12 | window.ApolloClient = ApolloClient; 13 | window.gql = gql; 14 | -------------------------------------------------------------------------------- /packages/sample-app/src-server/books.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | module.exports.books = 8 | [ 9 | { 10 | "title": "Fundamentals of Wavelets", 11 | "author": "Goswami, Jaideva", 12 | "genre": "signal_processing", 13 | "pages": 228, 14 | "publisher": "Wiley" 15 | }, 16 | { 17 | "title": "Data Smart", 18 | "author": "Foreman, John", 19 | "genre": "data_science", 20 | "pages": 235, 21 | "publisher": "Wiley" 22 | }, 23 | { 24 | "title": "God Created the Integers", 25 | "author": "Hawking, Stephen", 26 | "genre": "mathematics", 27 | "pages": 197, 28 | "publisher": "Penguin" 29 | }, 30 | { 31 | "title": "Superfreakonomics", 32 | "author": "Dubner, Stephen", 33 | "genre": "economics", 34 | "pages": 179, 35 | "publisher": "HarperCollins" 36 | }, 37 | { 38 | "title": "Orientalism", 39 | "author": "Said, Edward", 40 | "genre": "history", 41 | "pages": 197, 42 | "publisher": "Penguin" 43 | }, 44 | { 45 | "title": "Nature of Statistical Learning Theory, The", 46 | "author": "Vapnik, Vladimir", 47 | "genre": "data_science", 48 | "pages": 230, 49 | "publisher": "Springer" 50 | }, 51 | { 52 | "title": "Integration of the Indian States", 53 | "author": "Menon, V P", 54 | "genre": "history", 55 | "pages": 217, 56 | "publisher": "Orient Blackswan" 57 | }, 58 | { 59 | "title": "Drunkard's Walk, The", 60 | "author": "Mlodinow, Leonard", 61 | "genre": "science", 62 | "pages": 197, 63 | "publisher": "Penguin" 64 | }, 65 | { 66 | "title": "Image Processing & Mathematical Morphology", 67 | "author": "Shih, Frank", 68 | "genre": "signal_processing", 69 | "pages": 241, 70 | "publisher": "CRC" 71 | }, 72 | { 73 | "title": "How to Think Like Sherlock Holmes", 74 | "author": "Konnikova, Maria", 75 | "genre": "psychology", 76 | "pages": 240, 77 | "publisher": "Penguin" 78 | }, 79 | { 80 | "title": "Data Scientists at Work", 81 | "author": "Sebastian Gutierrez", 82 | "genre": "data_science", 83 | "pages": 230, 84 | "publisher": "Apress" 85 | }, 86 | { 87 | "title": "Slaughterhouse Five", 88 | "author": "Vonnegut, Kurt", 89 | "genre": "fiction", 90 | "pages": 198, 91 | "publisher": "Random House" 92 | }, 93 | { 94 | "title": "Birth of a Theorem", 95 | "author": "Villani, Cedric", 96 | "genre": "mathematics", 97 | "pages": 234, 98 | "publisher": "Bodley Head" 99 | }, 100 | { 101 | "title": "Structure & Interpretation of Computer Programs", 102 | "author": "Sussman, Gerald", 103 | "genre": "computer_science", 104 | "pages": 240, 105 | "publisher": "MIT Press" 106 | }, 107 | { 108 | "title": "Age of Wrath, The", 109 | "author": "Eraly, Abraham", 110 | "genre": "history", 111 | "pages": 238, 112 | "publisher": "Penguin" 113 | }, 114 | { 115 | "title": "Trial, The", 116 | "author": "Kafka, Frank", 117 | "genre": "fiction", 118 | "pages": 198, 119 | "publisher": "Random House" 120 | }, 121 | { 122 | "title": "Statistical Decision Theory'", 123 | "author": "Pratt, John", 124 | "genre": "data_science", 125 | "pages": 236, 126 | "publisher": "MIT Press" 127 | }, 128 | { 129 | "title": "Data Mining Handbook", 130 | "author": "Nisbet, Robert", 131 | "genre": "data_science", 132 | "pages": 242, 133 | "publisher": "Apress" 134 | }, 135 | { 136 | "title": "New Machiavelli, The", 137 | "author": "Wells, H. G.", 138 | "genre": "fiction", 139 | "pages": 180, 140 | "publisher": "Penguin" 141 | }, 142 | { 143 | "title": "Physics & Philosophy", 144 | "author": "Heisenberg, Werner", 145 | "genre": "science", 146 | "pages": 197, 147 | "publisher": "Penguin" 148 | }, 149 | { 150 | "title": "Making Software", 151 | "author": "Oram, Andy", 152 | "genre": "computer_science", 153 | "pages": 232, 154 | "publisher": "O'Reilly" 155 | }, 156 | { 157 | "title": "Analysis, Vol I", 158 | "author": "Tao, Terence", 159 | "genre": "mathematics", 160 | "pages": 248, 161 | "publisher": "HBA" 162 | }, 163 | { 164 | "title": "Machine Learning for Hackers", 165 | "author": "Conway, Drew", 166 | "genre": "data_science", 167 | "pages": 233, 168 | "publisher": "O'Reilly" 169 | }, 170 | { 171 | "title": "Signal and the Noise, The", 172 | "author": "Silver, Nate", 173 | "genre": "data_science", 174 | "pages": 233, 175 | "publisher": "Penguin" 176 | }, 177 | { 178 | "title": "Python for Data Analysis", 179 | "author": "McKinney, Wes", 180 | "genre": "data_science", 181 | "pages": 233, 182 | "publisher": "O'Reilly" 183 | }, 184 | { 185 | "title": "Introduction to Algorithms", 186 | "author": "Cormen, Thomas", 187 | "genre": "computer_science", 188 | "pages": 234, 189 | "publisher": "MIT Press" 190 | }, 191 | { 192 | "title": "Beautiful and the Damned, The", 193 | "author": "Deb, Siddhartha", 194 | "genre": "nonfiction", 195 | "pages": 198, 196 | "publisher": "Penguin" 197 | }, 198 | { 199 | "title": "Outsider, The", 200 | "author": "Camus, Albert", 201 | "genre": "fiction", 202 | "pages": 198, 203 | "publisher": "Penguin" 204 | }, 205 | { 206 | "title": "Complete Sherlock Holmes, The - Vol I", 207 | "author": "Doyle, Arthur Conan", 208 | "genre": "fiction", 209 | "pages": 176, 210 | "publisher": "Random House" 211 | }, 212 | { 213 | "title": "Complete Sherlock Holmes, The - Vol II", 214 | "author": "Doyle, Arthur Conan", 215 | "genre": "fiction", 216 | "pages": 176, 217 | "publisher": "Random House" 218 | }, 219 | { 220 | "title": "Wealth of Nations, The", 221 | "author": "Smith, Adam", 222 | "genre": "economics", 223 | "pages": 175, 224 | "publisher": "Random House" 225 | }, 226 | { 227 | "title": "Pillars of the Earth, The", 228 | "author": "Follett, Ken", 229 | "genre": "fiction", 230 | "pages": 176, 231 | "publisher": "Random House" 232 | }, 233 | { 234 | "title": "Mein Kampf", 235 | "author": "Hitler, Adolf", 236 | "genre": "nonfiction", 237 | "pages": 212, 238 | "publisher": "Rupa" 239 | }, 240 | { 241 | "title": "Tao of Physics, The", 242 | "author": "Capra, Fritjof", 243 | "genre": "science", 244 | "pages": 179, 245 | "publisher": "Penguin" 246 | }, 247 | { 248 | "title": "Surely You're Joking Mr Feynman", 249 | "author": "Feynman, Richard", 250 | "genre": "science", 251 | "pages": 198, 252 | "publisher": "Random House" 253 | }, 254 | { 255 | "title": "Farewell to Arms, A", 256 | "author": "Hemingway, Ernest", 257 | "genre": "fiction", 258 | "pages": 179, 259 | "publisher": "Rupa" 260 | }, 261 | { 262 | "title": "Veteran, The", 263 | "author": "Forsyth, Frederick", 264 | "genre": "fiction", 265 | "pages": 177, 266 | "publisher": "Transworld" 267 | }, 268 | { 269 | "title": "False Impressions", 270 | "author": "Archer, Jeffery", 271 | "genre": "fiction", 272 | "pages": 177, 273 | "publisher": "Pan" 274 | }, 275 | { 276 | "title": "Last Lecture, The", 277 | "author": "Pausch, Randy", 278 | "genre": "nonfiction", 279 | "pages": 197, 280 | "publisher": "Hyperion" 281 | }, 282 | { 283 | "title": "Return of the Primitive", 284 | "author": "Rand, Ayn", 285 | "genre": "philosophy", 286 | "pages": 202, 287 | "publisher": "Penguin" 288 | }, 289 | { 290 | "title": "Jurassic Park", 291 | "author": "Crichton, Michael", 292 | "genre": "fiction", 293 | "pages": 174, 294 | "publisher": "Random House" 295 | }, 296 | { 297 | "title": "Russian Journal, A", 298 | "author": "Steinbeck, John", 299 | "genre": "nonfiction", 300 | "pages": 196, 301 | "publisher": "Penguin" 302 | }, 303 | { 304 | "title": "Tales of Mystery and Imagination", 305 | "author": "Poe, Edgar Allen", 306 | "genre": "fiction", 307 | "pages": 172, 308 | "publisher": "HarperCollins" 309 | }, 310 | { 311 | "title": "Freakonomics", 312 | "author": "Dubner, Stephen", 313 | "genre": "economics", 314 | "pages": 197, 315 | "publisher": "Penguin" 316 | }, 317 | { 318 | "title": "Hidden Connections, The", 319 | "author": "Capra, Fritjof", 320 | "genre": "science", 321 | "pages": 197, 322 | "publisher": "HarperCollins" 323 | }, 324 | { 325 | "title": "Story of Philosophy, The", 326 | "author": "Durant, Will", 327 | "genre": "philosophy", 328 | "pages": 170, 329 | "publisher": "Pocket" 330 | }, 331 | { 332 | "title": "Asami Asami", 333 | "author": "Deshpande, P L", 334 | "genre": "fiction", 335 | "pages": 205, 336 | "publisher": "Mauj" 337 | }, 338 | { 339 | "title": "Journal of a Novel", 340 | "author": "Steinbeck, John", 341 | "genre": "fiction", 342 | "pages": 196, 343 | "publisher": "Penguin" 344 | }, 345 | { 346 | "title": "Once There Was a War", 347 | "author": "Steinbeck, John", 348 | "genre": "nonfiction", 349 | "pages": 196, 350 | "publisher": "Penguin" 351 | }, 352 | { 353 | "title": "Moon is Down, The", 354 | "author": "Steinbeck, John", 355 | "genre": "fiction", 356 | "pages": 196, 357 | "publisher": "Penguin" 358 | }, 359 | { 360 | "title": "Brethren, The", 361 | "author": "Grisham, John", 362 | "genre": "fiction", 363 | "pages": 174, 364 | "publisher": "Random House" 365 | }, 366 | { 367 | "title": "In a Free State", 368 | "author": "Naipaul, V. S.", 369 | "genre": "fiction", 370 | "pages": 196, 371 | "publisher": "Rupa" 372 | }, 373 | { 374 | "title": "Catch 22", 375 | "author": "Heller, Joseph", 376 | "genre": "fiction", 377 | "pages": 178, 378 | "publisher": "Random House" 379 | }, 380 | { 381 | "title": "Complete Mastermind, The", 382 | "author": "BBC", 383 | "genre": "nonfiction", 384 | "pages": 178, 385 | "publisher": "BBC" 386 | }, 387 | { 388 | "title": "Dylan on Dylan", 389 | "author": "Dylan, Bob", 390 | "genre": "nonfiction", 391 | "pages": 197, 392 | "publisher": "Random House" 393 | }, 394 | { 395 | "title": "Soft Computing & Intelligent Systems", 396 | "author": "Gupta, Madan", 397 | "genre": "data_science", 398 | "pages": 242, 399 | "publisher": "Elsevier" 400 | }, 401 | { 402 | "title": "Textbook of Economic Theory", 403 | "author": "Stonier, Alfred", 404 | "genre": "economics", 405 | "pages": 242, 406 | "publisher": "Pearson" 407 | }, 408 | { 409 | "title": "Econometric Analysis", 410 | "author": "Greene, W. H.", 411 | "genre": "economics", 412 | "pages": 242, 413 | "publisher": "Pearson" 414 | }, 415 | { 416 | "title": "Learning OpenCV", 417 | "author": "Bradsky, Gary", 418 | "genre": "data_science", 419 | "pages": 232, 420 | "publisher": "O'Reilly" 421 | }, 422 | { 423 | "title": "Data Structures Using C & C++", 424 | "author": "Tanenbaum, Andrew", 425 | "genre": "computer_science", 426 | "pages": 235, 427 | "publisher": "Prentice Hall" 428 | }, 429 | { 430 | "title": "Computer Vision, A Modern Approach", 431 | "author": "Forsyth, David", 432 | "genre": "data_science", 433 | "pages": 255, 434 | "publisher": "Pearson" 435 | }, 436 | { 437 | "title": "Principles of Communication Systems", 438 | "author": "Taub, Schilling", 439 | "genre": "computer_science", 440 | "pages": 240, 441 | "publisher": "TMH" 442 | }, 443 | { 444 | "title": "Let Us C", 445 | "author": "Kanetkar, Yashwant", 446 | "genre": "computer_science", 447 | "pages": 213, 448 | "publisher": "Prentice Hall" 449 | }, 450 | { 451 | "title": "Amulet of Samarkand, The", 452 | "author": "Stroud, Jonathan", 453 | "genre": "fiction", 454 | "pages": 179, 455 | "publisher": "Random House" 456 | }, 457 | { 458 | "title": "Crime and Punishment", 459 | "author": "Dostoevsky, Fyodor", 460 | "genre": "fiction", 461 | "pages": 180, 462 | "publisher": "Penguin" 463 | }, 464 | { 465 | "title": "Angels & Demons", 466 | "author": "Brown, Dan", 467 | "genre": "fiction", 468 | "pages": 178, 469 | "publisher": "Random House" 470 | }, 471 | { 472 | "title": "Argumentative Indian, The", 473 | "author": "Sen, Amartya", 474 | "genre": "nonfiction", 475 | "pages": 209, 476 | "publisher": "Picador" 477 | }, 478 | { 479 | "title": "Sea of Poppies", 480 | "author": "Ghosh, Amitav", 481 | "genre": "fiction", 482 | "pages": 197, 483 | "publisher": "Penguin" 484 | }, 485 | { 486 | "title": "Idea of Justice, The", 487 | "author": "Sen, Amartya", 488 | "genre": "nonfiction", 489 | "pages": 212, 490 | "publisher": "Penguin" 491 | }, 492 | { 493 | "title": "Raisin in the Sun, A", 494 | "author": "Hansberry, Lorraine", 495 | "genre": "fiction", 496 | "pages": 175, 497 | "publisher": "Penguin" 498 | }, 499 | { 500 | "title": "All the President's Men", 501 | "author": "Woodward, Bob", 502 | "genre": "history", 503 | "pages": 177, 504 | "publisher": "Random House" 505 | }, 506 | { 507 | "title": "Prisoner of Birth, A", 508 | "author": "Archer, Jeffery", 509 | "genre": "fiction", 510 | "pages": 176, 511 | "publisher": "Pan" 512 | }, 513 | { 514 | "title": "Scoop!", 515 | "author": "Nayar, Kuldip", 516 | "genre": "history", 517 | "pages": 216, 518 | "publisher": "HarperCollins" 519 | }, 520 | { 521 | "title": "Ahe Manohar Tari", 522 | "author": "Deshpande, Sunita", 523 | "genre": "nonfiction", 524 | "pages": 213, 525 | "publisher": "Mauj" 526 | }, 527 | { 528 | "title": "Last Mughal, The", 529 | "author": "Dalrymple, William", 530 | "genre": "history", 531 | "pages": 199, 532 | "publisher": "Penguin" 533 | }, 534 | { 535 | "title": "Social Choice & Welfare, Vol 39 No. 1", 536 | "author": "Various", 537 | "genre": "economics", 538 | "pages": 235, 539 | "publisher": "Springer" 540 | }, 541 | { 542 | "title": "Radiowaril Bhashane & Shrutika", 543 | "author": "Deshpande, P L", 544 | "genre": "nonfiction", 545 | "pages": 213, 546 | "publisher": "Mauj" 547 | }, 548 | { 549 | "title": "Gun Gayin Awadi", 550 | "author": "Deshpande, P L", 551 | "genre": "nonfiction", 552 | "pages": 212, 553 | "publisher": "Mauj" 554 | }, 555 | { 556 | "title": "Aghal Paghal", 557 | "author": "Deshpande, P L", 558 | "genre": "nonfiction", 559 | "pages": 212, 560 | "publisher": "Mauj" 561 | }, 562 | { 563 | "title": "Maqta-e-Ghalib", 564 | "author": "Garg, Sanjay", 565 | "genre": "fiction", 566 | "pages": 221, 567 | "publisher": "Mauj" 568 | }, 569 | { 570 | "title": "Beyond Degrees", 571 | "author": "", 572 | "genre": "nonfiction", 573 | "pages": 222, 574 | "publisher": "HarperCollins" 575 | }, 576 | { 577 | "title": "Manasa", 578 | "author": "Kale, V P", 579 | "genre": "nonfiction", 580 | "pages": 213, 581 | "publisher": "Mauj" 582 | }, 583 | { 584 | "title": "India from Midnight to Milennium", 585 | "author": "Tharoor, Shashi", 586 | "genre": "history", 587 | "pages": 198, 588 | "publisher": "Penguin" 589 | }, 590 | { 591 | "title": "World's Greatest Trials, The", 592 | "author": "", 593 | "genre": "history", 594 | "pages": 210, 595 | "publisher": "" 596 | }, 597 | { 598 | "title": "Great Indian Novel, The", 599 | "author": "Tharoor, Shashi", 600 | "genre": "fiction", 601 | "pages": 198, 602 | "publisher": "Penguin" 603 | }, 604 | { 605 | "title": "O Jerusalem!", 606 | "author": "Lapierre, Dominique", 607 | "genre": "history", 608 | "pages": 217, 609 | "publisher": "vikas" 610 | }, 611 | { 612 | "title": "City of Joy, The", 613 | "author": "Lapierre, Dominique", 614 | "genre": "fiction", 615 | "pages": 177, 616 | "publisher": "vikas" 617 | }, 618 | { 619 | "title": "Freedom at Midnight", 620 | "author": "Lapierre, Dominique", 621 | "genre": "history", 622 | "pages": 167, 623 | "publisher": "vikas" 624 | }, 625 | { 626 | "title": "Winter of Our Discontent, The", 627 | "author": "Steinbeck, John", 628 | "genre": "fiction", 629 | "pages": 196, 630 | "publisher": "Penguin" 631 | }, 632 | { 633 | "title": "On Education", 634 | "author": "Russell, Bertrand", 635 | "genre": "philosophy", 636 | "pages": 203, 637 | "publisher": "Routledge" 638 | }, 639 | { 640 | "title": "Free Will", 641 | "author": "Harris, Sam", 642 | "genre": "philosophy", 643 | "pages": 203, 644 | "publisher": "FreePress" 645 | }, 646 | { 647 | "title": "Bookless in Baghdad", 648 | "author": "Tharoor, Shashi", 649 | "genre": "nonfiction", 650 | "pages": 206, 651 | "publisher": "Penguin" 652 | }, 653 | { 654 | "title": "Case of the Lame Canary, The", 655 | "author": "Gardner, Earle Stanley", 656 | "genre": "fiction", 657 | "pages": 179, 658 | "publisher": "" 659 | }, 660 | { 661 | "title": "Theory of Everything, The", 662 | "author": "Hawking, Stephen", 663 | "genre": "science", 664 | "pages": 217, 665 | "publisher": "Jaico" 666 | }, 667 | { 668 | "title": "New Markets & Other Essays", 669 | "author": "Drucker, Peter", 670 | "genre": "economics", 671 | "pages": 176, 672 | "publisher": "Penguin" 673 | }, 674 | { 675 | "title": "Electric Universe", 676 | "author": "Bodanis, David", 677 | "genre": "science", 678 | "pages": 201, 679 | "publisher": "Penguin" 680 | }, 681 | { 682 | "title": "Hunchback of Notre Dame, The", 683 | "author": "Hugo, Victor", 684 | "genre": "fiction", 685 | "pages": 175, 686 | "publisher": "Random House" 687 | }, 688 | { 689 | "title": "Burning Bright", 690 | "author": "Steinbeck, John", 691 | "genre": "fiction", 692 | "pages": 175, 693 | "publisher": "Penguin" 694 | }, 695 | { 696 | "title": "Age of Discontuinity, The", 697 | "author": "Drucker, Peter", 698 | "genre": "economics", 699 | "pages": 178, 700 | "publisher": "Random House" 701 | }, 702 | { 703 | "title": "Doctor in the Nude", 704 | "author": "Gordon, Richard", 705 | "genre": "fiction", 706 | "pages": 179, 707 | "publisher": "Penguin" 708 | }, 709 | { 710 | "title": "Down and Out in Paris & London", 711 | "author": "Orwell, George", 712 | "genre": "nonfiction", 713 | "pages": 179, 714 | "publisher": "Penguin" 715 | }, 716 | { 717 | "title": "Identity & Violence", 718 | "author": "Sen, Amartya", 719 | "genre": "philosophy", 720 | "pages": 219, 721 | "publisher": "Penguin" 722 | }, 723 | { 724 | "title": "Beyond the Three Seas", 725 | "author": "Dalrymple, William", 726 | "genre": "history", 727 | "pages": 197, 728 | "publisher": "Random House" 729 | }, 730 | { 731 | "title": "World's Greatest Short Stories, The", 732 | "author": "", 733 | "genre": "fiction", 734 | "pages": 217, 735 | "publisher": "Jaico" 736 | }, 737 | { 738 | "title": "Talking Straight", 739 | "author": "Iacoca, Lee", 740 | "genre": "nonfiction", 741 | "pages": 175, 742 | "publisher": "" 743 | }, 744 | { 745 | "title": "Maugham's Collected Short Stories, Vol 3", 746 | "author": "Maugham, William S", 747 | "genre": "fiction", 748 | "pages": 171, 749 | "publisher": "Vintage" 750 | }, 751 | { 752 | "title": "Phantom of Manhattan, The", 753 | "author": "Forsyth, Frederick", 754 | "genre": "fiction", 755 | "pages": 180, 756 | "publisher": "" 757 | }, 758 | { 759 | "title": "Ashenden of The British Agent", 760 | "author": "Maugham, William S", 761 | "genre": "fiction", 762 | "pages": 160, 763 | "publisher": "Vintage" 764 | }, 765 | { 766 | "title": "Zen & The Art of Motorcycle Maintenance", 767 | "author": "Pirsig, Robert", 768 | "genre": "philosophy", 769 | "pages": 172, 770 | "publisher": "Vintage" 771 | }, 772 | { 773 | "title": "Great War for Civilization, The", 774 | "author": "Fisk, Robert", 775 | "genre": "history", 776 | "pages": 197, 777 | "publisher": "HarperCollins" 778 | }, 779 | { 780 | "title": "We the Living", 781 | "author": "Rand, Ayn", 782 | "genre": "fiction", 783 | "pages": 178, 784 | "publisher": "Penguin" 785 | }, 786 | { 787 | "title": "Artist and the Mathematician, The", 788 | "author": "Aczel, Amir", 789 | "genre": "science", 790 | "pages": 186, 791 | "publisher": "HighStakes" 792 | }, 793 | { 794 | "title": "History of Western Philosophy", 795 | "author": "Russell, Bertrand", 796 | "genre": "philosophy", 797 | "pages": 213, 798 | "publisher": "Routledge" 799 | }, 800 | { 801 | "title": "Selected Short Stories", 802 | "author": "", 803 | "genre": "fiction", 804 | "pages": 215, 805 | "publisher": "Jaico" 806 | }, 807 | { 808 | "title": "Rationality & Freedom", 809 | "author": "Sen, Amartya", 810 | "genre": "economics", 811 | "pages": 213, 812 | "publisher": "Springer" 813 | }, 814 | { 815 | "title": "Clash of Civilizations and Remaking of the World Order", 816 | "author": "Huntington, Samuel", 817 | "genre": "history", 818 | "pages": 228, 819 | "publisher": "Simon&Schuster" 820 | }, 821 | { 822 | "title": "Uncommon Wisdom", 823 | "author": "Capra, Fritjof", 824 | "genre": "nonfiction", 825 | "pages": 197, 826 | "publisher": "Fontana" 827 | }, 828 | { 829 | "title": "One", 830 | "author": "Bach, Richard", 831 | "genre": "nonfiction", 832 | "pages": 172, 833 | "publisher": "Dell" 834 | }, 835 | { 836 | "title": "Karl Marx Biography", 837 | "author": "", 838 | "genre": "nonfiction", 839 | "pages": 162, 840 | "publisher": "" 841 | }, 842 | { 843 | "title": "To Sir With Love", 844 | "author": "Braithwaite", 845 | "genre": "fiction", 846 | "pages": 197, 847 | "publisher": "Penguin" 848 | }, 849 | { 850 | "title": "Half A Life", 851 | "author": "Naipaul, V S", 852 | "genre": "fiction", 853 | "pages": 196, 854 | "publisher": "" 855 | }, 856 | { 857 | "title": "Discovery of India, The", 858 | "author": "Nehru, Jawaharlal", 859 | "genre": "history", 860 | "pages": 230, 861 | "publisher": "" 862 | }, 863 | { 864 | "title": "Apulki", 865 | "author": "Deshpande, P L", 866 | "genre": "nonfiction", 867 | "pages": 211, 868 | "publisher": "" 869 | }, 870 | { 871 | "title": "Unpopular Essays", 872 | "author": "Russell, Bertrand", 873 | "genre": "philosophy", 874 | "pages": 198, 875 | "publisher": "" 876 | }, 877 | { 878 | "title": "Deceiver, The", 879 | "author": "Forsyth, Frederick", 880 | "genre": "fiction", 881 | "pages": 178, 882 | "publisher": "" 883 | }, 884 | { 885 | "title": "Veil: Secret Wars of the CIA", 886 | "author": "Woodward, Bob", 887 | "genre": "history", 888 | "pages": 171, 889 | "publisher": "" 890 | }, 891 | { 892 | "title": "Char Shabda", 893 | "author": "Deshpande, P L", 894 | "genre": "nonfiction", 895 | "pages": 214, 896 | "publisher": "" 897 | }, 898 | { 899 | "title": "Rosy is My Relative", 900 | "author": "Durrell, Gerald", 901 | "genre": "fiction", 902 | "pages": 176, 903 | "publisher": "" 904 | }, 905 | { 906 | "title": "Moon and Sixpence, The", 907 | "author": "Maugham, William S", 908 | "genre": "fiction", 909 | "pages": 180, 910 | "publisher": "" 911 | }, 912 | { 913 | "title": "Political Philosophers", 914 | "author": "", 915 | "genre": "philosophy", 916 | "pages": 162, 917 | "publisher": "" 918 | }, 919 | { 920 | "title": "Short History of the World, A", 921 | "author": "Wells, H G", 922 | "genre": "history", 923 | "pages": 197, 924 | "publisher": "" 925 | }, 926 | { 927 | "title": "Trembling of a Leaf, The", 928 | "author": "Maugham, William S", 929 | "genre": "fiction", 930 | "pages": 205, 931 | "publisher": "" 932 | }, 933 | { 934 | "title": "Doctor on the Brain", 935 | "author": "Gordon, Richard", 936 | "genre": "fiction", 937 | "pages": 204, 938 | "publisher": "" 939 | }, 940 | { 941 | "title": "Simpsons & Their Mathematical Secrets", 942 | "author": "Singh, Simon", 943 | "genre": "science", 944 | "pages": 233, 945 | "publisher": "" 946 | }, 947 | { 948 | "title": "Pattern Classification", 949 | "author": "Duda, Hart", 950 | "genre": "data_science", 951 | "pages": 241, 952 | "publisher": "" 953 | }, 954 | { 955 | "title": "From Beirut to Jerusalem", 956 | "author": "Friedman, Thomas", 957 | "genre": "history", 958 | "pages": 202, 959 | "publisher": "" 960 | }, 961 | { 962 | "title": "Code Book, The", 963 | "author": "Singh, Simon", 964 | "genre": "science", 965 | "pages": 197, 966 | "publisher": "" 967 | }, 968 | { 969 | "title": "Age of the Warrior, The", 970 | "author": "Fisk, Robert", 971 | "genre": "history", 972 | "pages": 197, 973 | "publisher": "" 974 | }, 975 | { 976 | "title": "Final Crisis", 977 | "author": "", 978 | "genre": "comic", 979 | "pages": 257, 980 | "publisher": "" 981 | }, 982 | { 983 | "title": "Killing Joke, The", 984 | "author": "", 985 | "genre": "comic", 986 | "pages": 283, 987 | "publisher": "" 988 | }, 989 | { 990 | "title": "Flashpoint", 991 | "author": "", 992 | "genre": "comic", 993 | "pages": 265, 994 | "publisher": "" 995 | }, 996 | { 997 | "title": "Batman Earth One", 998 | "author": "", 999 | "genre": "comic", 1000 | "pages": 265, 1001 | "publisher": "" 1002 | }, 1003 | { 1004 | "title": "Crisis on Infinite Earths", 1005 | "author": "", 1006 | "genre": "comic", 1007 | "pages": 258, 1008 | "publisher": "" 1009 | }, 1010 | { 1011 | "title": "Numbers Behind Numb3rs, The", 1012 | "author": "Devlin, Keith", 1013 | "genre": "science", 1014 | "pages": 202, 1015 | "publisher": "" 1016 | }, 1017 | { 1018 | "title": "Superman Earth One - 1", 1019 | "author": "", 1020 | "genre": "comic", 1021 | "pages": 259, 1022 | "publisher": "" 1023 | }, 1024 | { 1025 | "title": "Superman Earth One - 2", 1026 | "author": "", 1027 | "genre": "comic", 1028 | "pages": 258, 1029 | "publisher": "" 1030 | }, 1031 | { 1032 | "title": "Justice League: Throne of Atlantis", 1033 | "author": "", 1034 | "genre": "comic", 1035 | "pages": 258, 1036 | "publisher": "" 1037 | }, 1038 | { 1039 | "title": "Justice League: The Villain's Journey", 1040 | "author": "", 1041 | "genre": "comic", 1042 | "pages": 258, 1043 | "publisher": "" 1044 | }, 1045 | { 1046 | "title": "Death of Superman, The", 1047 | "author": "", 1048 | "genre": "comic", 1049 | "pages": 258, 1050 | "publisher": "" 1051 | }, 1052 | { 1053 | "title": "History of the DC Universe", 1054 | "author": "", 1055 | "genre": "comic", 1056 | "pages": 258, 1057 | "publisher": "" 1058 | }, 1059 | { 1060 | "title": "Batman: The Long Halloween", 1061 | "author": "", 1062 | "genre": "comic", 1063 | "pages": 258, 1064 | "publisher": "" 1065 | }, 1066 | { 1067 | "title": "Life in Letters, A", 1068 | "author": "Steinbeck, John", 1069 | "genre": "nonfiction", 1070 | "pages": 196, 1071 | "publisher": "" 1072 | }, 1073 | { 1074 | "title": "Information, The", 1075 | "author": "Gleick, James", 1076 | "genre": "science", 1077 | "pages": 233, 1078 | "publisher": "" 1079 | }, 1080 | { 1081 | "title": "Journal of Economics, vol 106 No 3", 1082 | "author": "", 1083 | "genre": "economics", 1084 | "pages": 235, 1085 | "publisher": "" 1086 | }, 1087 | { 1088 | "title": "Elements of Information Theory", 1089 | "author": "Thomas, Joy", 1090 | "genre": "data_science", 1091 | "pages": 229, 1092 | "publisher": "" 1093 | }, 1094 | { 1095 | "title": "Power Electronics - Rashid", 1096 | "author": "Rashid, Muhammad", 1097 | "genre": "computer_science", 1098 | "pages": 235, 1099 | "publisher": "" 1100 | }, 1101 | { 1102 | "title": "Power Electronics - Mohan", 1103 | "author": "Mohan, Ned", 1104 | "genre": "computer_science", 1105 | "pages": 237, 1106 | "publisher": "" 1107 | }, 1108 | { 1109 | "title": "Neural Networks", 1110 | "author": "Haykin, Simon", 1111 | "genre": "data_science", 1112 | "pages": 240, 1113 | "publisher": "" 1114 | }, 1115 | { 1116 | "title": "Grapes of Wrath, The", 1117 | "author": "Steinbeck, John", 1118 | "genre": "fiction", 1119 | "pages": 196, 1120 | "publisher": "" 1121 | }, 1122 | { 1123 | "title": "Vyakti ani Valli", 1124 | "author": "Deshpande, P L", 1125 | "genre": "nonfiction", 1126 | "pages": 211, 1127 | "publisher": "" 1128 | }, 1129 | { 1130 | "title": "Statistical Learning Theory", 1131 | "author": "Vapnik, Vladimir", 1132 | "genre": "data_science", 1133 | "pages": 228, 1134 | "publisher": "" 1135 | }, 1136 | { 1137 | "title": "Empire of the Mughal - The Tainted Throne", 1138 | "author": "Rutherford, Alex", 1139 | "genre": "history", 1140 | "pages": 180, 1141 | "publisher": "" 1142 | }, 1143 | { 1144 | "title": "Empire of the Mughal - Brothers at War", 1145 | "author": "Rutherford, Alex", 1146 | "genre": "history", 1147 | "pages": 180, 1148 | "publisher": "" 1149 | }, 1150 | { 1151 | "title": "Empire of the Mughal - Ruler of the World", 1152 | "author": "Rutherford, Alex", 1153 | "genre": "history", 1154 | "pages": 180, 1155 | "publisher": "" 1156 | }, 1157 | { 1158 | "title": "Empire of the Mughal - The Serpent's Tooth", 1159 | "author": "Rutherford, Alex", 1160 | "genre": "history", 1161 | "pages": 180, 1162 | "publisher": "" 1163 | }, 1164 | { 1165 | "title": "Empire of the Mughal - Raiders from the North", 1166 | "author": "Rutherford, Alex", 1167 | "genre": "history", 1168 | "pages": 180, 1169 | "publisher": "" 1170 | }, 1171 | { 1172 | "title": "Mossad", 1173 | "author": "Baz-Zohar, Michael", 1174 | "genre": "history", 1175 | "pages": 236, 1176 | "publisher": "" 1177 | }, 1178 | { 1179 | "title": "Jim Corbett Omnibus", 1180 | "author": "Corbett, Jim", 1181 | "genre": "nonfiction", 1182 | "pages": 223, 1183 | "publisher": "" 1184 | }, 1185 | { 1186 | "title": "20000 Leagues Under the Sea", 1187 | "author": "Verne, Jules", 1188 | "genre": "fiction", 1189 | "pages": 190, 1190 | "publisher": "" 1191 | }, 1192 | { 1193 | "title": "Batatyachi Chal", 1194 | "author": "Deshpande P L", 1195 | "genre": "fiction", 1196 | "pages": 200, 1197 | "publisher": "" 1198 | }, 1199 | { 1200 | "title": "Hafasavnuk", 1201 | "author": "Deshpande P L", 1202 | "genre": "fiction", 1203 | "pages": 211, 1204 | "publisher": "" 1205 | }, 1206 | { 1207 | "title": "Urlasurla", 1208 | "author": "Deshpande P L", 1209 | "genre": "fiction", 1210 | "pages": 211, 1211 | "publisher": "" 1212 | }, 1213 | { 1214 | "title": "Pointers in C", 1215 | "author": "Kanetkar, Yashwant", 1216 | "genre": "computer_science", 1217 | "pages": 213, 1218 | "publisher": "" 1219 | }, 1220 | { 1221 | "title": "Cathedral and the Bazaar, The", 1222 | "author": "Raymond, Eric", 1223 | "genre": "computer_science", 1224 | "pages": 217, 1225 | "publisher": "" 1226 | }, 1227 | { 1228 | "title": "Design with OpAmps", 1229 | "author": "Franco, Sergio", 1230 | "genre": "computer_science", 1231 | "pages": 240, 1232 | "publisher": "" 1233 | }, 1234 | { 1235 | "title": "Think Complexity", 1236 | "author": "Downey, Allen", 1237 | "genre": "data_science", 1238 | "pages": 230, 1239 | "publisher": "" 1240 | }, 1241 | { 1242 | "title": "Devil's Advocate, The", 1243 | "author": "West, Morris", 1244 | "genre": "fiction", 1245 | "pages": 178, 1246 | "publisher": "" 1247 | }, 1248 | { 1249 | "title": "Ayn Rand Answers", 1250 | "author": "Rand, Ayn", 1251 | "genre": "philosophy", 1252 | "pages": 203, 1253 | "publisher": "" 1254 | }, 1255 | { 1256 | "title": "Philosophy: Who Needs It", 1257 | "author": "Rand, Ayn", 1258 | "genre": "philosophy", 1259 | "pages": 171, 1260 | "publisher": "" 1261 | }, 1262 | { 1263 | "title": "World's Great Thinkers, The", 1264 | "author": "", 1265 | "genre": "philosophy", 1266 | "pages": 189, 1267 | "publisher": "" 1268 | }, 1269 | { 1270 | "title": "Data Analysis with Open Source Tools", 1271 | "author": "Janert, Phillip", 1272 | "genre": "data_science", 1273 | "pages": 230, 1274 | "publisher": "" 1275 | }, 1276 | { 1277 | "title": "Broca's Brain", 1278 | "author": "Sagan, Carl", 1279 | "genre": "science", 1280 | "pages": 174, 1281 | "publisher": "" 1282 | }, 1283 | { 1284 | "title": "Men of Mathematics", 1285 | "author": "Bell, E T", 1286 | "genre": "mathematics", 1287 | "pages": 217, 1288 | "publisher": "" 1289 | }, 1290 | { 1291 | "title": "Oxford book of Modern Science Writing", 1292 | "author": "Dawkins, Richard", 1293 | "genre": "science", 1294 | "pages": 240, 1295 | "publisher": "" 1296 | }, 1297 | { 1298 | "title": "Justice, Judiciary and Democracy", 1299 | "author": "Ranjan, Sudhanshu", 1300 | "genre": "philosophy", 1301 | "pages": 224, 1302 | "publisher": "" 1303 | }, 1304 | { 1305 | "title": "Arthashastra, The", 1306 | "author": "Kautiyla", 1307 | "genre": "philosophy", 1308 | "pages": 214, 1309 | "publisher": "" 1310 | }, 1311 | { 1312 | "title": "We the People", 1313 | "author": "Palkhivala", 1314 | "genre": "philosophy", 1315 | "pages": 216, 1316 | "publisher": "" 1317 | }, 1318 | { 1319 | "title": "We the Nation", 1320 | "author": "Palkhivala", 1321 | "genre": "philosophy", 1322 | "pages": 216, 1323 | "publisher": "" 1324 | }, 1325 | { 1326 | "title": "Courtroom Genius, The", 1327 | "author": "Sorabjee", 1328 | "genre": "nonfiction", 1329 | "pages": 217, 1330 | "publisher": "" 1331 | }, 1332 | { 1333 | "title": "Dongri to Dubai", 1334 | "author": "Zaidi, Hussain", 1335 | "genre": "nonfiction", 1336 | "pages": 216, 1337 | "publisher": "" 1338 | }, 1339 | { 1340 | "title": "History of England, Foundation", 1341 | "author": "Ackroyd, Peter", 1342 | "genre": "history", 1343 | "pages": 197, 1344 | "publisher": "" 1345 | }, 1346 | { 1347 | "title": "City of Djinns", 1348 | "author": "Dalrymple, William", 1349 | "genre": "history", 1350 | "pages": 198, 1351 | "publisher": "" 1352 | }, 1353 | { 1354 | "title": "India's Legal System", 1355 | "author": "Nariman", 1356 | "genre": "nonfiction", 1357 | "pages": 177, 1358 | "publisher": "" 1359 | }, 1360 | { 1361 | "title": "More Tears to Cry", 1362 | "author": "Sassoon, Jean", 1363 | "genre": "fiction", 1364 | "pages": 235, 1365 | "publisher": "" 1366 | }, 1367 | { 1368 | "title": "Ropemaker, The", 1369 | "author": "Dickinson, Peter", 1370 | "genre": "fiction", 1371 | "pages": 196, 1372 | "publisher": "" 1373 | }, 1374 | { 1375 | "title": "Angels & Demons", 1376 | "author": "Brown, Dan", 1377 | "genre": "fiction", 1378 | "pages": 170, 1379 | "publisher": "" 1380 | }, 1381 | { 1382 | "title": "Judge, The", 1383 | "author": "", 1384 | "genre": "fiction", 1385 | "pages": 170, 1386 | "publisher": "" 1387 | }, 1388 | { 1389 | "title": "Attorney, The", 1390 | "author": "", 1391 | "genre": "fiction", 1392 | "pages": 170, 1393 | "publisher": "" 1394 | }, 1395 | { 1396 | "title": "Prince, The", 1397 | "author": "Machiavelli", 1398 | "genre": "philosophy", 1399 | "pages": 173, 1400 | "publisher": "" 1401 | }, 1402 | { 1403 | "title": "Eyeless in Gaza", 1404 | "author": "Huxley, Aldous", 1405 | "genre": "fiction", 1406 | "pages": 180, 1407 | "publisher": "" 1408 | }, 1409 | { 1410 | "title": "Tales of Beedle the Bard", 1411 | "author": "Rowling, J K", 1412 | "genre": "fiction", 1413 | "pages": 184, 1414 | "publisher": "" 1415 | }, 1416 | { 1417 | "title": "Girl with the Dragon Tattoo", 1418 | "author": "Larsson, Steig", 1419 | "genre": "fiction", 1420 | "pages": 179, 1421 | "publisher": "" 1422 | }, 1423 | { 1424 | "title": "Girl who kicked the Hornet's Nest", 1425 | "author": "Larsson, Steig", 1426 | "genre": "fiction", 1427 | "pages": 179, 1428 | "publisher": "" 1429 | }, 1430 | { 1431 | "title": "Girl who played with Fire", 1432 | "author": "Larsson, Steig", 1433 | "genre": "fiction", 1434 | "pages": 179, 1435 | "publisher": "" 1436 | }, 1437 | { 1438 | "title": "Batman Handbook", 1439 | "author": "", 1440 | "genre": "comic", 1441 | "pages": 270, 1442 | "publisher": "" 1443 | }, 1444 | { 1445 | "title": "Murphy's Law", 1446 | "author": "", 1447 | "genre": "nonfiction", 1448 | "pages": 178, 1449 | "publisher": "" 1450 | }, 1451 | { 1452 | "title": "Structure and Randomness", 1453 | "author": "Tao, Terence", 1454 | "genre": "mathematics", 1455 | "pages": 252, 1456 | "publisher": "" 1457 | }, 1458 | { 1459 | "title": "Image Processing with MATLAB", 1460 | "author": "Eddins, Steve", 1461 | "genre": "signal_processing", 1462 | "pages": 241, 1463 | "publisher": "" 1464 | }, 1465 | { 1466 | "title": "Animal Farm", 1467 | "author": "Orwell, George", 1468 | "genre": "fiction", 1469 | "pages": 180, 1470 | "publisher": "" 1471 | }, 1472 | { 1473 | "title": "Idiot, The", 1474 | "author": "Dostoevsky, Fyodor", 1475 | "genre": "fiction", 1476 | "pages": 197, 1477 | "publisher": "" 1478 | }, 1479 | { 1480 | "title": "Christmas Carol, A", 1481 | "author": "Dickens, Charles", 1482 | "genre": "fiction", 1483 | "pages": 196, 1484 | "publisher": "" 1485 | } 1486 | ] 1487 | -------------------------------------------------------------------------------- /packages/sample-app/src-server/graphql.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | const { gql } = require('apollo-server'); 8 | const { ApolloServer } = require('apollo-server-express'); 9 | 10 | //const log = process.stdout.write.bind(process.stdout); 11 | 12 | 13 | // A schema is a collection of type definitions (hence "typeDefs") 14 | // that together define the "shape" of queries that are executed against 15 | // your data. 16 | const typeDefs = gql` 17 | # Comments in GraphQL strings (such as this one) start with the hash (#) symbol. 18 | 19 | # This "Book" type defines the queryable fields for every book in our data source. 20 | type Book { 21 | id: String! 22 | title: String 23 | author: String 24 | genre: String 25 | pages: Int 26 | publisher: String 27 | } 28 | 29 | type BookCollection { 30 | books: [Book] 31 | totalCount: Int 32 | } 33 | 34 | # The "Query" type is special: it lists all of the available queries that 35 | # clients can execute, along with the return type for each. In this 36 | # case, the "books" query returns an array of zero or more Books (defined above). 37 | type Query { 38 | book(id: String): Book 39 | books(offset: Int, limit: Int): BookCollection 40 | } 41 | 42 | input BookInput { 43 | title: String 44 | author: String 45 | genre: String 46 | pages: Int 47 | publisher: String 48 | } 49 | type Mutation { 50 | createBook(book: BookInput!): Book 51 | updateBook(id: String!, book: BookInput!): Book 52 | deleteBook(id: String!): [String] 53 | } 54 | `; 55 | 56 | function sortBooks(books) { 57 | return books.sort( (b1,b2) => { 58 | const t1 = b1.title; 59 | const t2 = b2.title; 60 | if( t1 > t2) return 1; 61 | if( t1 < t2) return -1; 62 | return 0; 63 | }); 64 | } 65 | const books = sortBooks(require("./books").books); 66 | let nextBookId = books.length; 67 | 68 | books.forEach( (item,i) => {item.id = "id_"+i.toString()}); 69 | books.findById = function findById(id) { 70 | return this.find((book) => { 71 | return book.id===id; 72 | }); 73 | } 74 | 75 | // Resolvers define the technique for fetching the types defined in the 76 | // schema. This resolver retrieves books from the "books" array above. 77 | const resolvers = { 78 | Query: { 79 | book: (parent, {id}) => { 80 | const r = books.findById(id); 81 | return r; 82 | }, 83 | books: (parent, {offset,limit}) => { 84 | const start = offset||0 85 | const end = start + (limit||50) 86 | return { 87 | books: books.slice(start,end), 88 | totalCount: books.length 89 | } 90 | } 91 | }, 92 | Mutation: { 93 | createBook: (root,{book}) => { 94 | const b = Object.assign({},book); 95 | b.id = "id_" + (nextBookId++); 96 | books.push(b); 97 | sortBooks(books); 98 | return b; 99 | }, 100 | updateBook: (root,{id,book}) => { 101 | const b = books.findById(id); 102 | if(b) { 103 | Object.assign(b,book); 104 | } 105 | return b; 106 | }, 107 | deleteBook: (root,{id}) => { 108 | const b = books.findById(id); 109 | if(b) { 110 | const idx = books.indexOf(b); 111 | books.splice(idx,1); 112 | return [id]; 113 | } 114 | return []; 115 | }, 116 | }, 117 | }; 118 | 119 | const server = new ApolloServer({ 120 | // These will be defined for both new or existing servers 121 | typeDefs, 122 | resolvers, 123 | playground: {} 124 | }); 125 | 126 | 127 | 128 | function init(app) { 129 | server.applyMiddleware({ app }); 130 | } 131 | 132 | module.exports = { init, server } 133 | -------------------------------------------------------------------------------- /packages/sample-app/src-server/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | // -- Libs ----------------------------------------------------------------------------- 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | const express = require('express'); 11 | const useragent = require('useragent'); 12 | 13 | const graphql = require("./graphql"); 14 | 15 | const compression = require('compression') 16 | 17 | // -- Config ---------------------------------------------------------------------------- 18 | const template = fs.readFileSync(path.join(__dirname, '../public/template.html'), 'utf8'); 19 | const log = process.stdout.write.bind(process.stdout); 20 | const port = process.env.PORT || 3001; 21 | const app = express(); 22 | 23 | const production = false; 24 | 25 | // Production configuration 26 | if(production) { 27 | app.use(compression()) 28 | } 29 | 30 | 31 | // -- Helpers --------------------------------------------------------------------------- 32 | function isCompat(req) { 33 | if(req.query['compat'] || req.query['compat']==="") { 34 | return req.query['compat']!=="false"; 35 | } 36 | const userAgent = req.headers['user-agent']; 37 | const { family, major } = useragent.parse(userAgent); 38 | const majorVersion = parseInt(major, 10); 39 | return family === 'IE' 40 | || (family === 'Chrome' && majorVersion < 48) 41 | || (family === 'Firefox' && majorVersion < 52) 42 | || (family === 'Safari' && majorVersion < 10); 43 | } 44 | 45 | function staticPath(...args) { 46 | return path.join(__dirname+"/..", 'public', ...args); 47 | } 48 | 49 | function renderTemplate(template,isCompat) { 50 | const apolloclient = "apollo-client" + (production ? ".min.js" : ".js"); 51 | const lwccomponents = (isCompat ? 'lwc-components-compat' : 'lwc-components') + (production ? ".min.js" : ".js"); 52 | return template 53 | .replace('{{apollo-client}}', apolloclient) 54 | .replace('{{lwc-components}}', lwccomponents); 55 | } 56 | 57 | 58 | // -- Middlewares ----------------------------------------------------------------------- 59 | app.use('/static', express.static(staticPath())); 60 | 61 | app.get('/*', (req, res) => { 62 | const isCompatMode = isCompat(req); 63 | res.send(renderTemplate(template,isCompatMode)); 64 | }); 65 | 66 | 67 | // -- GraphQL ---------------------------------------------------------------------------- 68 | 69 | graphql.init(app); 70 | 71 | 72 | // -- Server Start ----------------------------------------------------------------------- 73 | module.exports.start = () => { 74 | return new Promise((resolve) => { 75 | const server = app.listen(port, () => { 76 | log(`Server ready\n`); 77 | log(` http://localhost:${port}\n`); 78 | log(` http://localhost:${port}${graphql.server.graphqlPath}\n`); 79 | resolve(server); 80 | }); 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/about/about.html: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/about/about.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, } from 'lwc'; 8 | 9 | export default class App extends LightningElement { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/body/body.html: -------------------------------------------------------------------------------- 1 | 7 | 26 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/body/body.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, buildCustomElementConstructor } from 'lwc'; 8 | 9 | 10 | export default class Body extends LightningElement { 11 | } 12 | 13 | customElements.define("app-body", buildCustomElementConstructor(Body)); 14 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/footer/footer.html: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/footer/footer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement } from 'lwc'; 8 | 9 | export default class Footer extends LightningElement { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/header/header.html: -------------------------------------------------------------------------------- 1 | 7 | 32 | -------------------------------------------------------------------------------- /packages/sample-app/src/app/header/header.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement } from 'lwc'; 8 | 9 | export default class Header extends LightningElement { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/sample-app/src/form/input.html: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /packages/sample-app/src/form/input.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, api } from 'lwc'; 8 | 9 | export default class Input extends LightningElement { 10 | 11 | @api object; 12 | @api bind; 13 | 14 | @api type; 15 | @api style; 16 | @api class; 17 | @api placeholder; 18 | 19 | _getValue() { 20 | if(this.object && this.bind) { 21 | return this.object[this.bind] 22 | } 23 | return undefined; 24 | } 25 | _setValue(value) { 26 | if(this.object && this.bind) { 27 | this.object[this.bind] = value; 28 | } 29 | } 30 | 31 | valueAsString() { 32 | const value = this._getValue(); 33 | if(value) { 34 | const t = this.type; 35 | if(t==="number") { 36 | return Number.toString(); 37 | } 38 | return value.toString(); 39 | } 40 | return ""; 41 | } 42 | 43 | handleChange(event) { 44 | const input = event.target; 45 | const value = input.value; 46 | if(this.type==="number") { 47 | this._setValue(Number.parseFloat(value)); 48 | } else { 49 | this._setValue(value); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/sample-app/src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | 9 | // Do not use native shadow DOM as it breaks bootstrap 10 | // This should be added before the LWC engine else it has some unpredictable results 11 | import "@lwc/synthetic-shadow" 12 | 13 | /* eslint-disable no-undef */ 14 | import { createElement, register } from "lwc"; 15 | 16 | // The wire service has to be registered once 17 | import { registerWireService } from 'wire-service'; 18 | registerWireService(register) 19 | 20 | // Import this web components to get them loaded and registered 21 | import Body from "app/body"; 22 | 23 | 24 | // We use the apollo-boost lib here for convenience, but this is not required 25 | // https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost 26 | //import ApolloClient from 'apollo-boost'; 27 | 28 | // We cannot embed the Apollo client in the LWC compiler as it misses a .mjs transformer, required by graphql.js 29 | // On the other hand, having the apollo-client isolated might make it perform better, as it can be cached independently 30 | // const ApolloClient = window.ApolloClient; 31 | import { setClient } from '@lwce/apollo-client'; 32 | 33 | // Register a single Apollo client to be used through the whole application 34 | setClient(new ApolloClient({ 35 | uri: 'http://localhost:3001/graphql' 36 | })); 37 | 38 | // Manually creates the component until the wire reform kicks in abs then we'll use a custom element instead 39 | document.getElementById("main").appendChild(createElement("app-body", { is: Body })); 40 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/app/app.html: -------------------------------------------------------------------------------- 1 | 7 | 22 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/app/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, } from 'lwc'; 8 | 9 | export default class App extends LightningElement { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/bookdialog/bookdialog.html: -------------------------------------------------------------------------------- 1 | 7 | 51 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/bookdialog/bookdialog.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import Dialog from "../../rad/dialog/dialog"; 8 | 9 | export default class BookDialog extends Dialog { 10 | 11 | constructor() { 12 | super(); 13 | this.title = "Book" 14 | } 15 | 16 | handleSave() { 17 | this.hide("ok"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/booklist/booklist.html: -------------------------------------------------------------------------------- 1 | 7 | 115 | -------------------------------------------------------------------------------- /packages/sample-app/src/query/booklist/booklist.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, wire } from 'lwc'; 8 | 9 | import { useQuery, useMutation } from '@lwce/apollo-client'; 10 | 11 | const BOOK_LIST = gql` 12 | query($offset: Int!, $limit: Int!) { 13 | books(offset: $offset, limit: $limit) { 14 | totalCount, 15 | books { 16 | id, 17 | title 18 | author, 19 | genre, 20 | publisher 21 | } 22 | } 23 | }`; 24 | 25 | const BOOK_CREATE = gql` 26 | mutation createBook($book: BookInput!) { 27 | createBook(book: $book) { 28 | id 29 | title 30 | author 31 | genre 32 | publisher 33 | } 34 | } 35 | `; 36 | 37 | const BOOK_UPDATE = gql` 38 | mutation updateBook($id: String!, $book: BookInput!) { 39 | updateBook(id: $id, book: $book) { 40 | id 41 | title 42 | author 43 | genre 44 | publisher 45 | } 46 | } 47 | `; 48 | const BOOK_DELETE = gql` 49 | mutation deleteBook($id: String!) { 50 | deleteBook(id: $id) 51 | } 52 | `; 53 | 54 | export default class Booklist extends LightningElement { 55 | 56 | showDeleteSuccessAlert=false; 57 | showCreateSuccessAlert=false; 58 | showUpdateSuccessAlert=false; 59 | 60 | variables = { 61 | offset: 0, 62 | limit: 10 63 | } 64 | 65 | // Query 66 | @wire(useQuery, { 67 | query: BOOK_LIST, 68 | variables: '$variables' 69 | }) books; 70 | 71 | // Mutations 72 | @wire(useMutation, { 73 | mutation: BOOK_CREATE 74 | }) bookCreate; 75 | 76 | @wire(useMutation, { 77 | mutation: BOOK_UPDATE 78 | }) bookUpdate; 79 | 80 | @wire(useMutation, { 81 | mutation: BOOK_DELETE 82 | }) bookDelete; 83 | 84 | handleFirst() { 85 | this.variables = { 86 | ...this.variables, 87 | offset: 0 88 | } 89 | } 90 | handlePrev() { 91 | if(this.variables.offset>0) { 92 | this.variables = { 93 | ...this.variables, 94 | offset: this.variables.offset - this.variables.limit 95 | } 96 | } 97 | } 98 | handleNext() { 99 | if( (this.variables.offset+this.variables.limit) { 131 | if(action==="ok") { 132 | const {...book} = o; 133 | const variables = { 134 | book: { 135 | title: book.title, 136 | author: book.author, 137 | genre: book.genre, 138 | publisher: book.publisher 139 | } 140 | }; 141 | // We violently reset the whole cache as the item order has changed 142 | // If we only re-execute the current query, then the displayed page will be accurate while 143 | // the other ones will not if they have been previously visited 144 | this.bookCreate.client.resetStore(); 145 | this.bookCreate.mutate({ 146 | variables 147 | // Reset the whole cache instead of re-executing the current query 148 | // refetchQueries: [{ 149 | // query: BOOK_LIST, 150 | // variables: this.variables 151 | // }] 152 | }).then(() => { 153 | this.showCreateSuccessAlert = true; 154 | });; 155 | } 156 | }); 157 | } 158 | } 159 | handleUpdate(event) { 160 | const idx = this._findBookIdx(event); 161 | if(idx!==undefined) { 162 | // We cannot edit the book returned from the query as it is read-only (enforced by Apollo client) 163 | // We make a copy that we edit 164 | const o = JSON.parse(JSON.stringify(this.books.data.books.books[idx])); 165 | const dlg = this.template.querySelector('query-bookdialog'); 166 | if(dlg) { 167 | dlg.show(o).then( (action) => { 168 | if(action==="ok") { 169 | const {id,...book} = o; 170 | const variables = { 171 | id, 172 | book: { 173 | title: book.title, 174 | author: book.author, 175 | genre: book.genre, 176 | publisher: book.publisher 177 | } 178 | }; 179 | this.bookUpdate.mutate({variables}).then(() => { 180 | this.showUpdateSuccessAlert = true; 181 | });; 182 | } 183 | }); 184 | } 185 | } 186 | } 187 | handleDelete(event) { 188 | const idx = this._findBookIdx(event); 189 | if(idx!==undefined) { 190 | const id = this.books.data.books.books[idx].id; 191 | const variables = { 192 | id 193 | }; 194 | // We violently reset the whole cache as the item order has changed 195 | // If we only re-execute the current query, then the displayed page will be accurate while 196 | // the other ones will not if they have been previously visited 197 | this.bookCreate.client.resetStore(); 198 | this.bookDelete.mutate({ 199 | variables 200 | // Reset the whole cache instead of re-executing the current query 201 | // refetchQueries: [{ 202 | // query: BOOK_LIST, 203 | // variables: this.variables 204 | // }] 205 | }).then(() => { 206 | this.showDeleteSuccessAlert = true; 207 | }); 208 | } 209 | } 210 | 211 | 212 | get pageNavigation() { 213 | if(this.books.data) { 214 | const page = this.variables.offset/this.variables.limit + 1; 215 | const total = this.books.data.books.totalCount/this.variables.limit + 1; 216 | return page.toFixed(0) + "/" + total.toFixed(0); 217 | } 218 | return "0/0"; 219 | } 220 | 221 | get isError() { 222 | return this.books.error; 223 | } 224 | 225 | get isLoading() { 226 | return !this.books.initialized && this.books.loading; 227 | } 228 | get hasData() { 229 | return this.books.initialized && this.books.data; 230 | } 231 | 232 | 233 | get bookList() { 234 | return (this.books.data && this.books.data.books.books) || []; 235 | } 236 | 237 | get totalCount() { 238 | return (this.books.data && this.books.data.books.totalCount) || 0; 239 | } 240 | 241 | hideDeleteAlert() { 242 | this.showDeleteSuccessAlert = false; 243 | } 244 | 245 | hideCreateAlert() { 246 | this.showCreateSuccessAlert = false; 247 | } 248 | 249 | hideUpdateAlert() { 250 | this.showUpdateSuccessAlert = false; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /packages/sample-app/src/rad/README.md: -------------------------------------------------------------------------------- 1 | # LWC RAD Utilities 2 | 3 | This is an experimental, opinionated UI library, for rapidly prototyping LWC applications using [Bootstrap](https://getbootstrap.com/). 4 | 5 | Eventually, it will be published as a standalone package. 6 | 7 | As of today, it can change any time to apply the best LWC patterns, so use it at your own risks. 8 | 9 | Stay tuned! 10 | -------------------------------------------------------------------------------- /packages/sample-app/src/rad/dialog/dialog.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, track, api } from 'lwc'; 8 | 9 | export const ACTION_CANCEL = "cancel"; 10 | 11 | function createPromise() { 12 | let _resolve, _reject; 13 | const promise = new Promise(function(resolve, reject){ 14 | _resolve = resolve; 15 | _reject = reject; 16 | }) 17 | promise.resolve = _resolve; 18 | promise.reject = _reject; 19 | return promise; 20 | } 21 | 22 | 23 | export default class Dialog extends LightningElement { 24 | 25 | title = "" 26 | 27 | // This should not change 28 | @track formData = { 29 | data: undefined, 30 | error: undefined 31 | }; 32 | 33 | _closePromise; 34 | _modal; 35 | 36 | connectedCallback() { 37 | this.addEventListener('request_context', (event) => { 38 | const key = event.detail.key; 39 | if (key==="form-data") { 40 | event.detail.formData = this.formData; 41 | event.preventDefault(); 42 | event.stopPropagation(); 43 | } 44 | }) 45 | } 46 | 47 | renderedCallback() { 48 | if(this._modal) { 49 | return; 50 | } 51 | this._modal = $(this.template.querySelector('.modal')); 52 | if(this._modal) { 53 | this._modal.on('hidden.bs.modal', () => { 54 | this._closePromise.resolve(this._closePromise.action); 55 | this._closePromise = undefined; 56 | }); 57 | } 58 | } 59 | 60 | @api show(data) { 61 | if(this._modal && !this._closePromise) { 62 | this._closePromise = new createPromise(); 63 | this._closePromise.action = ACTION_CANCEL; 64 | Object.assign( this.formData, { 65 | data, 66 | error: undefined 67 | }); 68 | this._modal.modal('show'); 69 | return this._closePromise; 70 | } 71 | } 72 | 73 | @api hide(action) { 74 | if(this._modal && this._closePromise) { 75 | this._closePromise.action = action || ACTION_CANCEL; 76 | Object.assign( this.formData, { 77 | data: undefined, 78 | error: undefined 79 | }); 80 | this._modal.modal('hide'); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /packages/sample-app/src/rad/form-field/form-field.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { LightningElement, api, track } from 'lwc'; 8 | 9 | export default class FormField extends LightningElement { 10 | 11 | @track formData; 12 | @api field 13 | 14 | 15 | // 16 | // Use dependency injection 17 | // Inspired by: 18 | // https://www.youtube.com/watch?v=6o5zaKHedTE 19 | // https://github.com/googlearchive/di-demo/blob/master/requester.html 20 | // Should later use, when available: 21 | // https://github.com/salesforce/lwc-rfcs/blob/master/text/0000-context-service.md 22 | // 23 | connectedCallback() { 24 | const key = 'form-data'; 25 | const event = new CustomEvent('request_context', { 26 | detail: {key}, 27 | bubbles: true, 28 | composed: true, 29 | cancelable: true, 30 | }); 31 | this.dispatchEvent(event); 32 | if (event.defaultPrevented) { 33 | this.formData = event.detail.formData; 34 | } else { 35 | throw new Error(`no provider found for ${key}, field: ${this.field}`); 36 | } 37 | } 38 | 39 | getData() { 40 | return this.formData && this.formData.data; 41 | } 42 | 43 | getValue() { 44 | if(this.getData() && this.field) { 45 | return this.getData()[this.field]; 46 | } 47 | return undefined; 48 | } 49 | getValueAsString() { 50 | const v = this.getValue(); 51 | if(v) { 52 | return v.toString(); 53 | } 54 | return ""; 55 | } 56 | 57 | setValue(value) { 58 | if(this.getData() && this.field) { 59 | this.getData()[this.field] = value; 60 | } 61 | return undefined; 62 | } 63 | setValueFromString(value) { 64 | if(value) { 65 | this.setValue(value); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/sample-app/src/rad/input/input.html: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /packages/sample-app/src/rad/input/input.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | import { api } from 'lwc'; 8 | import FormField from '../form-field/form-field'; 9 | 10 | export default class Input extends FormField { 11 | 12 | @api type="text" 13 | 14 | get value() { 15 | return this.getValueAsString(); 16 | } 17 | 18 | handleChange(event) { 19 | const v = event.target.value; 20 | this.setValueFromString(v); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/sample-app/test-integration/specs/greeting/Greeting.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | describe('Greeting', () => { 8 | }); 9 | -------------------------------------------------------------------------------- /packages/sample-app/test-integration/specs/greeting/GreetingPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | class GreetingAppPage { 8 | } 9 | module.exports = new GreetingAppPage(); 10 | -------------------------------------------------------------------------------- /scripts/license-header.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | const { updateFolder, updateFile } = require('./update-header'); 8 | 9 | updateFolder('/packages', {recursive:true} ); 10 | updateFolder('/scripts', {recursive:true} ); 11 | -------------------------------------------------------------------------------- /scripts/update-header.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020, salesforce.com, inc. 3 | All rights reserved. 4 | SPDX-License-Identifier: BSD-3-Clause 5 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | // 8 | // Script used to update the file headers with a license 9 | // This can eventually be packaged as a reusable library 10 | // 11 | 12 | const fs = require('fs'); 13 | 14 | const ENCODING = 'utf8'; 15 | const TRACE = false; 16 | 17 | 18 | function shouldProcessFolder(path) { 19 | if(path.endsWith('/node_modules')) { 20 | return false; 21 | } 22 | if(path.endsWith('/dist')) { 23 | return false; 24 | } 25 | return true; 26 | } 27 | 28 | function shouldProcessFile(path) { 29 | return true; 30 | } 31 | 32 | function getHeaderProperties(path) { 33 | if(shouldProcessFile(path)) { 34 | if(path.endsWith('.js')) { 35 | return { 36 | encoding: ENCODING, 37 | startComment: '/*', 38 | endComment: '*/', 39 | tags: ['Copyright (c)','salesforce.com, inc.'] 40 | } 41 | } 42 | if(path.endsWith('.ts')) { 43 | return { 44 | encoding: ENCODING, 45 | startComment: '/*', 46 | endComment: '*/', 47 | tags: ['Copyright (c)','salesforce.com, inc.'] 48 | } 49 | } 50 | if(path.endsWith('.html')) { 51 | return { 52 | encoding: ENCODING, 53 | startComment: '', 55 | tags: ['Copyright (c)','salesforce.com, inc.'] 56 | } 57 | } 58 | if(path.endsWith('.css')) { 59 | return { 60 | encoding: ENCODING, 61 | startComment: '/*', 62 | endComment: '*/', 63 | tags: ['Copyright (c)','salesforce.com, inc.'] 64 | } 65 | } 66 | } 67 | return null; 68 | } 69 | 70 | const LICENSE_CONTENT = ` 71 | Copyright (c) 2020, salesforce.com, inc. 72 | All rights reserved. 73 | SPDX-License-Identifier: BSD-3-Clause 74 | For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 75 | `; 76 | 77 | function loadLicenseContent(path,options) { 78 | return (options.license && options.license()) || LICENSE_CONTENT; 79 | } 80 | 81 | function findEncoding(path,options) { 82 | return (options.encoding && options.encoding()) || ENCODING; 83 | } 84 | 85 | 86 | // Some polyfills 87 | (function(w){ 88 | //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart 89 | var String=w.String, Proto=String.prototype; 90 | (function(o,p){ 91 | if(p in o?o[p]?false:true:true){ 92 | var r=/^\s+/; 93 | o[p]=o.trimLeft||function(){ 94 | return this.replace(r,'') 95 | } 96 | } 97 | })(Proto,'trimStart'); 98 | 99 | })(global); 100 | 101 | 102 | function processFile(path, options) { 103 | const headerProperties = getHeaderProperties(path); 104 | if(!headerProperties) { 105 | return; 106 | } 107 | 108 | const encoding = findEncoding(path,options); 109 | 110 | function loadFile() { 111 | return fs.readFileSync(path, encoding); 112 | } 113 | function saveFile(content) { 114 | console.log('Updated file: '+path); 115 | fs.writeFileSync(path,content, {encoding}); 116 | } 117 | 118 | function createFileHeader(_text) { 119 | const header = loadLicenseContent(path,options); 120 | if(header) { 121 | return headerProperties.startComment + header + headerProperties.endComment + '\n'; 122 | } 123 | return null; 124 | } 125 | 126 | function isLicenseComment(comment) { 127 | for(let i=0; i=0) { 140 | const comment = text.substring(0,end); 141 | if(isLicenseComment(comment)) { 142 | if(TRACE) { 143 | console.log('Found copyright header in: '+path); 144 | } 145 | return text.substring(end+headerProperties.endComment.length+1); 146 | } 147 | } 148 | } 149 | return text; 150 | } 151 | 152 | function replaceHeaderIfDifferent(_text,header) { 153 | let text = _text.trimLeft(); 154 | if(text.startsWith(header)) { // Ok, no need for a change... 155 | return false; 156 | } 157 | 158 | text = removeHeader(text); 159 | text = header + text; 160 | return text; 161 | } 162 | 163 | 164 | if(TRACE) { 165 | console.log("Processing file: "+path); 166 | } 167 | const fileContent = loadFile(); 168 | const header = createFileHeader(fileContent); 169 | const processedContent = replaceHeaderIfDifferent(fileContent,header); 170 | if(processedContent) { 171 | saveFile(processedContent) 172 | } 173 | } 174 | 175 | function processFolder(path, options) { 176 | if(!shouldProcessFolder(path)) { 177 | return; 178 | } 179 | if(TRACE) { 180 | console.log("Processing folder: "+path); 181 | } 182 | fs.readdirSync(path).forEach(function(file) { 183 | const filePath = path + "/" + file; 184 | let stat = fs.statSync(filePath); 185 | if (stat.isDirectory()) { 186 | if(options.recursive) { 187 | return processFolder(filePath, options); 188 | } 189 | } else { 190 | return processFile(filePath, options); 191 | } 192 | }); 193 | } 194 | 195 | function updateFolder(folder, options) { 196 | if(!folder.charAt(0)!=='/') { 197 | folder = '/' + folder; 198 | } 199 | const path = __dirname+'/..'+folder; 200 | processFolder(path, options); 201 | } 202 | 203 | function updateFile(file, options) { 204 | if(!file.chatAt(0)!=='/') { 205 | file = '/' + file; 206 | } 207 | const path = __dirname+'/..'+file; 208 | processFile(path, options); 209 | } 210 | 211 | module.exports = { 212 | updateFolder, 213 | updateFile 214 | } 215 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "noUnusedLocals": true, 5 | 6 | "declaration": true, 7 | "sourceMap": true, 8 | 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | 13 | "target": "es2018", 14 | }, 15 | 16 | "exclude": [ 17 | "**/__tests__", 18 | ] 19 | } 20 | --------------------------------------------------------------------------------