├── .github └── workflows │ ├── close-jira-ticket.yml │ ├── create-jira-ticket-from-issue.yml │ ├── create-jira-ticket-from-pull-request.yml │ └── nodejs.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── INSTRUCTIONS.md ├── LICENSE ├── README.md ├── concourse ├── pipeline.yml ├── scripts │ ├── publish.sh │ └── unit-tests.sh ├── set-pipeline.sh └── tasks │ ├── publish.yml │ └── unit-tests.yml ├── fql.configuration.json ├── log.configuration.json ├── media ├── .DS_Store ├── .gitignore ├── browse-feature.png ├── code.svg ├── database-solid.svg ├── delete.svg ├── extension-settings.png ├── fauna-commands.png ├── fauna-extension-logo.png ├── fauna-logo.svg ├── file-code.svg ├── file.svg ├── list.svg ├── plus.svg ├── query-feature.gif ├── refresh.svg ├── runAs.js ├── selection-query.gif ├── settings.svg ├── settingsWebView.css ├── settingsWebView.js ├── upload-schema-cmd.gif └── window.svg ├── package.json ├── src ├── CollectionSchemaItem.ts ├── DBSchemaItem.ts ├── DocumentSchemaItem.ts ├── FQLContentProvider.ts ├── FaunaSchemaProvider.ts ├── FunctionSchemaItem.ts ├── IndexSchemaItem.ts ├── RunAsWebviewProvider.ts ├── SettingsWebView.ts ├── config.ts ├── deleteResource.ts ├── extension.ts ├── fql.ts ├── index.d.ts ├── openFQLFile.ts ├── runQueryCommand.ts ├── specialTypes.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── fql.test.ts │ │ └── index.ts ├── types.ts ├── updateResourceCommand.ts └── uploadGraphqlSchemaCommand.ts ├── syntaxes ├── fql.tmLanguage └── log.tmLanguage ├── tsconfig.json ├── tslint.json └── yarn.lock /.github/workflows/close-jira-ticket.yml: -------------------------------------------------------------------------------- 1 | name: Set JIRA ticket to DONE 2 | 3 | on: 4 | issues: 5 | types: [closed, deleted] 6 | 7 | jobs: 8 | set_done_for_closed_issued: 9 | name: Set JIRA ticket to DONE 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Login 13 | uses: atlassian/gajira-login@master 14 | env: 15 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 16 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 17 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 18 | 19 | - name: Find comment with ticket number 20 | uses: peter-evans/find-comment@v1 21 | id: comment 22 | with: 23 | issue-number: ${{ github.event.issue.number }} 24 | body-includes: Internal ticket number is FE- 25 | 26 | - name: Get ticket number 27 | id: ticket 28 | uses: atlassian/gajira-find-issue-key@master 29 | with: 30 | string: ${{ steps.comment.outputs.comment-body }} 31 | 32 | - name: Close 33 | uses: atlassian/gajira-transition@master 34 | with: 35 | issue: ${{ steps.ticket.outputs.issue }} 36 | transition: 'DONE' 37 | -------------------------------------------------------------------------------- /.github/workflows/create-jira-ticket-from-issue.yml: -------------------------------------------------------------------------------- 1 | name: Create JIRA ticket for GH issue 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | create_ticket_from_issue: 9 | name: Create JIRA ticket 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Login 13 | uses: atlassian/gajira-login@master 14 | env: 15 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 16 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 17 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 18 | 19 | - name: Create 20 | id: create 21 | uses: atlassian/gajira-create@master 22 | with: 23 | project: FE 24 | issuetype: Bug 25 | summary: Issue ${{ github.event.issue.number }} ${{ github.event.issue.title }} 26 | description: ${{ github.event.issue.html_url }} 27 | fields: '{"labels": ["github", "issue", "dx-ops"], "components": [{ "name": "V4 VSCode Plugin" }]}' 28 | 29 | - name: Comment 30 | uses: peter-evans/create-or-update-comment@v1 31 | with: 32 | issue-number: ${{ github.event.issue.number }} 33 | body: Internal ticket number is ${{ steps.create.outputs.issue }} 34 | 35 | - name: Complete 36 | if: ${{ steps.create.outputs.issue }} 37 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 38 | -------------------------------------------------------------------------------- /.github/workflows/create-jira-ticket-from-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Create JIRA ticket for GH pull request 2 | 3 | on: 4 | pull_request: 5 | types: [opened] 6 | 7 | jobs: 8 | create_ticket_from_pr: 9 | name: Create JIRA ticket 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Login 13 | uses: atlassian/gajira-login@master 14 | env: 15 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 16 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 17 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 18 | 19 | - name: Create 20 | id: create 21 | uses: atlassian/gajira-create@master 22 | with: 23 | project: FE 24 | issuetype: Task 25 | summary: Pull Request ${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }} 26 | description: ${{ github.event.pull_request.html_url }} 27 | fields: '{"labels": ["github", "pr", "dx-ops"], "components": [{ "name": "V4 VSCode Plugin" }]}' 28 | 29 | - name: Complete 30 | if: ${{ steps.create.outputs.issue }} 31 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 32 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: yarn 28 | - run: yarn run build 29 | env: 30 | CI: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | *.zip 6 | .DS_Store 7 | .faunarc 8 | package-lock.json 9 | 10 | .idea/ 11 | *.iml 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 2.1.0 4 | 5 | - Update `faunadb-js` minor version 6 | 7 | ## 2.0.0 8 | 9 | - Latest version of the FaunaDB driver 10 | 11 | ## 1.8.1 12 | 13 | - Fixes `Run As` defect 14 | 15 | ## 1.8.0 16 | 17 | - Allows to modify/create/delete resources 18 | - Fixes Graphql schema files inability to upload or merge if it has more than one dot . in file name 19 | - Adds ability to run queries with different roles 20 | 21 | ## 1.7.2 22 | 23 | - Revised comment syntax to use `//` instead of `#`. 24 | - Improved GraphQL file type check for the `uploadGraphqlSchema` command. 25 | - Added GraphQL domain configuration item. 26 | 27 | ## 1.7.1 28 | 29 | - New branding leftover fixes (nested databases data couldn't be opened) 30 | 31 | ## 1.7.0 32 | 33 | - Only one Client instance is ever created 34 | - Renamed faunadb.get command to faunadb.open 35 | - Added faunadb.query command for easy Client reuse 36 | - Adds ability to configure domain, scheme, port 37 | - New branding updates 38 | - Makes it possible to run multiline queries 39 | 40 | ## 1.6.0 41 | 42 | - Add the new functions `CreateAccessProvider`, `AccessProvider`, `AccessProviders`, `CurrentIdentity`, `HasCurrentIdentity`, `CurrentToken` and `HasCurrentToken`. 43 | 44 | ## 1.5.0 45 | 46 | - Add an alias of the `Contains` function called `ContainsPath`, and deprecated the `Contains` function. 47 | - Add the new functions `ContainsField` and `ContainsValue` functions to make it easier to explore the structure of objects and documents. 48 | - Add the new `Reverse` function to reverse the order of items in an Array, Page, or Set. 49 | 50 | ## 1.3.2 51 | 52 | - Fix syntax highlight setup. 53 | 54 | ## 1.3.1 55 | 56 | - Update README and CHANGELOG 57 | 58 | ## 1.3.0 59 | 60 | - Add minimal syntax highlight to FQL file extension. [maestroartistryconsulting](https://github.com/maestroartistryconsulting) 61 | - Fix graphQL schema upload. [maestroartistryconsulting](https://github.com/maestroartistryconsulting) 62 | 63 | ## 1.2.0 64 | 65 | - Add commands to upload, merge, and override GraphQL schema. [nksaraf](https://github.com/nksaraf) 66 | - Add flexible auth via a .faunarc config file. [gahabeen](https://github.com/gahabeen) 67 | - Add `Documents()` on query runner. 68 | 69 | ## 1.1.0 70 | 71 | - Highlight FQL expression and run selected FQL query. [jfloresremar](https://github.com/jfloresremar) 72 | 73 | ## 1.0.0 74 | 75 | - Browse databases, indexes, collections, documents, and functions. 76 | - Run queries. 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ### Setup 4 | 5 | 1. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) this repository 6 | 7 | 2. [Clone](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the repository to your local system 8 | 9 | ```bash 10 | $ git clone git@github.com:[YOUR_USER_NAME]/vscode.git 11 | 12 | // OR 13 | 14 | $ git clone https://github.com/[YOUR_USER_NAME]/vscode.git 15 | ``` 16 | 17 | 3. Navigate into the cloned repository and open VSCode 18 | 19 | ```bash 20 | $ cd vscode/ 21 | $ code . 22 | ``` 23 | 24 | 4. Create a new `git` branch 25 | 26 | ```bash 27 | $ git checkout -b my-descriptive-branch-name 28 | ``` 29 | 30 | 5. Make changes to the files in your local repository 31 | 32 | 6. Test your work using the built-in VSCode debugger, which can be accessed from the Activity Bar or the Run > Start Debugging menu 33 | 34 | 7. [Push](https://help.github.com/en/github/using-git/pushing-commits-to-a-remote-repository) your work to a remote branch on your fork 35 | 36 | 8. [Issue a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) against the `main` branch of the `fauna/vscode` repo 37 | 38 | 39 | ### Pull Request Guidelines 40 | 41 | When issuing a PR, please do the following to help us expedite merging your work: 42 | 43 | - **IMPORTANT:** Ensure [maintainers can make changes](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) to your PR 44 | 45 | - Add a description to your PR 46 | 47 | - Update the `README.md` file to describe your new functionality 48 | -------------------------------------------------------------------------------- /INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | Most of the content in this guide follows the official (VSCode extension documentation)[https://code.visualstudio.com/api/get-started/your-first-extension]. You should read the `Get Started` section before moving on. 4 | 5 | ## Development 6 | 7 | To begin development, you'll need to clone this repo to your local system and install the dependencies using `yarn`. 8 | ```bash 9 | git clone https://github.com/fauna/vscode.git 10 | cd ./vscode 11 | yarn install 12 | ``` 13 | 14 | To use the extension during development, go to `Debug and Run > Play`. This will open a new VS Code window with the extension installed. 15 | 16 | ## Tests 17 | 18 | Currently, there are no tests, but we will want to add those in the future. 19 | 20 | For references, here are the (official docs about extension testing)[https://code.visualstudio.com/api/working-with-extensions/testing-extension]. 21 | 22 | ## Packaging 23 | 24 | The following command will generate a `.vsix` to publish. You can also share this file with teammates in order to QA test it before release. 25 | 26 | ```bash 27 | yarn vsce package 28 | ``` 29 | 30 | For more info you can check the (Publish Extension guide)[https://code.visualstudio.com/api/working-with-extensions/publishing-extension]. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Fauna VSCode Plugin is licensed for use as follows: 2 | 3 | """ 4 | Apache License 5 | Version 2.0, January 2004 6 | http://www.apache.org/licenses/ 7 | 8 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 9 | 10 | 1. Definitions. 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, 13 | and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by 16 | the copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all 19 | other entities that control, are controlled by, or are under common 20 | control with that entity. For the purposes of this definition, 21 | "control" means (i) the power, direct or indirect, to cause the 22 | direction or management of such entity, whether by contract or 23 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 24 | outstanding shares, or (iii) beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity 27 | exercising permissions granted by this License. 28 | 29 | "Source" form shall mean the preferred form for making modifications, 30 | including but not limited to software source code, documentation 31 | source, and configuration files. 32 | 33 | "Object" form shall mean any form resulting from mechanical 34 | transformation or translation of a Source form, including but 35 | not limited to compiled object code, generated documentation, 36 | and conversions to other media types. 37 | 38 | "Work" shall mean the work of authorship, whether in Source or 39 | Object form, made available under the License, as indicated by a 40 | copyright notice that is included in or attached to the work 41 | (an example is provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object 44 | form, that is based on (or derived from) the Work and for which the 45 | editorial revisions, annotations, elaborations, or other modifications 46 | represent, as a whole, an original work of authorship. For the purposes 47 | of this License, Derivative Works shall not include works that remain 48 | separable from, or merely link (or bind by name) to the interfaces of, 49 | the Work and Derivative Works thereof. 50 | 51 | "Contribution" shall mean any work of authorship, including 52 | the original version of the Work and any modifications or additions 53 | to that Work or Derivative Works thereof, that is intentionally 54 | submitted to Licensor for inclusion in the Work by the copyright owner 55 | or by an individual or Legal Entity authorized to submit on behalf of 56 | the copyright owner. For the purposes of this definition, "submitted" 57 | means any form of electronic, verbal, or written communication sent 58 | to the Licensor or its representatives, including but not limited to 59 | communication on electronic mailing lists, source code control systems, 60 | and issue tracking systems that are managed by, or on behalf of, the 61 | Licensor for the purpose of discussing and improving the Work, but 62 | excluding communication that is conspicuously marked or otherwise 63 | designated in writing by the copyright owner as "Not a Contribution." 64 | 65 | "Contributor" shall mean Licensor and any individual or Legal Entity 66 | on behalf of whom a Contribution has been received by Licensor and 67 | subsequently incorporated within the Work. 68 | 69 | 2. Grant of Copyright License. Subject to the terms and conditions of 70 | this License, each Contributor hereby grants to You a perpetual, 71 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 72 | copyright license to reproduce, prepare Derivative Works of, 73 | publicly display, publicly perform, sublicense, and distribute the 74 | Work and such Derivative Works in Source or Object form. 75 | 76 | 3. Grant of Patent License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | (except as stated in this section) patent license to make, have made, 80 | use, offer to sell, sell, import, and otherwise transfer the Work, 81 | where such license applies only to those patent claims licensable 82 | by such Contributor that are necessarily infringed by their 83 | Contribution(s) alone or by combination of their Contribution(s) 84 | with the Work to which such Contribution(s) was submitted. If You 85 | institute patent litigation against any entity (including a 86 | cross-claim or counterclaim in a lawsuit) alleging that the Work 87 | or a Contribution incorporated within the Work constitutes direct 88 | or contributory patent infringement, then any patent licenses 89 | granted to You under this License for that Work shall terminate 90 | as of the date such litigation is filed. 91 | 92 | 4. Redistribution. You may reproduce and distribute copies of the 93 | Work or Derivative Works thereof in any medium, with or without 94 | modifications, and in Source or Object form, provided that You 95 | meet the following conditions: 96 | 97 | (a) You must give any other recipients of the Work or 98 | Derivative Works a copy of this License; and 99 | 100 | (b) You must cause any modified files to carry prominent notices 101 | stating that You changed the files; and 102 | 103 | (c) You must retain, in the Source form of any Derivative Works 104 | that You distribute, all copyright, patent, trademark, and 105 | attribution notices from the Source form of the Work, 106 | excluding those notices that do not pertain to any part of 107 | the Derivative Works; and 108 | 109 | (d) If the Work includes a "NOTICE" text file as part of its 110 | distribution, then any Derivative Works that You distribute must 111 | include a readable copy of the attribution notices contained 112 | within such NOTICE file, excluding those notices that do not 113 | pertain to any part of the Derivative Works, in at least one 114 | of the following places: within a NOTICE text file distributed 115 | as part of the Derivative Works; within the Source form or 116 | documentation, if provided along with the Derivative Works; or, 117 | within a display generated by the Derivative Works, if and 118 | wherever such third-party notices normally appear. The contents 119 | of the NOTICE file are for informational purposes only and 120 | do not modify the License. You may add Your own attribution 121 | notices within Derivative Works that You distribute, alongside 122 | or as an addendum to the NOTICE text from the Work, provided 123 | that such additional attribution notices cannot be construed 124 | as modifying the License. 125 | 126 | You may add Your own copyright statement to Your modifications and 127 | may provide additional or different license terms and conditions 128 | for use, reproduction, or distribution of Your modifications, or 129 | for any such Derivative Works as a whole, provided Your use, 130 | reproduction, and distribution of the Work otherwise complies with 131 | the conditions stated in this License. 132 | 133 | 5. Submission of Contributions. Unless You explicitly state otherwise, 134 | any Contribution intentionally submitted for inclusion in the Work 135 | by You to the Licensor shall be under the terms and conditions of 136 | this License, without any additional terms or conditions. 137 | Notwithstanding the above, nothing herein shall supersede or modify 138 | the terms of any separate license agreement you may have executed 139 | with Licensor regarding such Contributions. 140 | 141 | 6. Trademarks. This License does not grant permission to use the trade 142 | names, trademarks, service marks, or product names of the Licensor, 143 | except as required for reasonable and customary use in describing the 144 | origin of the Work and reproducing the content of the NOTICE file. 145 | 146 | 7. Disclaimer of Warranty. Unless required by applicable law or 147 | agreed to in writing, Licensor provides the Work (and each 148 | Contributor provides its Contributions) on an "AS IS" BASIS, 149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 150 | implied, including, without limitation, any warranties or conditions 151 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 152 | PARTICULAR PURPOSE. You are solely responsible for determining the 153 | appropriateness of using or redistributing the Work and assume any 154 | risks associated with Your exercise of permissions under this License. 155 | 156 | 8. Limitation of Liability. In no event and under no legal theory, 157 | whether in tort (including negligence), contract, or otherwise, 158 | unless required by applicable law (such as deliberate and grossly 159 | negligent acts) or agreed to in writing, shall any Contributor be 160 | liable to You for damages, including any direct, indirect, special, 161 | incidental, or consequential damages of any character arising as a 162 | result of this License or out of the use or inability to use the 163 | Work (including but not limited to damages for loss of goodwill, 164 | work stoppage, computer failure or malfunction, or any and all 165 | other commercial damages or losses), even if such Contributor 166 | has been advised of the possibility of such damages. 167 | 168 | 9. Accepting Warranty or Additional Liability. While redistributing 169 | the Work or Derivative Works thereof, You may choose to offer, 170 | and charge a fee for, acceptance of support, warranty, indemnity, 171 | or other liability obligations and/or rights consistent with this 172 | License. However, in accepting such obligations, You may act only 173 | on Your own behalf and on Your sole responsibility, not on behalf 174 | of any other Contributor, and only if You agree to indemnify, 175 | defend, and hold each Contributor harmless for any liability 176 | incurred by, or claims asserted against, such Contributor by reason 177 | of your accepting any such warranty or additional liability. 178 | 179 | END OF TERMS AND CONDITIONS 180 | 181 | APPENDIX: How to apply the Apache License to your work. 182 | 183 | To apply the Apache License to your work, attach the following 184 | boilerplate notice, with the fields enclosed by brackets "[]" 185 | replaced with your own identifying information. (Don't include 186 | the brackets!) The text should be enclosed in the appropriate 187 | comment syntax for the file format. We also recommend that a 188 | file or class name and description of purpose be included on the 189 | same "printed page" as the copyright notice for easier 190 | identification within third-party archives. 191 | 192 | Copyright 2020 Fauna, Inc. 193 | 194 | Licensed under the Apache License, Version 2.0 (the "License"); 195 | you may not use this file except in compliance with the License. 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | distributed under the License is distributed on an "AS IS" BASIS, 202 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 203 | See the License for the specific language governing permissions and 204 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Fauna extension for VS Code](https://marketplace.visualstudio.com/items?itemName=fauna.fauna) 2 | 3 | Source code for the [Fauna VS Code extension](https://marketplace.visualstudio.com/items?itemName=fauna.fauna). 4 | 5 | This [extension](https://marketplace.visualstudio.com/items?itemName=fauna.fauna) allows users to view individual Fauna databases and run [FQL queries](https://docs.fauna.com/fauna/current/api/fql/) directly inside VS Code. 6 | 7 | **Note: This extension does not yet support auto-complete, which is planned for a future release. Alternatively, please feel free to submit a PR to help out!** 8 | 9 | ## Getting started 10 | 11 | ### Prerequisites 12 | 13 | 1. Create a [Fauna account](https://dashboard.fauna.com/accounts/register) 14 | 15 | 2. Install [VS Code](https://code.visualstudio.com/Download) 16 | 17 | 3. Install the [Fauna extension](https://marketplace.visualstudio.com/items?itemName=fauna.fauna) in VS Code. 18 | 19 | ### 1. Set your secret key 20 | 21 | You can persist a Fauna secret using either a `.faunarc` file in your project or using the settings in the VS Code IDE. 22 | 23 | Persisting a secret in VS Code settings sets the key at a global level for all projects. A project can override the global key by including a `.faunarc` config file in the root of the project. 24 | 25 | #### Using a `.faunarc` file for a project 26 | 27 | 1. **IMPORTANT:** Add `.faunarc` to your `.gitignore`. 28 | 2. Create a file `.faunarc` in your project root folder. 29 | 3. Add `FAUNA_KEY=your-secret-key` to the `.faunarc` file. 30 | 31 | Additionally, the config file supports these optional parameters: 32 | 33 | ``` 34 | FAUNA_DOMAIN=db.fauna.com 35 | FAUNA_SCHEME=https 36 | FAUNA_PORT=443 37 | FAUNA_GRAPHQL_HOST=https://graphql.fauna.com 38 | ``` 39 | 40 | #### Using VS Code to store a database key 41 | 42 | 1. The VS Code extension allows you to work in the context of a single database, for which you need to provide a key. You can do this by generating a key with the "admin" role from the database "Security" tab in the Fauna Dashboard. 43 | 44 | > Note that you'll need to update the Fauna domain in the extension settings (or `.faunarc` file) as needed to match the database Region Group; see [the Region Group documentation](https://docs.fauna.com/fauna/current/learn/understanding/region_groups#how-to-use-region-groups) for more details. 45 | 46 | 2. Copy the secret and paste it in the Fauna extension settings. You can find the Fauna extension settings by either: 47 | 48 | - Selecting `View > Extensions` to open the Extensions pane, scrolling down in the list of installed extensions until you see Fauna, then clicking the cog icon in the lower right to open the extension settings. 49 | 50 | - Selecting `Code > Preferences > Settings > Extensions > Fauna` from the VS Code menu. 51 | 52 | 3. Restart VS Code after setting or changing your secret. 53 | 54 | ![Extension settings](media/extension-settings.png) 55 | 56 | - `fauna.adminSecretKey`: The secret for a specific database. 57 | - `fauna.domain`: The Fauna domain for the database [Region Group](https://docs.fauna.com/fauna/current/learn/understanding/region_groups#how-to-use-region-groups) or `localhost` when using a local Fauna Dev Docker instance. 58 | - `fauna.scheme`: One of `https` or `http`. Only set to `http` when using a local Fauna Dev Docker instance. 59 | - `fauna.port`: The port number for the Fauna endpoint. When using a Fauna Dev Docker instance, use the port passed to the `docker run` command - usually `8443`. 60 | - `fauna.graphqlHost`: The full URL for the Fauna GraphQL API. The default is `https://graphql.fauna.com`. See [GraphQL Endpoints](https://docs.fauna.com/fauna/current/api/graphql/endpoints) for Region Group-specific URLs. 61 | 62 | > **WARNING**: Be careful! To avoid exposing this secret, do not commit it to your local `.vscode` configuration. 63 | 64 | ### 2. Browse database 65 | 66 | 1. Click on the Fauna bird icon in the Activity bar on the far left. If you do not see the Activity Bar, select `View > Appearance > Show Activity Bar` from the VS Code menu. 67 | 2. You should see a pane listing all of the database contents, which includes child databases, indexes, collections, documents, and functions. 68 | 69 | **Note: Browsing is read-only at this time, but you can edit all of your data by running queries (see next section).** 70 | 71 | ![Browse your database data](media/browse-feature.png) 72 | 73 | ### 3. Run queries 74 | 75 | 1. If you are on a Mac, open the command palette with the keyboard shortcut `Cmd` + `Shift` + `P`. If you are on a PC, use `Ctrl` + `Shift` + `P`. 76 | 77 | 2. Create a new file from which to run your [FQL queries](https://docs.fauna.com/fauna/current/api/fql/) by either: 78 | 79 | - Typing `Fauna: Create query` to select that command from the command palette dropdown menu. This opens a new tab with the `Paginate(Collections())` query already included. 80 | 81 | - Creating a new file with the `.fql` file extension. 82 | 83 | 3. Open the command palette again using `Cmd` + `Shift` + `P`, but this time start typing `Fauna: Run query` to select that command from the dropdown menu. 84 | 85 | 4. The Output panel should open from the bottom of the VS Code window and display the query results. 86 | 87 | ![Run queries](media/query-feature.gif) 88 | 89 | 5. If your file contains multiple FQL expressions, you can trigger them individually by highlighting the expression you want to execute. 90 | 91 | ![Run selection query](media/selection-query.gif) 92 | 93 | ### 4. Upload GraphQL Schema 94 | 95 | 1. Open a `.graphql` or `.gql` file containing your GraphQL schema as described in the [Fauna specification](https://docs.fauna.com/fauna/current/api/graphql). 96 | 97 | 2. If you are on a Mac, open the command palette with the keyboard shortcut `Cmd` + `Shift` + `P`. If you are on a PC, use `Ctrl` + `Shift` + `P`. 98 | 99 | 3. Fauna provides commands for uploading schemas, depending on [modes](https://docs.fauna.com/fauna/current/api/graphql/endpoints#modes): 100 | 101 | - Enter `Fauna: Upload GraphQL Schema` to upload in the default `merge` mode. 102 | 103 | - Enter `Fauna: Merge GraphQL Schema` to explicitly upload in `merge` mode. 104 | 105 | - Enter `Fauna: Override GraphQL Schema` to upload in `override` mode. 106 | 107 | > **WARNING**: `override` mode causes data loss for any previous GraphQL schema. Collections, indexes, or documents that are not involved in GraphQL are not affected. 108 | 109 | ![Upload GraphQL schema](media/upload-schema-cmd.gif) 110 | 111 | ## Features 112 | 113 | **Commands** 114 | 115 | - Fauna: Create query 116 | - Fauna: Run query 117 | - Fauna: Upload GraphQL Schema 118 | - Fauna: Merge GraphQL Schema 119 | - Fauna: Override GraphQL Schema 120 | 121 | ![Fauna commands](media/fauna-commands.png) 122 | 123 | ## Built With 124 | 125 | - [Fauna](https://fauna.com/) 126 | -------------------------------------------------------------------------------- /concourse/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | resources: 3 | - name: fauna-vscode-repository 4 | type: git 5 | icon: github 6 | source: 7 | uri: https://github.com/fauna/vscode.git 8 | branch: main 9 | 10 | jobs: 11 | - name: release 12 | serial: true 13 | public: false 14 | plan: 15 | - get: fauna-vscode-repository 16 | 17 | - task: unit-tests 18 | file: fauna-vscode-repository/concourse/tasks/unit-tests.yml 19 | 20 | - task: publish 21 | file: fauna-vscode-repository/concourse/tasks/publish.yml 22 | params: 23 | VSCE_PAT: ((vscode-marketplace-token_expires-2024_11_21)) 24 | -------------------------------------------------------------------------------- /concourse/scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eou 4 | 5 | cd ./fauna-vscode-repository 6 | 7 | apk add --no-cache git 8 | 9 | yarn install 10 | yarn run compile 11 | 12 | PACKAGE_VERSION=$(node -p -e "require('./package.json').version") 13 | echo "Current package version: $PACKAGE_VERSION" 14 | 15 | echo "Publishing a new version..." 16 | yarn run vsce publish 17 | -------------------------------------------------------------------------------- /concourse/scripts/unit-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eou pipefail 4 | 5 | cd ./fauna-vscode-repository 6 | 7 | apt-get update 8 | apt-get install -y xvfb gnupg2 libxshmfence-dev libnss3-dev libatk1.0-0 libatk-bridge2.0-0 libdrm2 libgtk-3-0 libgbm-dev libasound2 9 | 10 | export DISPLAY=':1' 11 | Xvfb :1 -screen 0 1024x768x24 > /dev/null 2>&1 & 12 | echo "Started Xvfb" 13 | 14 | yarn install 15 | yarn run compile 16 | yarn test 17 | -------------------------------------------------------------------------------- /concourse/set-pipeline.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # update the concourse pipeline with this script. 3 | 4 | fly -t faunadb set-pipeline --pipeline vscode-extension-release --config concourse/pipeline.yml -------------------------------------------------------------------------------- /concourse/tasks/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: node 7 | tag: 15.14.0-alpine3.10 8 | 9 | params: 10 | VSCE_PAT: 11 | 12 | inputs: 13 | - name: fauna-vscode-repository 14 | 15 | run: 16 | path: ./fauna-vscode-repository/concourse/scripts/publish.sh 17 | -------------------------------------------------------------------------------- /concourse/tasks/unit-tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: node 7 | tag: 16 8 | 9 | inputs: 10 | - name: fauna-vscode-repository 11 | 12 | run: 13 | path: ./fauna-vscode-repository/concourse/scripts/unit-tests.sh 14 | -------------------------------------------------------------------------------- /fql.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": ["/*", "*/"] 5 | }, 6 | "brackets": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"] 10 | ], 11 | "autoClosingPairs": [ 12 | ["{", "}"], 13 | ["[", "]"], 14 | ["(", ")"], 15 | ["'", "'"] 16 | ], 17 | "surroundingPairs": [ 18 | ["{", "}"], 19 | ["[", "]"], 20 | ["(", ")"], 21 | ["'", "'"] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /log.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "brackets": [ 3 | ["{", "}"], 4 | ["[", "]"], 5 | ["(", ")"], 6 | ["<", ">"] 7 | ], 8 | "autoClosingPairs": [ 9 | ["{", "}"], 10 | ["[", "]"], 11 | ["(", ")"], 12 | ["\"", "\""], 13 | ["'", "'"] 14 | ], 15 | "surroundingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /media/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/.DS_Store -------------------------------------------------------------------------------- /media/.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /media/browse-feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/browse-feature.png -------------------------------------------------------------------------------- /media/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /media/database-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /media/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/extension-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/extension-settings.png -------------------------------------------------------------------------------- /media/fauna-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/fauna-commands.png -------------------------------------------------------------------------------- /media/fauna-extension-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/fauna-extension-logo.png -------------------------------------------------------------------------------- /media/fauna-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /media/file-code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /media/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /media/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /media/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/query-feature.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/query-feature.gif -------------------------------------------------------------------------------- /media/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/runAs.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const vscode = acquireVsCodeApi(); 3 | 4 | const specifyDocument = document.getElementById('specifyDocument'); 5 | const roleSelector = document.getElementById('roleId'); 6 | const runAsActivate = document.getElementById('runAsActivate'); 7 | const runAs = document.getElementById('runAs'); 8 | const closeRunAs = document.getElementById('closeRunAs'); 9 | const collectionInput = document.getElementById('collectionInput'); 10 | const idInput = document.getElementById('idInput'); 11 | 12 | runAsActivate.addEventListener('click', () => { 13 | runAsActivate.style.display = 'none'; 14 | runAs.style.display = 'block'; 15 | roleSelector.value = 'admin'; 16 | vscode.postMessage({ type: 'roleChanged', role: 'admin' }); 17 | }); 18 | 19 | closeRunAs.addEventListener('click', () => { 20 | runAsActivate.style.display = 'flex'; 21 | runAs.style.display = 'none'; 22 | specifyDocument.style.display = 'none'; 23 | vscode.postMessage({ type: 'deactivateRunAs' }); 24 | }); 25 | 26 | roleSelector.addEventListener('change', event => { 27 | specifyDocument.style.display = 28 | event.target.value === '@doc' ? 'flex' : 'none'; 29 | 30 | vscode.postMessage({ type: 'roleChanged', role: event.target.value }); 31 | }); 32 | 33 | collectionInput.addEventListener('change', event => { 34 | vscode.postMessage({ 35 | type: 'collectionChanged', 36 | collection: event.target.value 37 | }); 38 | }); 39 | 40 | idInput.addEventListener('change', event => { 41 | vscode.postMessage({ 42 | type: 'idChanged', 43 | id: event.target.value 44 | }); 45 | }); 46 | })(); 47 | -------------------------------------------------------------------------------- /media/selection-query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/selection-query.gif -------------------------------------------------------------------------------- /media/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /media/settingsWebView.css: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 400px; 3 | margin: 0 auto; 4 | margin-top: 50px; 5 | } 6 | 7 | .form-group { 8 | display: flex; 9 | width: 100%; 10 | flex-direction: column; 11 | margin: 20px 0; 12 | } 13 | 14 | .form-group > ul { 15 | padding-left: 0; 16 | list-style-position: inside; 17 | margin: 0; 18 | } 19 | 20 | .form-group > label { 21 | font-weight: bold; 22 | font-size: 1rem; 23 | margin-bottom: 5px; 24 | display: flex; 25 | align-items: center; 26 | } 27 | 28 | .form-group > .readonly { 29 | font-size: 0.9rem; 30 | } 31 | 32 | .form-group > label > span { 33 | color: red; 34 | } 35 | 36 | .form-group input, 37 | .form-group select { 38 | box-sizing: border-box; 39 | height: 36px; 40 | } 41 | 42 | .form-group > .hint { 43 | font-size: 0.75rem; 44 | } 45 | 46 | .controls { 47 | display: flex; 48 | justify-content: space-between; 49 | } 50 | 51 | .delete { 52 | cursor: pointer; 53 | display: flex; 54 | align-items: center; 55 | margin-top: 10px; 56 | } 57 | 58 | .delete > svg { 59 | width: 12px; 60 | color: #492fb1; 61 | margin-right: 5px; 62 | } 63 | 64 | .controls > button { 65 | height: 35px; 66 | width: 180px; 67 | outline: none; 68 | border: none; 69 | cursor: pointer; 70 | } 71 | .controls > button.primary { 72 | color: white; 73 | background: #3f00a5; 74 | } 75 | 76 | .controls > button.primary:disabled { 77 | background: #ccc; 78 | color: #666; 79 | } 80 | 81 | .tooltip { 82 | position: relative; 83 | display: inline-block; 84 | border-bottom: 1px dotted black; 85 | } 86 | 87 | .tooltip > svg { 88 | width: 16px; 89 | height: 16px; 90 | color: #492fb1; 91 | } 92 | 93 | .tooltip .tooltiptext { 94 | width: 400px; 95 | visibility: hidden; 96 | background-color: black; 97 | color: #fff; 98 | text-align: center; 99 | border-radius: 6px; 100 | padding: 14px 30px; 101 | position: absolute; 102 | z-index: 1; 103 | } 104 | 105 | .tooltip:hover .tooltiptext { 106 | visibility: visible; 107 | } 108 | -------------------------------------------------------------------------------- /media/settingsWebView.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const vscode = acquireVsCodeApi(); 3 | 4 | const cancelBtn = document.getElementById('cancel'); 5 | const submitBtn = document.getElementById('submit'); 6 | const deleteBtn = document.getElementById('delete'); 7 | 8 | if (deleteBtn) { 9 | deleteBtn.addEventListener('click', e => { 10 | e.preventDefault(); 11 | vscode.postMessage({ type: 'delete' }); 12 | }); 13 | } 14 | 15 | cancelBtn.addEventListener('click', e => { 16 | e.preventDefault(); 17 | vscode.postMessage({ type: 'close' }); 18 | }); 19 | 20 | submitBtn.addEventListener('click', e => { 21 | e.preventDefault(); 22 | 23 | const elements = document.querySelectorAll('[name]'); 24 | const data = {}; 25 | for (let e of elements) { 26 | if (e.type === 'number') { 27 | if (e.type !== '') { 28 | data[e.name] = +e.value; 29 | } 30 | continue; 31 | } 32 | 33 | if (e.type === 'checkbox') { 34 | data[e.name] = e.value === 'on'; 35 | continue; 36 | } 37 | 38 | data[e.name] = e.value; 39 | } 40 | 41 | submitBtn.disabled = true; 42 | vscode.postMessage({ type: 'save', data }); 43 | }); 44 | 45 | window.addEventListener('message', event => { 46 | const message = event.data; 47 | switch (message.type) { 48 | case 'release_save': 49 | submitBtn.disabled = false; 50 | break; 51 | } 52 | }); 53 | })(); 54 | -------------------------------------------------------------------------------- /media/upload-schema-cmd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fauna/vscode/1d111a93c19a361113b002d42f746632003d8002/media/upload-schema-cmd.gif -------------------------------------------------------------------------------- /media/window.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fauna", 3 | "displayName": "Fauna v4", 4 | "description": "Fauna extension", 5 | "icon": "media/fauna-extension-logo.png", 6 | "version": "2.1.0", 7 | "publisher": "fauna", 8 | "engines": { 9 | "vscode": "^1.40.0" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/fauna/vscode.git" 14 | }, 15 | "categories": [ 16 | "Other" 17 | ], 18 | "activationEvents": [ 19 | "onCommand:extension.helloWorld", 20 | "onCommand:fauna.runQuery", 21 | "onCommand:fauna.createQuery", 22 | "onCommand:fauna.uploadGraphQLSchema", 23 | "onCommand:fauna.mergeGraphQLSchema", 24 | "onCommand:fauna.overrideGraphQLSchema", 25 | "onView:fauna-databases" 26 | ], 27 | "main": "./out/extension.js", 28 | "contributes": { 29 | "configuration": { 30 | "title": "Fauna v4", 31 | "required": [ 32 | "fauna.adminSecretKey" 33 | ], 34 | "properties": { 35 | "fauna.adminSecretKey": { 36 | "type": "string", 37 | "markdownDescription": "The secret for a specific database." 38 | }, 39 | "fauna.domain": { 40 | "type": "string", 41 | "default": "db.fauna.com", 42 | "markdownDescription": "Optional - The Fauna domain for the database [Region Group](https://docs.fauna.com/fauna/current/learn/understanding/region_groups#how-to-use-region-groups) or `localhost` when using a local Fauna Dev Docker instance." 43 | }, 44 | "fauna.scheme": { 45 | "type": "string", 46 | "default": "https", 47 | "enum": [ 48 | "https", 49 | "http" 50 | ], 51 | "markdownDescription": "Optional - One of `https` or `http`. Only set to `http` when using a local Fauna Dev Docker instance." 52 | }, 53 | "fauna.port": { 54 | "type": "number", 55 | "default": 443, 56 | "markdownDescription": "Optional - The port number for the Fauna endpoint. When using a Fauna Dev Docker instance, use the port passed to the `docker run` command - usually `8443`." 57 | }, 58 | "fauna.graphqlHost": { 59 | "type": "string", 60 | "default": "https://graphql.fauna.com", 61 | "markdownDescription": "Optional - The full URL for the Fauna GraphQL API. The default is `https://graphql.fauna.com`. See [GraphQL Endpoints](https://docs.fauna.com/fauna/current/api/graphql/endpoints) for Region Group-specific URLs." 62 | } 63 | } 64 | }, 65 | "languages": [ 66 | { 67 | "id": "Log", 68 | "aliases": [ 69 | "log" 70 | ], 71 | "extensions": [ 72 | ".log" 73 | ], 74 | "configuration": "./log.configuration.json", 75 | "mimetypes": [ 76 | "log", 77 | "text/log", 78 | "text/x-log", 79 | "text/x-code-output", 80 | "x-code-output" 81 | ] 82 | }, 83 | { 84 | "id": "fql", 85 | "aliases": [ 86 | "Fauna Query Language" 87 | ], 88 | "configuration": "./fql.configuration.json", 89 | "filenamePatterns": [ 90 | "*.fql" 91 | ] 92 | } 93 | ], 94 | "grammars": [ 95 | { 96 | "language": "Log", 97 | "scopeName": "code.log", 98 | "path": "./syntaxes/log.tmLanguage" 99 | }, 100 | { 101 | "language": "fql", 102 | "scopeName": "source.fql", 103 | "path": "./syntaxes/fql.tmLanguage" 104 | } 105 | ], 106 | "commands": [ 107 | { 108 | "command": "fauna.createQuery", 109 | "title": "Fauna: Create Query" 110 | }, 111 | { 112 | "command": "fauna.uploadGraphQLSchema", 113 | "title": "Fauna: Upload GraphQL Schema" 114 | }, 115 | { 116 | "command": "fauna.mergeGraphQLSchema", 117 | "title": "Fauna: Merge GraphQL Schema" 118 | }, 119 | { 120 | "command": "fauna.overrideGraphQLSchema", 121 | "title": "Fauna: Override GraphQL Schema" 122 | }, 123 | { 124 | "command": "fauna.runQuery", 125 | "title": "Fauna: Run Query" 126 | }, 127 | { 128 | "command": "fauna.updateResource", 129 | "title": "Update Fauna Resource" 130 | }, 131 | { 132 | "command": "fauna.settings", 133 | "title": "Settings", 134 | "icon": { 135 | "light": "media/settings.svg", 136 | "dark": "media/settings.svg" 137 | } 138 | }, 139 | { 140 | "command": "fauna.delete", 141 | "title": "Delete", 142 | "icon": { 143 | "light": "media/delete.svg", 144 | "dark": "media/delete.svg" 145 | } 146 | }, 147 | { 148 | "command": "fauna.refreshEntry", 149 | "title": "Refresh", 150 | "icon": { 151 | "light": "media/refresh.svg", 152 | "dark": "media/refresh.svg" 153 | } 154 | }, 155 | { 156 | "command": "fauna.create", 157 | "title": "Create", 158 | "icon": { 159 | "light": "media/plus.svg", 160 | "dark": "media/plus.svg" 161 | } 162 | } 163 | ], 164 | "keybindings": [ 165 | { 166 | "command": "fauna.runQuery", 167 | "key": "ctrl+enter", 168 | "mac": "cmd+enter", 169 | "when": "editorTextFocus && editorLangId == fql" 170 | }, 171 | { 172 | "command": "fauna.updateResource", 173 | "key": "ctrl+s", 174 | "mac": "cmd+s", 175 | "when": "editorTextFocus && resourceDirname =~ /fauna-vscode-tmp/" 176 | } 177 | ], 178 | "viewsContainers": { 179 | "activitybar": [ 180 | { 181 | "id": "fauna-explorer", 182 | "title": "Fauna", 183 | "icon": "media/fauna-logo.svg" 184 | } 185 | ] 186 | }, 187 | "views": { 188 | "fauna-explorer": [ 189 | { 190 | "id": "fauna-databases", 191 | "name": "Databases" 192 | }, 193 | { 194 | "type": "webview", 195 | "id": "run-as", 196 | "name": "Run as" 197 | } 198 | ] 199 | }, 200 | "menus": { 201 | "view/title": [ 202 | { 203 | "command": "fauna.refreshEntry", 204 | "when": "view == fauna-databases", 205 | "group": "navigation" 206 | }, 207 | { 208 | "command": "fauna.create", 209 | "when": "view == fauna-databases", 210 | "group": "navigation" 211 | } 212 | ], 213 | "view/item/context": [ 214 | { 215 | "command": "fauna.settings", 216 | "when": "view == fauna-databases && viewItem != document", 217 | "group": "inline@1" 218 | }, 219 | { 220 | "command": "fauna.delete", 221 | "when": "view == fauna-databases && viewItem == document", 222 | "group": "inline@1" 223 | } 224 | ] 225 | } 226 | }, 227 | "scripts": { 228 | "vscode:prepublish": "yarn run compile", 229 | "compile": "tsc -p ./", 230 | "watch": "tsc -watch -p ./", 231 | "pretest": "yarn run compile", 232 | "test": "node ./out/test/runTest.js", 233 | "build": "yarn vsce package" 234 | }, 235 | "devDependencies": { 236 | "@types/chai": "^4.2.14", 237 | "@types/glob": "^7.1.1", 238 | "@types/js-beautify": "^1.13.2", 239 | "@types/mocha": "^5.2.7", 240 | "@types/node": "^12.11.7", 241 | "@types/sinon": "^9.0.10", 242 | "@types/vscode": "^1.40.0", 243 | "chai": "^4.2.0", 244 | "glob": "^7.1.5", 245 | "husky": "^4.3.6", 246 | "mocha": "^6.2.3", 247 | "pretty-quick": "^3.1.0", 248 | "sinon": "^9.2.4", 249 | "tslint": "^5.20.0", 250 | "typescript": "^3.6.4", 251 | "vscode-test": "^1.5.2" 252 | }, 253 | "dependencies": { 254 | "@types/highlight.js": "^9.12.3", 255 | "@types/node-fetch": "^2.5.7", 256 | "@types/prettier": "^2.1.6", 257 | "faunadb": "4.8.0", 258 | "highlight.js": "^10.4.1", 259 | "js-beautify": "^1.14.0", 260 | "node-fetch": "^2.6.0", 261 | "prettier": "^2.2.1", 262 | "vsce": "^1.73.0" 263 | }, 264 | "husky": { 265 | "hooks": { 266 | "pre-commit": "pretty-quick --staged" 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/CollectionSchemaItem.ts: -------------------------------------------------------------------------------- 1 | import { values } from 'faunadb'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import DBSchemaItem from './DBSchemaItem'; 5 | import { SchemaType } from './types'; 6 | 7 | export default class CollectionSchemaItem extends vscode.TreeItem { 8 | constructor( 9 | public readonly ref: values.Ref, 10 | public readonly parent?: DBSchemaItem 11 | ) { 12 | super(ref.id, vscode.TreeItemCollapsibleState.Collapsed); 13 | } 14 | 15 | iconPath = { 16 | light: path.join(__filename, '..', '..', 'media', 'window.svg'), 17 | dark: path.join(__filename, '..', '..', 'media', 'window.svg') 18 | }; 19 | 20 | contextValue = SchemaType.Collection; 21 | 22 | // constructor( 23 | // public readonly name: string, 24 | // public readonly itemPath?: string, 25 | // private client?: Client 26 | // ) { 27 | // super(name); 28 | // } 29 | 30 | // get tooltip(): string { 31 | // return `${this.name}`; 32 | // } 33 | 34 | // public displayInfo() { 35 | // if (!this.client) { 36 | // return Promise.resolve(null); 37 | // } 38 | 39 | // return this.client 40 | // .query(q.Get(q.Collection(this.name))) 41 | // .then(async (content: any) => { 42 | // let uri = vscode.Uri.parse( 43 | // `fqlcode:${this.name}#${Expr.toString(q.Object(content))}` 44 | // ); 45 | // let doc = await vscode.workspace.openTextDocument(uri); // calls back into the provider 46 | // await vscode.window.showTextDocument(doc, { preview: false }); 47 | // vscode.languages.setTextDocumentLanguage(doc, 'javascript'); 48 | // }) 49 | // .catch(error => console.error(error)); 50 | // } 51 | 52 | // command = { 53 | // command: 'fauna.get', 54 | // title: '', 55 | // arguments: [this] 56 | // }; 57 | 58 | // iconPath = { 59 | // light: path.join(__filename, '..', '..', 'media', 'window.svg'), 60 | // dark: path.join(__filename, '..', '..', 'media', 'window.svg') 61 | // }; 62 | 63 | // contextValue = SchemaType.Collection; 64 | } 65 | -------------------------------------------------------------------------------- /src/DBSchemaItem.ts: -------------------------------------------------------------------------------- 1 | import { values } from 'faunadb'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import { SchemaType } from './types'; 5 | 6 | export default class DBSchemaItem extends vscode.TreeItem { 7 | constructor( 8 | public readonly ref: values.Ref, 9 | public readonly parent?: DBSchemaItem 10 | ) { 11 | super(ref.id, vscode.TreeItemCollapsibleState.Collapsed); 12 | } 13 | 14 | get path(): string { 15 | let item: DBSchemaItem = this; 16 | let itemPath = item.ref.id; 17 | while (item.parent) { 18 | itemPath = item.parent.ref.id + '/' + itemPath; 19 | item = item.parent; 20 | } 21 | return itemPath; 22 | } 23 | 24 | iconPath = { 25 | light: path.join(__filename, '..', '..', 'media', 'database-solid.svg'), 26 | dark: path.join(__filename, '..', '..', 'media', 'database-solid.svg') 27 | }; 28 | 29 | contextValue = SchemaType.Database; 30 | } 31 | -------------------------------------------------------------------------------- /src/DocumentSchemaItem.ts: -------------------------------------------------------------------------------- 1 | import { Expr, query as q, values } from 'faunadb'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import CollectionSchemaItem from './CollectionSchemaItem'; 5 | import { SchemaType } from './types'; 6 | 7 | export default class DocumentSchemaItem extends vscode.TreeItem { 8 | constructor( 9 | public readonly ref: values.Ref, 10 | public readonly parent: CollectionSchemaItem 11 | ) { 12 | super(ref.id); 13 | } 14 | 15 | get content(): Expr { 16 | return q.Get(this.ref); 17 | } 18 | 19 | command = { 20 | command: 'fauna.open', 21 | title: '', 22 | arguments: [this] 23 | }; 24 | 25 | iconPath = { 26 | light: path.join(__filename, '..', '..', 'media', 'file.svg'), 27 | dark: path.join(__filename, '..', '..', 'media', 'file.svg') 28 | }; 29 | 30 | contextValue = SchemaType.Document; 31 | } 32 | -------------------------------------------------------------------------------- /src/FQLContentProvider.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | const prettier = require('prettier/standalone'); 3 | const plugins = [require('prettier/parser-meriyah')]; 4 | 5 | export default class FQLContentProvider 6 | implements vscode.TextDocumentContentProvider { 7 | onDidChangeEmitter = new vscode.EventEmitter(); 8 | onDidChange = this.onDidChangeEmitter.event; 9 | 10 | provideTextDocumentContent(uri: vscode.Uri): string { 11 | return prettier 12 | .format(`(${uri.fragment})`, { parser: 'meriyah', plugins }) 13 | .trim() 14 | .replace(/^(\({)/, '{') 15 | .replace(/(}\);$)/g, '}') 16 | .replace(';', ''); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/FaunaSchemaProvider.ts: -------------------------------------------------------------------------------- 1 | import { Expr, query as q, values } from 'faunadb'; 2 | import vscode from 'vscode'; 3 | import CollectionSchemaItem from './CollectionSchemaItem'; 4 | import DBSchemaItem from './DBSchemaItem'; 5 | import DocumentSchemaItem from './DocumentSchemaItem'; 6 | import FunctionSchemaItem from './FunctionSchemaItem'; 7 | import IndexSchemaItem from './IndexSchemaItem'; 8 | 9 | export interface SchemaItem extends vscode.TreeItem { 10 | readonly ref: values.Ref; 11 | readonly parent?: DBSchemaItem | CollectionSchemaItem; 12 | readonly content?: Expr; 13 | } 14 | 15 | export default class FaunaSchemaProvider 16 | implements vscode.TreeDataProvider { 17 | private _onDidChangeTreeData: vscode.EventEmitter< 18 | vscode.TreeItem | undefined 19 | > = new vscode.EventEmitter(); 20 | readonly onDidChangeTreeData: vscode.Event = this 21 | ._onDidChangeTreeData.event; 22 | 23 | refresh(): void { 24 | this._onDidChangeTreeData.fire(undefined); 25 | } 26 | 27 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem { 28 | return element; 29 | } 30 | 31 | getChildren( 32 | element?: 33 | | DBSchemaItem 34 | | CollectionSchemaItem 35 | | IndexSchemaItem 36 | | DocumentSchemaItem 37 | | FunctionSchemaItem 38 | ): Thenable { 39 | if (element instanceof DBSchemaItem || !element) { 40 | return Promise.all([ 41 | this.load({ 42 | parent: element, 43 | Resource: q.Databases, 44 | Item: DBSchemaItem 45 | }), 46 | this.load({ 47 | parent: element, 48 | Resource: q.Collections, 49 | Item: CollectionSchemaItem 50 | }), 51 | this.load({ 52 | parent: element, 53 | Resource: q.Indexes, 54 | Item: IndexSchemaItem 55 | }), 56 | this.load({ 57 | parent: element, 58 | Resource: q.Functions, 59 | Item: FunctionSchemaItem 60 | }) 61 | ]).then(([databases, collections, indexes, functions]) => [ 62 | ...databases, 63 | ...collections, 64 | ...indexes, 65 | ...functions 66 | ]); 67 | } 68 | 69 | if (element instanceof CollectionSchemaItem) { 70 | return this.loadDocuments(element); 71 | } 72 | 73 | throw new Error('No valid vscode.TreeItem'); 74 | } 75 | 76 | async query(expr: Expr, parent?: DBSchemaItem | CollectionSchemaItem) { 77 | return vscode.commands.executeCommand( 78 | 'fauna.query', 79 | expr, 80 | parent 81 | ) as Promise; 82 | } 83 | 84 | async load({ 85 | parent, 86 | Resource, 87 | Item 88 | }: { 89 | parent?: DBSchemaItem; 90 | Resource: (...props: any[]) => Expr; 91 | Item: any; 92 | }): Promise { 93 | const result = await this.query & { error?: any }>( 94 | q.Paginate(Resource()), 95 | parent 96 | ); 97 | 98 | if (result.error) { 99 | vscode.window.showErrorMessage( 100 | `Fetch ${Resource.name} failed: ${result.error.message}` 101 | ); 102 | return []; 103 | } 104 | 105 | return result.data ? result.data.map(ref => new Item(ref, parent)) : []; 106 | } 107 | 108 | async loadDocuments(parent: CollectionSchemaItem) { 109 | const result = await this.query>( 110 | q.Paginate(q.Documents(parent.ref)), 111 | parent.parent 112 | ); 113 | 114 | return result.data.map(ref => new DocumentSchemaItem(ref, parent)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/FunctionSchemaItem.ts: -------------------------------------------------------------------------------- 1 | import { Expr, query as q, values } from 'faunadb'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import DBSchemaItem from './DBSchemaItem'; 5 | import { SchemaType } from './types'; 6 | 7 | export default class FunctionSchemaItem extends vscode.TreeItem { 8 | constructor( 9 | public readonly ref: values.Ref, 10 | public readonly parent?: DBSchemaItem 11 | ) { 12 | super(ref.id); 13 | } 14 | 15 | get content(): Expr { 16 | return q.Get(this.ref); 17 | } 18 | 19 | command = { 20 | command: 'fauna.open', 21 | title: '', 22 | arguments: [this] 23 | }; 24 | 25 | iconPath = { 26 | light: path.join(__filename, '..', '..', 'media', 'code.svg'), 27 | dark: path.join(__filename, '..', '..', 'media', 'code.svg') 28 | }; 29 | 30 | contextValue = SchemaType.Function; 31 | } 32 | -------------------------------------------------------------------------------- /src/IndexSchemaItem.ts: -------------------------------------------------------------------------------- 1 | import { Expr, query as q, values } from 'faunadb'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | import DBSchemaItem from './DBSchemaItem'; 5 | import { SchemaType } from './types'; 6 | 7 | export default class IndexSchemaItem extends vscode.TreeItem { 8 | constructor( 9 | public readonly ref: values.Ref, 10 | public readonly parent?: DBSchemaItem 11 | ) { 12 | super(ref.id); 13 | } 14 | 15 | get content(): Expr { 16 | return q.Get(this.ref); 17 | } 18 | 19 | command = { 20 | command: 'fauna.open', 21 | title: '', 22 | arguments: [this] 23 | }; 24 | 25 | iconPath = { 26 | light: path.join(__filename, '..', '..', 'media', 'list.svg'), 27 | dark: path.join(__filename, '..', '..', 'media', 'list.svg') 28 | }; 29 | 30 | contextValue = SchemaType.Index; 31 | } 32 | -------------------------------------------------------------------------------- /src/RunAsWebviewProvider.ts: -------------------------------------------------------------------------------- 1 | import { Client, errors, query, values } from 'faunadb'; 2 | import * as vscode from 'vscode'; 3 | 4 | export default class RunAsWebviewProvider 5 | implements vscode.WebviewViewProvider { 6 | private _view?: vscode.WebviewView; 7 | public role: string | null = null; 8 | 9 | constructor( 10 | private readonly _extensionUri: vscode.Uri, 11 | private client: Client, 12 | private secret: string 13 | ) {} 14 | 15 | public getSecretWithRole() { 16 | return this.role ? [this.secret, this.role].join(':') : undefined; 17 | } 18 | 19 | public async resolveWebviewView( 20 | webviewView: vscode.WebviewView, 21 | context: vscode.WebviewViewResolveContext, 22 | _token: vscode.CancellationToken 23 | ) { 24 | this._view = webviewView; 25 | 26 | webviewView.webview.options = { 27 | // Allow scripts in the webview 28 | enableScripts: true, 29 | 30 | localResourceRoots: [this._extensionUri] 31 | }; 32 | 33 | const isAdmin = await this.isAdmin(); 34 | if (isAdmin) { 35 | webviewView.webview.html = await this.getHtmlForRunAs(); 36 | } else { 37 | webviewView.webview.html = this.getHtmlForNonAdmin(); 38 | } 39 | 40 | webviewView.webview.onDidReceiveMessage(data => { 41 | switch (data.type) { 42 | case 'roleChanged': 43 | this.role = data.role; 44 | break; 45 | case 'deactivateRunAs': 46 | this.role = null; 47 | break; 48 | case 'collectionChanged': 49 | this.role = [this.role, data.collection].join('/'); 50 | break; 51 | case 'idChanged': 52 | this.role = [this.role, data.id].join('/'); 53 | } 54 | }); 55 | } 56 | 57 | private async isAdmin() { 58 | return this.client 59 | .query(query.Now(), { secret: this.secret + ':admin' }) 60 | .then(() => true) 61 | .catch(err => false); 62 | } 63 | 64 | private getHtmlForNonAdmin() { 65 | return ` 66 |
67 | ${this.getHtmlForDesc()} 68 | Available only for secret with 'admin' role 69 |
70 | `; 71 | } 72 | 73 | private async getRoles() { 74 | const result = await vscode.commands.executeCommand<{ 75 | error?: errors.FaunaHTTPError; 76 | data?: values.Ref[]; 77 | }>('fauna.query', query.Paginate(query.Roles())); 78 | 79 | if (result?.error) { 80 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw); 81 | return; 82 | } 83 | 84 | return result?.data ?? []; 85 | } 86 | 87 | private async getHtmlForRunAs() { 88 | const scriptUri = this._view!.webview.asWebviewUri( 89 | vscode.Uri.joinPath(this._extensionUri, 'media', 'runAs.js') 90 | ); 91 | const nonce = getNonce(); 92 | const roles = await this.getRoles(); 93 | 94 | return ` 95 | 96 | 97 | 98 |
99 | ${this.getHtmlForDesc()} 100 |
101 | 102 | Select role 103 |
104 |
105 | 125 | 126 | 127 | `; 128 | } 129 | 130 | private getHtmlForDesc() { 131 | return ` 132 |
133 | Verify your ABAC 134 | configuration by running your query with an Admin or Server 135 | key, 136 | a specific Role, or a specific identity document. See 137 | Fauna Security for details. 138 |
139 | `; 140 | } 141 | } 142 | function getNonce() { 143 | let text = ''; 144 | const possible = 145 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 146 | for (let i = 0; i < 32; i++) { 147 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 148 | } 149 | return text; 150 | } 151 | -------------------------------------------------------------------------------- /src/SettingsWebView.ts: -------------------------------------------------------------------------------- 1 | import { errors, Expr, query as q, values } from 'faunadb'; 2 | import * as vscode from 'vscode'; 3 | import CollectionSchemaItem from './CollectionSchemaItem'; 4 | import DBSchemaItem from './DBSchemaItem'; 5 | import deleteResource from './deleteResource'; 6 | import { SchemaItem } from './FaunaSchemaProvider'; 7 | import { evalFQLCode } from './fql'; 8 | import { openFQLFile } from './openFQLFile'; 9 | import { SchemaType } from './types'; 10 | 11 | type Term = { field: string[] }; 12 | type Value = Term & { reverse?: boolean }; 13 | 14 | enum EventType { 15 | Close = 'close', 16 | Save = 'save', 17 | Delete = 'delete' 18 | } 19 | 20 | const Tooltip = { 21 | Collection: { 22 | Name: 23 | 'Cannot be events, sets, self, documents, or _. Please wait a few moments before using a recently deleted collection name. This required field can be changed later.', 24 | HistoryDays: 25 | 'Document history is retained for at least this many days. Clearing the field will retain history forever, but please note that this will increase storage utilization. This field defaults to 30 days, and can be changed later.', 26 | TTL: 27 | 'Documents are deleted this many days after their last write. Documents of the collection will be removed if they have not been updated within the configured TTL. This optional field can be changed later.' 28 | }, 29 | Index: { 30 | Source: 31 | 'The ability to add multiple source collections and field bindings is coming to the Dashboard soon. Meanwhile, you can do this via the Fauna Shell.', 32 | Name: 33 | 'Cannot be events, sets, self, documents, or _. Renaming an index changes its ref, but preserves inbound references to the index. Index data is not rebuilt. This required field can be changed later.', 34 | Terms: 35 | 'The terms field specifies which document fields can be searched. Leaving it blank means that specific documents cannot be searched for, but the index can be used to paginate over all documents in the source collection(s). This optional field may not be changed later. To adjust it, delete the current index and create a new one with the desired definition. Example: `data.email,data.phone`', 36 | Values: 37 | "The values field specifies which document fields to return for matching entries. Leaving it blank returns the indexed document's reference. This optional field may not be changed later. To adjust it, delete the current index and create a new one with the desired definition. Example: `reverse:data.createdAt, data.age`", 38 | Unique: 39 | 'If checked, maintains a uniqueness constraint on combined terms and values. Indexes with the unique constraint must also be serialized. Note: If you update the unique field, it will not remove existing duplicated items from the index.', 40 | Serialized: 41 | 'If checked, writes to this index are serialized with concurrent reads and writes.' 42 | } 43 | }; 44 | 45 | export class SettingsWebView { 46 | private panel: vscode.WebviewPanel; 47 | private resource?: any; 48 | private item?: Partial>; 49 | 50 | private renders = { 51 | [SchemaType.Database]: this.renderDatabase, 52 | [SchemaType.Collection]: this.renderCollection, 53 | [SchemaType.Index]: this.renderIndex, 54 | [SchemaType.Function]: this.renderFunction, 55 | [SchemaType.Document]: this.renderDocument 56 | }; 57 | 58 | private eventHandlers: Record void> = { 59 | [EventType.Close]: () => this.panel.dispose(), 60 | [EventType.Save]: data => this.save(data), 61 | [EventType.Delete]: () => this.delete() 62 | }; 63 | 64 | constructor( 65 | private extensionUri: vscode.Uri, 66 | private contextValue: SchemaType 67 | ) { 68 | const column = vscode.window.activeTextEditor 69 | ? vscode.window.activeTextEditor.viewColumn 70 | : undefined; 71 | 72 | this.panel = vscode.window.createWebviewPanel( 73 | 'createOrEdit', 74 | '', 75 | column || vscode.ViewColumn.One, 76 | { 77 | enableScripts: true, 78 | localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')] 79 | } 80 | ); 81 | 82 | this.panel.webview.onDidReceiveMessage( 83 | (data: { type: EventType; data: any }) => { 84 | this.eventHandlers[data.type](data.data); 85 | } 86 | ); 87 | } 88 | 89 | async forResource(item: SchemaItem) { 90 | const resource = await vscode.commands.executeCommand( 91 | 'fauna.query', 92 | q.Get(item.ref), 93 | item.parent 94 | ); 95 | this.item = item; 96 | this.resource = resource; 97 | return resource; 98 | } 99 | 100 | async setCreateForParent(parent: DBSchemaItem | CollectionSchemaItem) { 101 | this.item = { parent }; 102 | } 103 | 104 | async render() { 105 | const styles = this.panel.webview.asWebviewUri( 106 | vscode.Uri.joinPath(this.extensionUri, 'media', 'settingsWebView.css') 107 | ); 108 | 109 | const script = this.panel!.webview.asWebviewUri( 110 | vscode.Uri.joinPath(this.extensionUri, 'media', 'settingsWebView.js') 111 | ); 112 | 113 | this.panel.title = this.item?.ref 114 | ? `Settings ${this.item.ref.toString()}` 115 | : `Create ${this.contextValue}`; 116 | 117 | const body = await this.renders[this.contextValue].call(this); 118 | 119 | this.panel.webview.html = ` 120 | 121 | 122 | 123 | 124 | ${body} 125 | 126 | 127 | `; 128 | } 129 | 130 | private async getRoles() { 131 | const result = await vscode.commands.executeCommand<{ 132 | error?: errors.FaunaHTTPError; 133 | data?: values.Ref[]; 134 | }>('fauna.query', q.Paginate(q.Roles()), this.item?.parent); 135 | 136 | if (result?.error) { 137 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw); 138 | return; 139 | } 140 | 141 | return result?.data ?? []; 142 | } 143 | 144 | private async getCollections() { 145 | const result = await vscode.commands.executeCommand<{ 146 | error?: errors.FaunaHTTPError; 147 | data?: values.Ref[]; 148 | }>('fauna.query', q.Paginate(q.Collections()), this.item?.parent); 149 | 150 | if (result?.error) { 151 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw); 152 | return; 153 | } 154 | 155 | return result?.data ?? []; 156 | } 157 | 158 | private async save(data: any) { 159 | this.remapData(data); 160 | let result: 161 | | { error?: errors.FaunaHTTPError; ref?: values.Ref; body?: Expr } 162 | | undefined; 163 | if (this.item?.ref) { 164 | result = await vscode.commands.executeCommand( 165 | 'fauna.query', 166 | q.Update(this.item.ref, data), 167 | this.item.parent 168 | ); 169 | } else { 170 | const map: any = { 171 | [SchemaType.Database]: q.CreateDatabase, 172 | [SchemaType.Collection]: q.CreateCollection, 173 | [SchemaType.Index]: q.CreateIndex, 174 | [SchemaType.Function]: q.CreateFunction 175 | }; 176 | result = await vscode.commands.executeCommand( 177 | 'fauna.query', 178 | map[this.contextValue](data) 179 | ); 180 | } 181 | 182 | this.panel.webview.postMessage({ type: 'release_save' }); 183 | 184 | if (result!.error) { 185 | vscode.window.showErrorMessage(result!.error.requestResult.responseRaw); 186 | return; 187 | } 188 | 189 | if (!this.resource) { 190 | const item = { 191 | contextValue: this.contextValue, 192 | ref: result!.ref!, 193 | parent: this.item?.parent 194 | }; 195 | this.forResource(item).then(() => this.render()); 196 | 197 | if (this.contextValue === SchemaType.Function) { 198 | openFQLFile(Expr.toString(result!.body!), item); 199 | } 200 | } 201 | 202 | vscode.window.showInformationMessage( 203 | `${result?.ref} ${this.item?.ref ? 'updated' : 'created'}` 204 | ); 205 | vscode.commands.executeCommand('fauna.refreshEntry'); 206 | } 207 | 208 | private remapData(data: any) { 209 | if (data.body) { 210 | data.body = evalFQLCode(data.body); 211 | } 212 | 213 | if (data.source) { 214 | data.source = q.Collection(data.source); 215 | } 216 | 217 | if (data.role?.startsWith('@role/')) { 218 | data.role = q.Role(data.role.replace('@role/', '')); 219 | } 220 | 221 | if (data.role === '') { 222 | data.role = null; 223 | } 224 | 225 | if (data.values === '') { 226 | data.values = null; 227 | } else if (data.values) { 228 | data.values = data.values 229 | .replaceAll(' ', '') 230 | .split(',') 231 | .map((value: string) => { 232 | const data = value.split(':'); 233 | const reverse = data.length === 2; 234 | const field = 235 | data.length === 1 ? data[0].split('.') : data[1].split('.'); 236 | return { field, reverse }; 237 | }); 238 | } 239 | 240 | if (data.terms === '') { 241 | data.terms = null; 242 | } else if (data.terms) { 243 | data.terms = data.terms 244 | .replaceAll(' ', '') 245 | .split(',') 246 | .map((term: string) => ({ field: term.split('.') })); 247 | } 248 | 249 | return data; 250 | } 251 | 252 | delete() { 253 | if (!this.item) return; 254 | return deleteResource(this.item).then(success => { 255 | if (success) this.panel.dispose(); 256 | }); 257 | } 258 | 259 | private async renderCollection() { 260 | return ` 261 |
262 |
263 | 266 | 269 |
270 |
271 | 274 | 277 |
278 |
279 | 282 | 285 |
286 | ${this.renderControls()} 287 |
288 | `; 289 | } 290 | private async renderDatabase() { 291 | return ` 292 |
293 |
294 | 295 | 298 |
299 | Cannot contain spaces 300 |
301 |
302 | ${this.renderControls()} 303 |
304 | `; 305 | } 306 | 307 | private async renderIndex() { 308 | const collections = await this.getCollections(); 309 | const terms = this.resource 310 | ? this.resource.terms 311 | ? `
    ${this.resource.terms 312 | .map((term: Term) => `
  • ${term.field.join('.')}
  • `) 313 | .join('')}
` 314 | : 'Not set' 315 | : ''; 316 | 317 | const values = this.resource 318 | ? this.resource.values 319 | ? `
    ${this.resource.values 320 | .map( 321 | (value: Value) => 322 | `
  • ${value.reverse ? 'reverse' : ''} ${value.field.join( 323 | '.' 324 | )}
  • ` 325 | ) 326 | .join('')}
` 327 | : 'Not set (using ref by default)' 328 | : ` `; 329 | 330 | const source = this.resource 331 | ? `${this.resource.source.id}` 332 | : ` 333 | `; 342 | 343 | return ` 344 |
345 |
346 | 349 | ${source} 350 |
351 |
352 | 355 | 358 |
359 |
360 | 363 | ${terms} 364 |
365 |
366 | 369 | ${values} 370 |
371 |
372 | 379 |
380 |
381 | 392 |
393 | ${this.renderControls()} 394 |
395 | `; 396 | } 397 | 398 | private async renderFunction() { 399 | const roles = await this.getRoles(); 400 | 401 | return ` 402 |
403 | ${ 404 | this.resource 405 | ? '' 406 | : '' 407 | } 408 |
409 | 410 | 413 |
414 |
415 | 416 | 433 |
434 | ${this.renderControls()} 435 |
436 | `; 437 | } 438 | 439 | private async renderDocument() { 440 | return ''; 441 | } 442 | 443 | private renderTooltip(text: string) { 444 | return ` 445 |
446 | 447 | ${text} 448 |
449 | `; 450 | } 451 | 452 | private renderControls() { 453 | return ` 454 |
455 | 456 | 459 |
460 | ${ 461 | this.item?.ref 462 | ? ` 463 |
464 |
465 | 466 | DELETE 467 |
468 | ` 469 | : '' 470 | } 471 | `; 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import * as vscode from 'vscode'; 4 | 5 | export interface Config { 6 | secret: string; 7 | scheme?: 'http' | 'https'; 8 | domain?: string; 9 | port?: number; 10 | graphqlHost?: string; 11 | } 12 | 13 | export function loadConfig(): Config { 14 | const env = loadEnvironmentFile(); 15 | const config = vscode.workspace.getConfiguration('fauna'); 16 | 17 | const secret = env.FAUNA_KEY || config.get('adminSecretKey', ''); 18 | 19 | if (!secret) { 20 | throw new Error('Please provide secret key'); 21 | } 22 | 23 | const domain = env.FAUNA_DOMAIN || config.get('domain'); 24 | const scheme = env.FAUNA_SCHEME || config.get('scheme'); 25 | const port = env.FAUNA_PORT || config.get('port'); 26 | // should be explicitly set to a default value as this used to format endpoints and doesn't pass to faunadb-js driver 27 | const graphqlHost = 28 | env.FAUNA_GRAPHQL_HOST || 29 | config.get('graphqlHost') || 30 | 'https://graphql.fauna.com'; 31 | 32 | return { 33 | secret, 34 | ...(!!scheme && { scheme }), 35 | ...(domain && { domain }), 36 | ...(port && { port }), 37 | ...(graphqlHost && { graphqlHost }) 38 | }; 39 | } 40 | 41 | interface Env { 42 | FAUNA_KEY?: string; 43 | FAUNA_SCHEME?: 'http' | 'https'; 44 | FAUNA_DOMAIN?: string; 45 | FAUNA_PORT?: number; 46 | FAUNA_GRAPHQL_HOST?: string; 47 | } 48 | 49 | function loadEnvironmentFile() { 50 | let env: Env | undefined; 51 | 52 | const { workspaceFolders } = vscode.workspace; 53 | if (workspaceFolders) { 54 | workspaceFolders.find(workspace => { 55 | if (workspace.uri.scheme === 'file') { 56 | const envPath = path.join(workspace.uri.fsPath, '.faunarc'); 57 | try { 58 | env = parseEnvironmentFile(fs.readFileSync(envPath, 'utf8')); 59 | return true; 60 | } catch (e) { 61 | if (e.code !== 'ENOENT') { 62 | console.error(`Error parsing ${envPath}\n`, e); 63 | } 64 | } 65 | } 66 | }); 67 | } 68 | 69 | return env || {}; 70 | } 71 | 72 | function parseEnvironmentFile(src: string) { 73 | const result: { [key: string]: any } = {}; 74 | const lines = src.toString().split('\n'); 75 | for (const line of lines) { 76 | const match = line.match(/^([^=:#]+?)[=:](.*)/); 77 | if (match) { 78 | const key = match[1].trim(); 79 | const value = match[2].trim(); 80 | result[key] = value; 81 | } 82 | } 83 | return result; 84 | } 85 | -------------------------------------------------------------------------------- /src/deleteResource.ts: -------------------------------------------------------------------------------- 1 | import { errors, query as q } from 'faunadb'; 2 | import * as vscode from 'vscode'; 3 | import { SchemaItem } from './FaunaSchemaProvider'; 4 | 5 | export default async (item: Partial>) => { 6 | if (!item.ref) return; 7 | const confirm = await vscode.window.showInformationMessage( 8 | `Would you like to delete ${item.ref}?`, 9 | ...['Yes', 'No'] 10 | ); 11 | if (confirm === 'Yes') { 12 | const resp = await vscode.commands.executeCommand<{ 13 | error: errors.FaunaHTTPError; 14 | }>('fauna.query', q.Delete(item.ref), item.parent); 15 | 16 | if (resp?.error) { 17 | vscode.window.showErrorMessage(resp.error.requestResult.responseRaw); 18 | } else { 19 | vscode.window.showInformationMessage(`${item!.ref} deleted`); 20 | vscode.commands.executeCommand('fauna.refreshEntry'); 21 | return true; 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { Client, errors, Expr, query as q, values } from 'faunadb'; 2 | import * as vscode from 'vscode'; 3 | import CollectionSchemaItem from './CollectionSchemaItem'; 4 | import { loadConfig } from './config'; 5 | import DBSchemaItem from './DBSchemaItem'; 6 | import deleteResource from './deleteResource'; 7 | import DocumentSchemaItem from './DocumentSchemaItem'; 8 | import FaunaSchemaProvider, { SchemaItem } from './FaunaSchemaProvider'; 9 | import FQLContentProvider from './FQLContentProvider'; 10 | import { openFQLFile, openJSONFile } from './openFQLFile'; 11 | import RunAsWebviewProvider from './RunAsWebviewProvider'; 12 | import createRunQueryCommand from './runQueryCommand'; 13 | import { SettingsWebView } from './SettingsWebView'; 14 | import { SchemaType } from './types'; 15 | import updateResourceCommand from './updateResourceCommand'; 16 | import uploadGraphqlSchemaCommand from './uploadGraphqlSchemaCommand'; 17 | 18 | const config = loadConfig(); 19 | const client = new Client({ 20 | secret: config.secret, 21 | domain: config.domain, 22 | scheme: config.scheme, 23 | port: config.port, 24 | headers: { 25 | 'X-Fauna-Source': 'VSCode' 26 | } 27 | }); 28 | 29 | export function activate(context: vscode.ExtensionContext) { 30 | // Set output channel to display FQL results 31 | const outputChannel = vscode.window.createOutputChannel('FQL'); 32 | 33 | // Set FQL Document Content Provider 34 | context.subscriptions.push( 35 | vscode.workspace.registerTextDocumentContentProvider( 36 | 'fqlcode', 37 | new FQLContentProvider() 38 | ) 39 | ); 40 | 41 | // Registered commands and providers that depend on configuration 42 | let registered: vscode.Disposable[] = []; 43 | 44 | register(); 45 | 46 | // Reload the extension when reconfigured 47 | const watcher = vscode.workspace.createFileSystemWatcher('./.faunarc'); 48 | context.subscriptions.push( 49 | vscode.workspace.onDidChangeConfiguration( 50 | event => event.affectsConfiguration('fauna') && register() 51 | ), 52 | watcher, 53 | watcher.onDidChange(register), 54 | watcher.onDidCreate(register), 55 | watcher.onDidDelete(register) 56 | ); 57 | 58 | async function register() { 59 | // Set Schema Provider to display items on sidebar 60 | const faunaSchemaProvider = new FaunaSchemaProvider(); 61 | const runAsProvider = new RunAsWebviewProvider( 62 | context.extensionUri, 63 | client, 64 | config.secret 65 | ); 66 | 67 | vscode.window.registerWebviewViewProvider('run-as', runAsProvider); 68 | vscode.window.registerTreeDataProvider( 69 | 'fauna-databases', 70 | faunaSchemaProvider 71 | ); 72 | 73 | const mountSecret = (scope?: DBSchemaItem | CollectionSchemaItem) => { 74 | const database = 75 | scope instanceof CollectionSchemaItem ? scope.parent : scope; 76 | const secret = config.secret + (database ? ':' + database.path : ''); 77 | return config.secret + (database ? ':' + database.path : '') + ':admin'; 78 | }; 79 | 80 | registered.forEach(reg => reg.dispose()); 81 | 82 | registered = [ 83 | vscode.commands.registerCommand( 84 | 'fauna.runQuery', 85 | createRunQueryCommand(client, outputChannel, runAsProvider) 86 | ), 87 | vscode.commands.registerCommand( 88 | 'fauna.updateResource', 89 | updateResourceCommand(client) 90 | ), 91 | vscode.commands.registerCommand('fauna.createQuery', () => 92 | openFQLFile('Paginate(Collections())') 93 | ), 94 | vscode.commands.registerCommand( 95 | 'fauna.uploadGraphQLSchema', 96 | uploadGraphqlSchemaCommand('merge', config, outputChannel) 97 | ), 98 | vscode.commands.registerCommand( 99 | 'fauna.mergeGraphQLSchema', 100 | uploadGraphqlSchemaCommand('merge', config, outputChannel) 101 | ), 102 | vscode.commands.registerCommand( 103 | 'fauna.overrideGraphQLSchema', 104 | uploadGraphqlSchemaCommand('override', config, outputChannel) 105 | ), 106 | vscode.commands.registerCommand( 107 | 'fauna.query', 108 | (expr: Expr, scope?: DBSchemaItem | CollectionSchemaItem) => 109 | client 110 | .query(expr, { 111 | secret: mountSecret(scope) 112 | }) 113 | .catch((error: errors.FaunaHTTPError) => ({ error })) 114 | ), 115 | vscode.commands.registerCommand('fauna.open', (item: SchemaItem) => { 116 | client 117 | .query(item.content!, { 118 | secret: mountSecret(item.parent) 119 | }) 120 | .then(async resp => { 121 | if (item.contextValue === SchemaType.Function) { 122 | openFQLFile(Expr.toString(resp.body), item); 123 | return; 124 | } 125 | 126 | if (item.contextValue === SchemaType.Document) { 127 | openJSONFile(JSON.stringify(resp.data, null, '\t'), item); 128 | return; 129 | } 130 | 131 | if (item.contextValue === SchemaType.Index) { 132 | openJSONFile(Expr.toString(q.Object(resp)), item); 133 | return; 134 | } 135 | }) 136 | .catch(err => { 137 | console.error(err); 138 | }); 139 | }), 140 | vscode.commands.registerCommand('fauna.refreshEntry', () => 141 | faunaSchemaProvider.refresh() 142 | ), 143 | 144 | vscode.commands.registerCommand('fauna.create', async () => { 145 | const pick = await vscode.window.showQuickPick( 146 | Object.values(SchemaType) 147 | ); 148 | if (!pick) return; 149 | 150 | if (pick === SchemaType.Document) { 151 | const response = await vscode.commands.executeCommand<{ 152 | collections: values.Ref[]; 153 | newId: string; 154 | }>( 155 | 'fauna.query', 156 | q.Let( 157 | {}, 158 | { 159 | newId: q.NewId(), 160 | collections: q.Select(['data'], q.Paginate(q.Collections())) 161 | } 162 | ) 163 | ); 164 | 165 | if (!response?.collections?.length) { 166 | vscode.window.showErrorMessage( 167 | 'You need to create at least one collection' 168 | ); 169 | return; 170 | } 171 | 172 | const collection = await vscode.window.showQuickPick( 173 | response.collections.map(c => c.id) 174 | ); 175 | if (!collection) return; 176 | 177 | const docItem = new DocumentSchemaItem( 178 | new values.Ref('newDoc'), 179 | new CollectionSchemaItem(new values.Ref(collection)) 180 | ); 181 | 182 | openJSONFile('{}', docItem); 183 | return; 184 | } 185 | 186 | const view = new SettingsWebView( 187 | context.extensionUri, 188 | pick as SchemaType 189 | ); 190 | view.render(); 191 | }), 192 | 193 | vscode.commands.registerCommand('fauna.settings', faunaRes => { 194 | const view = new SettingsWebView( 195 | context.extensionUri, 196 | faunaRes.contextValue 197 | ); 198 | view.forResource(faunaRes).then(() => view.render()); 199 | }), 200 | 201 | vscode.commands.registerCommand('fauna.delete', deleteResource) 202 | ]; 203 | } 204 | } 205 | 206 | // this method is called when your extension is deactivated 207 | export function deactivate() {} 208 | -------------------------------------------------------------------------------- /src/fql.ts: -------------------------------------------------------------------------------- 1 | import { Client, query } from 'faunadb'; 2 | import { renderSpecialType } from './specialTypes'; 3 | const prettier = require('prettier/standalone'); 4 | const plugins = [require('prettier/parser-meriyah')]; 5 | 6 | export class InvalidFQL extends Error {} 7 | 8 | export function evalFQLCode(code: string) { 9 | return baseEvalFQL(code, query); 10 | } 11 | 12 | function baseEvalFQL(fql: string, q: typeof query) { 13 | const { 14 | Ref, 15 | Bytes, 16 | Abort, 17 | At, 18 | Let, 19 | Var, 20 | If, 21 | Do, 22 | Object, 23 | Lambda, 24 | Call, 25 | Query, 26 | Map, 27 | Foreach, 28 | Filter, 29 | Take, 30 | Drop, 31 | Prepend, 32 | Append, 33 | IsEmpty, 34 | IsNonEmpty, 35 | Get, 36 | KeyFromSecret, 37 | Paginate, 38 | Exists, 39 | Create, 40 | Update, 41 | Replace, 42 | Delete, 43 | Insert, 44 | Remove, 45 | CreateClass, 46 | CreateCollection, 47 | CreateDatabase, 48 | CreateIndex, 49 | CreateKey, 50 | CreateFunction, 51 | CreateRole, 52 | Singleton, 53 | Events, 54 | Match, 55 | Union, 56 | Intersection, 57 | Difference, 58 | Distinct, 59 | Join, 60 | Login, 61 | Logout, 62 | Identify, 63 | Identity, 64 | HasIdentity, 65 | Concat, 66 | Casefold, 67 | FindStr, 68 | FindStrRegex, 69 | Length, 70 | LowerCase, 71 | LTrim, 72 | NGram, 73 | Repeat, 74 | ReplaceStr, 75 | ReplaceStrRegex, 76 | RTrim, 77 | Space, 78 | SubString, 79 | TitleCase, 80 | Trim, 81 | UpperCase, 82 | Time, 83 | Epoch, 84 | Date, 85 | NextId, 86 | NewId, 87 | Database, 88 | Index, 89 | Class, 90 | Collection, 91 | Function, 92 | Role, 93 | Classes, 94 | Collections, 95 | Databases, 96 | Indexes, 97 | Functions, 98 | Roles, 99 | Keys, 100 | Tokens, 101 | Credentials, 102 | Equals, 103 | Contains, 104 | Select, 105 | SelectAll, 106 | Abs, 107 | Add, 108 | BitAnd, 109 | BitNot, 110 | BitOr, 111 | BitXor, 112 | Ceil, 113 | Divide, 114 | Floor, 115 | Max, 116 | Min, 117 | Modulo, 118 | Multiply, 119 | Round, 120 | Subtract, 121 | Sign, 122 | Sqrt, 123 | Trunc, 124 | Acos, 125 | Asin, 126 | Atan, 127 | Cos, 128 | Cosh, 129 | Degrees, 130 | Exp, 131 | Hypot, 132 | Ln, 133 | Log, 134 | Pow, 135 | Radians, 136 | Sin, 137 | Sinh, 138 | Tan, 139 | Tanh, 140 | LT, 141 | LTE, 142 | GT, 143 | GTE, 144 | And, 145 | Or, 146 | Not, 147 | ToString, 148 | ToNumber, 149 | ToTime, 150 | ToSeconds, 151 | ToMicros, 152 | ToMillis, 153 | DayOfMonth, 154 | DayOfWeek, 155 | DayOfYear, 156 | Second, 157 | Minute, 158 | Hour, 159 | Month, 160 | Year, 161 | ToDate, 162 | Format, 163 | Merge, 164 | Range, 165 | Reduce, 166 | MoveDatabase, 167 | Count, 168 | Mean, 169 | Sum, 170 | StartsWith, 171 | EndsWith, 172 | ContainsStr, 173 | ContainsStrRegex, 174 | RegexEscape, 175 | Now, 176 | ToDouble, 177 | ToInteger, 178 | ToObject, 179 | ToArray, 180 | Any, 181 | All, 182 | TimeAdd, 183 | TimeSubtract, 184 | TimeDiff, 185 | IsNumber, 186 | IsDouble, 187 | IsInteger, 188 | IsBoolean, 189 | IsNull, 190 | IsBytes, 191 | IsTimestamp, 192 | IsDate, 193 | IsString, 194 | IsArray, 195 | IsObject, 196 | IsRef, 197 | IsSet, 198 | IsDoc, 199 | IsLambda, 200 | IsCollection, 201 | IsDatabase, 202 | IsIndex, 203 | IsFunction, 204 | IsKey, 205 | IsToken, 206 | IsCredentials, 207 | IsRole, 208 | Documents, 209 | Reverse, 210 | ContainsPath, 211 | ContainsField, 212 | ContainsValue, 213 | CreateAccessProvider, 214 | AccessProvider, 215 | AccessProviders, 216 | CurrentIdentity, 217 | HasCurrentIdentity, 218 | CurrentToken, 219 | HasCurrentToken 220 | } = q; 221 | 222 | // eslint-disable-next-line 223 | return fql.match(/^\s*{(.*\n*)*}\s*$/) ? eval(`(${fql})`) : eval(fql); 224 | } 225 | 226 | export function parseQueries(code: string): string[] { 227 | const brackets: Record = { 228 | '{': '}', 229 | '(': ')', 230 | '[': ']', 231 | '"': '"', 232 | "'": "'" 233 | }; 234 | const openBrackets = new Set(Object.keys(brackets)); 235 | const closeBrackets = new Set(Object.values(brackets)); 236 | const queries = []; 237 | const stack = []; 238 | let start = 0; 239 | let isOpening; 240 | code = code.trim().split('\n').join('').split('\r').join(''); 241 | 242 | for (let i = 0; i < code.length; i++) { 243 | if (openBrackets.has(code[i])) { 244 | stack.push(code[i]); 245 | isOpening = true; 246 | } 247 | 248 | if (closeBrackets.has(code[i]) && brackets[stack.pop()!] !== code[i]) { 249 | throw new InvalidFQL( 250 | `Unexpected closing bracket ${code[i]} at position: ${i + 1}` 251 | ); 252 | } 253 | 254 | if (stack.length === 0 && isOpening) { 255 | const line = code.slice(start, i + 1); 256 | if (!line.startsWith('//')) { 257 | queries.push(line); 258 | } 259 | start = i + 1; 260 | isOpening = false; 261 | } 262 | } 263 | 264 | if (isOpening) { 265 | throw new InvalidFQL('Expect all opened brackets to be closed'); 266 | } 267 | 268 | return queries; 269 | } 270 | 271 | export function runFQLQuery(code: string, client: Client, secret?: string) { 272 | const queriesArray = parseQueries(code); 273 | if (queriesArray.length === 0) { 274 | throw new InvalidFQL('No queries found'); 275 | } 276 | 277 | const wrappedQueries = queriesArray.map(query => { 278 | return client.query(evalFQLCode(query), { 279 | ...(secret && { secret }) 280 | }); 281 | }); 282 | 283 | return Promise.all(wrappedQueries); 284 | } 285 | 286 | export function stringify(obj: object) { 287 | const replacements: string[] = []; 288 | 289 | let string = JSON.stringify( 290 | obj, 291 | (key, value) => { 292 | const parsed = renderSpecialType(value); 293 | 294 | if (parsed) { 295 | const placeHolder = '$$dash_replacement_$' + replacements.length + '$$'; 296 | replacements.push(parsed); 297 | return placeHolder; 298 | } 299 | 300 | return value; 301 | }, 302 | 2 303 | ); 304 | 305 | replacements.forEach((replace, index) => { 306 | string = string.replace('"$$dash_replacement_$' + index + '$$"', replace); 307 | }); 308 | 309 | if (string) { 310 | string = string.replace(/\(null\)/g, '()'); 311 | } 312 | 313 | return string; 314 | } 315 | 316 | export function formatFQLCode(code: object | string): string { 317 | if (typeof code === 'object') { 318 | code = stringify(code); 319 | } 320 | 321 | try { 322 | return prettier 323 | .format(`(${code})`, { parser: 'meriyah', plugins }) 324 | .trim() 325 | .replace(/^(\({)/, '{') 326 | .replace(/(}\);$)/g, '}') 327 | .replace(';', ''); 328 | } catch (error) { 329 | return code; 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'faunadb/src/_json' { 2 | export function toJSON(value: object): string; 3 | export function parseJSON(value: string): object; 4 | } 5 | 6 | declare module 'highlight.js/lib/languages/javascript' { 7 | export = Object; 8 | } 9 | -------------------------------------------------------------------------------- /src/openFQLFile.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as os from 'os'; 3 | import * as Path from 'path'; 4 | import vscode from 'vscode'; 5 | import { SchemaItem } from './FaunaSchemaProvider'; 6 | import { formatFQLCode } from './fql'; 7 | 8 | const resolvePath = (filepath: string): string => { 9 | if (filepath[0] === '~') { 10 | const hoveVar = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'; 11 | return Path.join( 12 | process.env[hoveVar] ?? '', 13 | filepath.slice(1), 14 | 'fauna-vscode-tmp' 15 | ); 16 | } else { 17 | return Path.resolve(Path.join(filepath, 'fauna-vscode-tmp')); 18 | } 19 | }; 20 | 21 | const tempdir = resolvePath( 22 | vscode.workspace.getConfiguration('createtmpfile').get('tmpDir') || 23 | os.tmpdir() 24 | ); 25 | 26 | if (!fs.existsSync(tempdir)) { 27 | fs.mkdirSync(tempdir); 28 | } 29 | 30 | export async function openJSONFile(content: string, item: SchemaItem) { 31 | try { 32 | const filePath = await saveTmpFile({ 33 | item, 34 | content: formatFQLCode(content), 35 | ext: 'js' 36 | }); 37 | const doc = await vscode.workspace.openTextDocument(filePath); 38 | vscode.window.showTextDocument(doc); 39 | } catch (err) { 40 | vscode.window.showErrorMessage(err.message); 41 | } 42 | } 43 | 44 | export async function openFQLFile(content: string, item?: SchemaItem) { 45 | try { 46 | let doc; 47 | if (item) { 48 | const filePath = await saveTmpFile({ 49 | item, 50 | ext: 'fql', 51 | content: formatFQLCode(content) 52 | }); 53 | doc = await vscode.workspace.openTextDocument(filePath); 54 | } else { 55 | doc = await vscode.workspace.openTextDocument({ 56 | language: 'fql', 57 | content: 'Paginate(Collections())' 58 | }); 59 | } 60 | vscode.window.showTextDocument(doc); 61 | } catch (err) { 62 | vscode.window.showErrorMessage(err.message); 63 | } 64 | } 65 | 66 | function saveTmpFile({ 67 | item, 68 | ext, 69 | content 70 | }: { 71 | item: SchemaItem; 72 | ext: string; 73 | content: string; 74 | }): Promise { 75 | const itemName = [[item.contextValue, item.ref.id].join('#'), ext].join('.'); 76 | let name; 77 | if (item.parent) { 78 | let parent: SchemaItem | undefined = item.parent; 79 | const paths = []; 80 | while (parent) { 81 | paths.unshift([parent.contextValue, parent.ref.id].join('#')); 82 | parent = parent.parent; 83 | } 84 | name = [...paths, itemName].join('.'); 85 | } else { 86 | name = itemName; 87 | } 88 | 89 | const filePath = `${tempdir}${Path.sep}${name}`; 90 | return fs.promises.writeFile(filePath, content).then(() => filePath); 91 | } 92 | -------------------------------------------------------------------------------- /src/runQueryCommand.ts: -------------------------------------------------------------------------------- 1 | import { Client, errors } from 'faunadb'; 2 | import vscode from 'vscode'; 3 | import { formatFQLCode, runFQLQuery } from './fql'; 4 | import RunAsWebviewProvider from './RunAsWebviewProvider'; 5 | 6 | export default ( 7 | client: Client, 8 | outputChannel: vscode.OutputChannel, 9 | runAsProvider: RunAsWebviewProvider 10 | ) => async () => { 11 | const { activeTextEditor } = vscode.window; 12 | 13 | if (!activeTextEditor || activeTextEditor.document.languageId !== 'fql') { 14 | vscode.window.showWarningMessage( 15 | 'You have to select a FQL document to run a FQL query.' 16 | ); 17 | return; 18 | } 19 | 20 | const selection = activeTextEditor.selection; 21 | const selectedText = activeTextEditor.document.getText(selection); 22 | const fqlExpression = 23 | selectedText.length > 0 24 | ? selectedText 25 | : activeTextEditor.document.getText(); 26 | if (fqlExpression.length < 1) { 27 | vscode.window.showWarningMessage( 28 | 'Selected file or selected text must have a FQL query to run' 29 | ); 30 | 31 | return; 32 | } 33 | 34 | const runAs = runAsProvider.role ? `(as ${runAsProvider.role})` : ''; 35 | outputChannel.appendLine(''); 36 | outputChannel.appendLine(`RUNNING ${runAs}: ${fqlExpression}`); 37 | outputChannel.show(); 38 | 39 | try { 40 | const result = await runFQLQuery( 41 | fqlExpression, 42 | client, 43 | runAsProvider.getSecretWithRole() 44 | ); 45 | const formattedCode = formatFQLCode(result); 46 | outputChannel.appendLine(formattedCode); 47 | } catch (error) { 48 | let message = error.message; 49 | 50 | //@ts-ignore 51 | if (error instanceof errors.FaunaHTTPError) { 52 | message = JSON.stringify(error.errors(), null, 2); 53 | } 54 | 55 | outputChannel.appendLine('ERROR:'); 56 | outputChannel.appendLine(message); 57 | } 58 | }; 59 | 60 | function truncate(text: string, n: number) { 61 | return text.length > n ? text.substr(0, n - 1) + '...' : text; 62 | } 63 | -------------------------------------------------------------------------------- /src/specialTypes.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { values as v, Expr } from 'faunadb'; 3 | 4 | const ctors = { 5 | classes: 'Class', 6 | collections: 'Collection', 7 | indexes: 'Index', 8 | databases: 'Database', 9 | keys: 'Key', 10 | roles: 'Role' 11 | }; 12 | 13 | const parseRef = obj => { 14 | if (obj === undefined) { 15 | return obj; 16 | } else if (obj instanceof v.Ref) { 17 | return obj; 18 | } else { 19 | const ref = '@ref' in obj ? obj['@ref'] : obj; 20 | return new v.Ref(ref.id, parseRef(ref.collection), parseRef(ref.database)); 21 | } 22 | }; 23 | 24 | const renderRef = obj => { 25 | let args = [`"${obj.id}"`]; 26 | 27 | if (obj.collection !== undefined) { 28 | const ctor = ctors[obj.collection.id]; 29 | if (ctor !== undefined) { 30 | if (obj.database !== undefined) args.push(renderRef(obj.database)); 31 | args = args.join(', '); 32 | return `${ctor}(${args})`; 33 | } 34 | } 35 | 36 | if (obj.collection !== undefined) 37 | args = [renderRef(obj.collection)].concat(args); 38 | args = args.join(', '); 39 | return `Ref(${args})`; 40 | }; 41 | 42 | export const renderSpecialType = type => { 43 | if (!type) return null; 44 | 45 | if (type instanceof v.Value) { 46 | if (type instanceof v.Ref) return renderRef(type); 47 | if (type instanceof v.FaunaTime) return `Time("${type.value}")`; 48 | if (type instanceof v.FaunaDate) return `Date("${type.value}")`; 49 | if (type instanceof v.Query) return `Query(${Expr.toString(type.value)})`; 50 | return null; 51 | } 52 | 53 | if (typeof type === 'object' && !Array.isArray(type)) { 54 | const keys = Object.keys(type); 55 | 56 | switch (keys[0]) { 57 | case '@ref': 58 | return renderRef(parseRef(type)); 59 | case '@ts': 60 | return renderSpecialType(new v.FaunaTime(type['@ts'])); 61 | case '@date': 62 | return renderSpecialType(new v.FaunaDate(type['@date'])); 63 | case '@code': 64 | return type['@code']; 65 | case '@query': 66 | return renderSpecialType(new v.Query(type['@query'])); 67 | default: 68 | return null; 69 | } 70 | } 71 | 72 | return null; 73 | }; 74 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/fql.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { Client } from 'faunadb'; 3 | import { afterEach, before } from 'mocha'; 4 | import sinon from 'sinon'; 5 | import { evalFQLCode, InvalidFQL, runFQLQuery } from '../../fql'; 6 | 7 | suite('FQL', () => { 8 | suite('runFQLQuery', () => { 9 | let client: Client; 10 | const queryFake = sinon.fake.resolves({}); 11 | before(() => { 12 | client = new Client({ secret: '' }); 13 | client.query = queryFake; 14 | }); 15 | 16 | afterEach(() => { 17 | queryFake.resetHistory(); 18 | }); 19 | 20 | test('throw an error for empty query', () => { 21 | assert.throws( 22 | () => runFQLQuery('', client as Client), 23 | InvalidFQL, 24 | 'No queries found' 25 | ); 26 | }); 27 | 28 | test('throw an error for not closed bracket', () => { 29 | assert.throws( 30 | () => runFQLQuery('Paginate(Indexes()', client), 31 | InvalidFQL, 32 | `Expect all opened brackets to be closed` 33 | ); 34 | }); 35 | 36 | test('throw an error for invalid closed bracket', () => { 37 | assert.throws( 38 | () => runFQLQuery('Paginate(Indexes(})', client), 39 | InvalidFQL, 40 | 'Unexpected closing bracket } at position: 18' 41 | ); 42 | }); 43 | 44 | test('run one query in one line', () => { 45 | const fql = 'Paginate(Indexes())'; 46 | runFQLQuery(fql, client); 47 | assert.deepEqual(queryFake.firstCall.firstArg, evalFQLCode(fql)); 48 | }); 49 | 50 | test('run multiple queries in one line', () => { 51 | const fql1 = 'Paginate(Indexes())'; 52 | const fql2 = 'Paginate(Collections())'; 53 | runFQLQuery([fql1, fql2].join(' '), client); 54 | assert.deepEqual(queryFake.getCall(0).firstArg, evalFQLCode(fql1)); 55 | assert.deepEqual(queryFake.getCall(1).firstArg, evalFQLCode(fql2)); 56 | }); 57 | 58 | test('run one query in multi-lines', () => { 59 | const fql = ` 60 | Paginate( 61 | Indexes() 62 | ) 63 | `; 64 | runFQLQuery(fql, client); 65 | assert.deepEqual(queryFake.firstCall.firstArg, evalFQLCode(fql)); 66 | }); 67 | 68 | test('run multiple queries in multi-lines', () => { 69 | const fql = ` 70 | Paginate( 71 | Indexes() 72 | ) 73 | Paginate( 74 | Collections() 75 | ) 76 | `; 77 | runFQLQuery(fql, client); 78 | console.info(queryFake.getCall(0).firstArg); 79 | assert.deepEqual( 80 | queryFake.getCall(0).firstArg, 81 | evalFQLCode('Paginate(Indexes())') 82 | ); 83 | assert.deepEqual( 84 | queryFake.getCall(1).firstArg, 85 | evalFQLCode('Paginate(Collections())') 86 | ); 87 | }); 88 | 89 | test('run multiline queries with comments', () => { 90 | const fql = ` 91 | Paginate(Indexes()) 92 | // Paginate(Collections()) 93 | `; 94 | runFQLQuery(fql, client); 95 | assert.deepEqual( 96 | queryFake.getCall(0).firstArg, 97 | evalFQLCode('Paginate(Indexes())') 98 | ); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import Mocha from 'mocha'; 3 | import glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd' 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export enum SchemaType { 2 | Database = 'database', 3 | Collection = 'collection', 4 | Index = 'index', 5 | Function = 'function', 6 | Document = 'document' 7 | } 8 | -------------------------------------------------------------------------------- /src/updateResourceCommand.ts: -------------------------------------------------------------------------------- 1 | import { Client, Expr, query as q, values } from 'faunadb'; 2 | import * as fs from 'fs'; 3 | import * as vscode from 'vscode'; 4 | import { loadConfig } from './config'; 5 | import { runFQLQuery } from './fql'; 6 | 7 | const config = loadConfig(); 8 | 9 | const Ref: Record< 10 | string, 11 | { 12 | ref: (name: string, parent?: Expr) => Expr; 13 | payload: (data: string) => string; 14 | } 15 | > = { 16 | function: { 17 | ref: (name: string) => q.Function(name), 18 | payload: (data: string) => `{body: ${data} }` 19 | }, 20 | document: { 21 | ref: (name: string, parent?: Expr) => q.Ref(parent!, name), 22 | payload: (data: string) => `{ data: ${data} }` 23 | }, 24 | collection: { 25 | ref: (name: string) => q.Collection(name), 26 | payload: () => '' 27 | } 28 | }; 29 | 30 | export default (client: Client) => async () => { 31 | const { activeTextEditor } = vscode.window; 32 | if (!activeTextEditor) return; 33 | 34 | const match = activeTextEditor.document.fileName.match( 35 | /fauna-vscode-tmp\/(.*)\.[fql|json]/ 36 | ); 37 | if (!match) return; 38 | 39 | const parts = match[1].split('.').map(p => p.split('#')); 40 | 41 | let ref: Expr; 42 | let parentRef; 43 | const [itemType, itemId] = parts[parts.length - 1]; 44 | 45 | if (parts.length > 1) { 46 | const [parentType, parentId] = parts[parts.length - 2]; 47 | parentRef = Ref[parentType].ref(parentId); 48 | ref = Ref[itemType].ref(itemId, parentRef); 49 | } else { 50 | ref = Ref[itemType].ref(itemId); 51 | } 52 | 53 | const data = activeTextEditor.document.getText(); 54 | 55 | const dbPaths: string[] = []; 56 | parts.forEach(parts => { 57 | if (parts[0] === 'database') dbPaths.unshift(parts[1]); 58 | }); 59 | 60 | const secret = [ 61 | dbPaths.length ? `${config.secret}:${dbPaths.join('/')}` : config.secret, 62 | 'admin' 63 | ].join(':'); 64 | 65 | const isNewDoc = activeTextEditor.document.fileName.includes('newDoc'); 66 | 67 | const query = 68 | isNewDoc && parentRef 69 | ? `Create(${Expr.toString(parentRef)}, ${Ref[itemType].payload(data)})` 70 | : `Update(${Expr.toString(ref)}, ${Ref[itemType].payload(data)})`; 71 | 72 | try { 73 | const resp = await runFQLQuery(query, client, secret); 74 | 75 | const resource = resp[0] as { ref: values.Ref }; 76 | vscode.window.showInformationMessage( 77 | `${Expr.toString(resource.ref)} ${isNewDoc ? ' created' : ' updated'}` 78 | ); 79 | 80 | if (isNewDoc) { 81 | const newPath = activeTextEditor.document.fileName.replace( 82 | 'newDoc', 83 | resource.ref.id 84 | ); 85 | await fs.promises.rename(activeTextEditor.document.fileName, newPath); 86 | 87 | const doc = await vscode.workspace.openTextDocument(newPath); 88 | await vscode.commands.executeCommand( 89 | 'workbench.action.closeActiveEditor' 90 | ); 91 | await vscode.commands.executeCommand('fauna.refreshEntry'); 92 | vscode.window.showTextDocument(doc, activeTextEditor.viewColumn); 93 | } 94 | } catch (error) { 95 | vscode.window.showErrorMessage( 96 | error.requestResult ? error.requestResult.responseRaw : error.message 97 | ); 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /src/uploadGraphqlSchemaCommand.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import vscode from 'vscode'; 3 | import { Config } from './config'; 4 | 5 | export default ( 6 | mode: string = 'merge', 7 | config: Config, 8 | outputChannel: vscode.OutputChannel 9 | ) => async () => { 10 | const { activeTextEditor } = vscode.window; 11 | 12 | const fileName = activeTextEditor?.document.fileName.split('.') || []; 13 | 14 | if ( 15 | !activeTextEditor || 16 | !['graphql', 'gql'].includes(fileName[fileName.length - 1]) 17 | ) { 18 | vscode.window.showErrorMessage( 19 | 'Your GraphQL schema file must include the `.graphql` or `.gql` extension.' 20 | ); 21 | return; 22 | } 23 | 24 | if (activeTextEditor.document.languageId !== 'graphql') { 25 | vscode.window.showWarningMessage( 26 | 'We recommend to install vscode-graphql extension for syntax highlighting, validation, and language features like go to definition, hover information and autocompletion for graphql projects' 27 | ); 28 | } 29 | 30 | const selection = activeTextEditor.selection; 31 | const selectedText = activeTextEditor.document.getText(selection); 32 | const fqlExpression = 33 | selectedText.length > 0 34 | ? selectedText 35 | : activeTextEditor.document.getText(); 36 | if (fqlExpression.length < 1) { 37 | vscode.window.showWarningMessage( 38 | 'Selected file or selected text must have a GraphQL Schema to run' 39 | ); 40 | 41 | return; 42 | } 43 | 44 | outputChannel.appendLine(''); 45 | outputChannel.appendLine( 46 | `UPLOADING SCHEMA (mode=${mode}): ${activeTextEditor.document.fileName}` 47 | ); 48 | outputChannel.show(); 49 | 50 | try { 51 | const buffer = Buffer.from(fqlExpression, 'utf-8'); 52 | const result = await fetch(`${config.graphqlHost}/import?mode=${mode}`, { 53 | method: 'POST', 54 | headers: { 55 | AUTHORIZATION: `Bearer ${config.secret}` 56 | }, 57 | 58 | body: buffer 59 | }); 60 | outputChannel.appendLine(''); 61 | outputChannel.appendLine('RESPONSE:'); 62 | outputChannel.appendLine(await result.text()); 63 | } catch (error) { 64 | let message = error.message; 65 | outputChannel.appendLine('ERROR:'); 66 | outputChannel.appendLine(message); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /syntaxes/fql.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | fql 8 | 9 | name 10 | fql 11 | patterns 12 | 13 | 14 | match 15 | (? 16 | name 17 | constant.numeric.integer.hexadecimal.fql 18 | 19 | 20 | match 21 | (? 22 | name 23 | constant.numeric.float.hexadecimal.fql 24 | 25 | 26 | match 27 | (? 28 | name 29 | constant.numeric.integer.fql 30 | 31 | 32 | match 33 | (? 34 | name 35 | constant.numeric.float.fql 36 | 37 | 38 | begin 39 | ' 40 | beginCaptures 41 | 42 | 0 43 | 44 | name 45 | punctuation.definition.string.begin.fql 46 | 47 | 48 | end 49 | ' 50 | endCaptures 51 | 52 | 0 53 | 54 | name 55 | punctuation.definition.string.end.fql 56 | 57 | 58 | name 59 | string.quoted.single.fql 60 | patterns 61 | 62 | 63 | include 64 | #escaped_char 65 | 66 | 67 | 68 | 69 | begin 70 | " 71 | beginCaptures 72 | 73 | 0 74 | 75 | name 76 | punctuation.definition.string.begin.fql 77 | 78 | 79 | end 80 | " 81 | endCaptures 82 | 83 | 0 84 | 85 | name 86 | punctuation.definition.string.end.fql 87 | 88 | 89 | name 90 | string.quoted.double.fql 91 | patterns 92 | 93 | 94 | include 95 | #escaped_char 96 | 97 | 98 | 99 | 100 | captures 101 | 102 | 1 103 | 104 | name 105 | punctuation.definition.comment.fql 106 | 107 | 108 | match 109 | \A(#).*$\n? 110 | name 111 | comment.line.shebang.fql 112 | 113 | 114 | begin 115 | (^[ \t]+)?(?=//) 116 | beginCaptures 117 | 118 | 1 119 | 120 | name 121 | punctuation.whitespace.comment.leading.fql 122 | 123 | 124 | end 125 | (?!\G)((?!^)[ \t]+\n)? 126 | endCaptures 127 | 128 | 1 129 | 130 | name 131 | punctuation.whitespace.comment.trailing.fql 132 | 133 | 134 | patterns 135 | 136 | 137 | begin 138 | // 139 | beginCaptures 140 | 141 | 0 142 | 143 | name 144 | punctuation.definition.comment.fql 145 | 146 | 147 | end 148 | \n 149 | name 150 | comment.line.octothorpe.fql 151 | 152 | 153 | 154 | 155 | begin 156 | (^[ \t]+)?(?=/\*) 157 | beginCaptures 158 | 159 | 1 160 | 161 | name 162 | punctuation.whitespace.comment.leading.fql 163 | 164 | 165 | end 166 | (?!\G)((?!^)[ \t]+\*/)? 167 | endCaptures 168 | 169 | 1 170 | 171 | name 172 | punctuation.whitespace.comment.trailing.fql 173 | 174 | 175 | patterns 176 | 177 | 178 | begin 179 | /\* 180 | beginCaptures 181 | 182 | 0 183 | 184 | name 185 | punctuation.definition.comment.fql 186 | 187 | 188 | end 189 | \*/ 190 | name 191 | comment.line.octothorpe.fql 192 | 193 | 194 | 195 | 196 | match 197 | \b(If|Else|And|Or|For)\b 198 | name 199 | keyword.control.fql 200 | 201 | 202 | match 203 | (? 204 | name 205 | constant.language.fql 206 | 207 | 208 | match 209 | (? 210 | name 211 | support.function.fql 212 | 213 | 214 | match 215 | (?<=[^.]\.|:)\b([a-zA-Z_][a-zA-Z0-9_]*) 216 | name 217 | variable.other.fql 218 | 219 | 220 | repository 221 | 222 | escaped_char 223 | 224 | patterns 225 | 226 | 227 | match 228 | \\[abfnrtvz\\"'\n] 229 | name 230 | constant.character.escape.fql 231 | 232 | 233 | match 234 | \\\d{1,3} 235 | name 236 | constant.character.escape.byte.fql 237 | 238 | 239 | match 240 | \\x[0-9A-Fa-f][0-9A-Fa-f] 241 | name 242 | constant.character.escape.byte.fql 243 | 244 | 245 | match 246 | \\u\{[0-9A-Fa-f]+\} 247 | name 248 | constant.character.escape.unicode.fql 249 | 250 | 251 | match 252 | \\. 253 | name 254 | invalid.illegal.character.escape.fql 255 | 256 | 257 | 258 | 259 | scopeName 260 | source.fql 261 | uuid 262 | 93E017CC-6F27-11D9-90EB-000D93589AF7 263 | 264 | 265 | -------------------------------------------------------------------------------- /syntaxes/log.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | scopeName 6 | code.log 7 | fileTypes 8 | 9 | log 10 | 11 | name 12 | Log file 13 | patterns 14 | 15 | 16 | 17 | match 18 | "(.*?)" 19 | name 20 | string.quoted 21 | 22 | 23 | 24 | match 25 | '(.*?)' 26 | name 27 | string.quoted 28 | 29 | 36 | 37 | 38 | match 39 | \b(?i:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))\b 40 | name 41 | support.class 42 | 43 | 44 | 45 | match 46 | \S+@\S+\.\S+ 47 | name 48 | markup.bold 49 | 50 | 51 | 52 | match 53 | \b(?i:((\.)*[a-z]|[0-9])*(Exception|Error|Failure|Fail))\b 54 | name 55 | invalid 56 | 57 | 58 | 59 | match 60 | \b(((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]([0-2]?[0-9]|[3][0-1])[/|\-|\.| ]((19|20)?[0-9]{2})\b 61 | name 62 | constant.numeric 63 | 64 | 65 | 66 | match 67 | \b((19|20)?[0-9]{2}[/|\-|\.| ](((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]([0-2]?[0-9]|[3][0-1]))\b 68 | name 69 | constant.numeric 70 | 71 | 72 | 73 | match 74 | \b([0-2]?[0-9]|[3][0-1])[/|\-|\.| ](((0|1)?[0-9][1-2]?)|(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sept(ember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?))[/|\-|\.| ]((19|20)?[0-9]{2})\b 75 | name 76 | constant.numeric 77 | 78 | 79 | 80 | match 81 | \b([0|1]?[0-9]|2[0-3])\:[0-5][0-9](\:[0-5][0-9])?( ?(?i:(a|p)m?))?( ?[+-]?[0-9]*)?\b 82 | name 83 | constant.numeric 84 | 85 | 86 | 87 | match 88 | \b\d+\.?\d*?\b 89 | name 90 | constant.numeric 91 | 92 | 93 | 94 | match 95 | \b(?i:(0?x)?[0-9a-f][0-9a-f]+)\b 96 | name 97 | constant.numeric 98 | 99 | 100 | 101 | match 102 | \b(?i:(([a-z]|[0-9]|[_|-])*(\.([a-z]|[0-9]|[_|-])*)+))\b 103 | name 104 | support.type 105 | 106 | 107 | match 108 | \b(?i:(Down|Error|Failure|Fail|Fatal|false))(\:|\b) 109 | name 110 | invalid.illegal 111 | 112 | 113 | match 114 | \b(?i:(hint|info|information|true|log))(\:|\b) 115 | name 116 | keyword 117 | 118 | 119 | match 120 | \b(?i:(warning|warn|test|debug|null|undefined|NaN))(\:|\b) 121 | name 122 | invalid.deprecated 123 | 124 | 125 | match 126 | \b(?i:(local))(\:|\b) 127 | name 128 | support.function 129 | 130 | 131 | match 132 | \b(?i:(server|running|remote))(\:|\b) 133 | name 134 | comment.line 135 | 136 | 137 | 138 | match 139 | \b(?i:([a-z]|[0-9])+\:((\/\/)|((\/\/)?(\S)))+) 140 | name 141 | storage 142 | 143 | 144 | 145 | match 146 | (-)+>|├(─)+|└(─)+ 147 | name 148 | comment.line 149 | 150 | 151 | uuid 152 | ab259404-3072-4cd4-a943-7cbbd32e373f 153 | 154 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "commonjs", 5 | "target": "es6", 6 | "outDir": "out", 7 | "lib": ["es2017", "dom"], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | }, 16 | "exclude": ["node_modules", ".vscode-test"] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | --------------------------------------------------------------------------------