├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitlab-ci.yml ├── .npmignore ├── .prettierrc.js ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── Zeus.gif ├── commitlint.config.js ├── examples └── typescript-node │ ├── package.json │ ├── src │ ├── index.ts │ └── zeus │ │ ├── const.ts │ │ ├── index.ts │ │ └── typedDocumentNode.ts │ ├── tsconfig.json │ └── zeus.graphql ├── images ├── example.png ├── zeus-bash-command.png ├── zeus-logo.png └── zeus.webp ├── jest.config.js ├── libBuilder.ts ├── package.json ├── packages ├── graphql-zeus-core │ ├── Models │ │ ├── Environment.ts │ │ └── index.ts │ ├── TranslateGraphQL │ │ └── index.ts │ ├── TreeToTS │ │ ├── functions │ │ │ ├── apiSubscription │ │ │ │ ├── graphql-ws.ts │ │ │ │ └── legacy.ts │ │ │ ├── apiTest │ │ │ │ └── index.spec.ts │ │ │ ├── generated.ts │ │ │ └── new │ │ │ │ ├── apiFetch.ts │ │ │ │ ├── buildQuery.spec.ts │ │ │ │ ├── buildQuery.ts │ │ │ │ ├── clientFunctions.ts │ │ │ │ ├── decodeScalarsInResponse.spec.ts │ │ │ │ ├── decodeScalarsInResponse.ts │ │ │ │ ├── mocks.ts │ │ │ │ ├── models.ts │ │ │ │ ├── prepareScalarPaths.spec.ts │ │ │ │ ├── prepareScalarPaths.ts │ │ │ │ ├── purifyGraphQLKey.spec.ts │ │ │ │ ├── purifyGraphQLKey.ts │ │ │ │ ├── resolvePath.spec.ts │ │ │ │ ├── resolvePath.ts │ │ │ │ ├── resolverFor.ts │ │ │ │ ├── types.ts │ │ │ │ └── variableExtract.ts │ │ ├── index.ts │ │ └── templates │ │ │ ├── modelTypes │ │ │ └── index.ts │ │ │ ├── operations │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ │ ├── returnedPropTypes │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ │ ├── returnedReturns.ts │ │ │ ├── returnedTypes │ │ │ ├── enum.spec.ts │ │ │ ├── enum.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.spec.ts │ │ │ ├── interfaces.ts │ │ │ ├── models.ts │ │ │ ├── unionMember.spec.ts │ │ │ ├── unionMember.ts │ │ │ ├── unions.spec.ts │ │ │ └── unions.ts │ │ │ ├── scalars │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ │ ├── shared │ │ │ ├── description.spec.ts │ │ │ ├── description.ts │ │ │ ├── field.spec.ts │ │ │ ├── field.ts │ │ │ ├── primitive.spec.ts │ │ │ └── primitive.ts │ │ │ ├── truthy.ts │ │ │ ├── valueTypes │ │ │ ├── arg.spec.ts │ │ │ ├── arg.ts │ │ │ ├── index.ts │ │ │ ├── inputTypes │ │ │ │ ├── arg.ts │ │ │ │ └── index.ts │ │ │ └── models.ts │ │ │ └── variableTypes │ │ │ └── index.ts │ ├── __tests__ │ │ └── TestUtils.ts │ ├── index.ts │ ├── jest.config.js │ ├── package.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── graphql-zeus-jsonschema │ ├── TreeToJSONSchema │ │ ├── index.spec.ts │ │ └── index.ts │ ├── index.ts │ ├── jest.config.js │ ├── package.json │ ├── tsconfig.build.json │ └── tsconfig.json └── graphql-zeus │ ├── CLIClass.ts │ ├── Utils │ └── index.ts │ ├── config.ts │ ├── index.ts │ ├── package.json │ ├── plugins │ └── typedDocumentNode │ │ └── index.ts │ └── tsconfig.json ├── run-example.sh └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 6 | ], 7 | parserOptions: { 8 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 9 | sourceType: 'module', // Allows for the use of imports 10 | }, 11 | plugins: ['@typescript-eslint', 'prettier'], 12 | rules: { 13 | '@typescript-eslint/no-use-before-define': 0, 14 | '@typescript-eslint/explicit-function-return-type': 0, 15 | '@typescript-eslint/no-var-requires': 0, 16 | '@typescript-eslint/explicit-module-boundary-types': 0, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | tags-ignore: 7 | - '[0-9]+.[0-9]+.[0-9]+' 8 | pull_request: 9 | branches: 10 | - master 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | node: [22, 20] 16 | platform: [ubuntu-latest, macos-latest, windows-latest] 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | - name: setup node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node }} 23 | - name: checkout 24 | uses: actions/checkout@v1 25 | - name: Get npm cache directory 26 | id: npm-cache 27 | run: | 28 | echo "::set-output name=dir::$(npm config get cache)" 29 | - uses: actions/cache@v4 30 | with: 31 | path: ${{ steps.npm-cache.outputs.dir }} 32 | key: ${{ matrix.platform }}-node-${{ matrix.node }}-${{ hashFiles('package-lock.json') }} 33 | restore-keys: | 34 | ${{ matrix.platform }}-node-${{ matrix.node }} 35 | - name: install deps 36 | run: npm install 37 | - name: run tests 38 | run: npm run test 39 | - name: build library 40 | run: npm run build --ws --if-present 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - '[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | # Setup .npmrc file to publish to npm 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: '22.x' 15 | registry-url: 'https://registry.npmjs.org' 16 | - run: npm install 17 | - run: npm run test 18 | - run: npm run build --ws --if-present 19 | - run: npm publish -ws --access public --tag latest 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_STORE 3 | node_modules 4 | .module-cache 5 | *.log* 6 | build 7 | dist 8 | docs-dist 9 | lib 10 | .idea 11 | .docz 12 | .tscache 13 | package-lock.json 14 | *.tsbuildinfo* -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:carbon 2 | 3 | cache: 4 | paths: 5 | - node_modules/ 6 | 7 | stages: 8 | - build 9 | - test 10 | 11 | build: 12 | stage: build 13 | script: 14 | - npm install 15 | 16 | test: 17 | stage: test 18 | script: 19 | - npm install 20 | - npm run test -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_STORE 3 | .docz 4 | .tscache 5 | node_modules 6 | .module-cache 7 | *.log* 8 | build 9 | dist 10 | src 11 | example 12 | images 13 | .idea 14 | package-lock.json 15 | __tests__ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: "always", 3 | printWidth: 80, 4 | semi: true, 5 | trailingComma: "all", 6 | singleQuote: true, 7 | printWidth: 120, 8 | tabWidth: 2 9 | }; 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to GraphQL Zeus 2 | 3 | We welcome contributions to GraphQL Zeus of any kind including documentation, themes, 4 | organization, tutorials, blog posts, bug reports, issues, feature requests, 5 | feature implementations, pull requests, answering questions on the forum, 6 | helping to manage issues, etc. 7 | 8 | _Changes to the codebase **and** related documentation, e.g. for a new feature, should still use a single pull request._ 9 | 10 | ## Table of Contents 11 | 12 | - [Contributing to GraphQL Zeus](#Contributing-to-GraphQL-Zeus) 13 | - [Table of Contents](#Table-of-Contents) 14 | - [Reporting Issues](#Reporting-Issues) 15 | - [Code Contribution](#Code-Contribution) 16 | 17 | 18 | ## Reporting Issues 19 | 20 | If you believe you have found a defect in GraphQL Zeus or its documentation, use 21 | the GitHub [issue tracker](https://github.com/graphql-editor/graphql-zeus/issues) to report 22 | the problem to the maintainers. 23 | 24 | ## Code Contribution 25 | 26 | GraphQL Zeus is still looking for its direction so remember functionality has to: 27 | 28 | - repair existing bugs 29 | - introduce new feature requests 30 | 31 | **Bug fixes are, of course, always welcome.** 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Artur Czemiel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](images/zeus-logo.png) 2 | 3 | [![npm](https://img.shields.io/npm/v/graphql-zeus.svg?style=flat-square)](https://www.npmjs.com/package/graphql-zeus) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](http://commitizen.github.io/cz-cli/) [![npm downloads](https://img.shields.io/npm/dt/graphql-zeus.svg?style=flat-square)](https://www.npmjs.com/package/graphql-zeus) 4 | 5 | Strongly Typed GraphQL from the team at [GraphQL Editor](https://graphqleditor.com/?utm_source=graphql_zeus_github) 6 | 7 | # How it works 8 | 9 | ![](Zeus.gif) 10 | 11 | GraphQL Zeus is the absolute best way to interact with your GraphQL endpoints in a type-safe way. Zeus uses your schema to generate Typescript types and strongly typed clients to unlock the power, efficiency, productivity and safety of Typescript on your GraphQL requests. 12 | 13 | GraphQL Syntax ( not type-safe 😢 ) 14 | 15 | ```gql 16 | query ($id: String!) { 17 | usersQuery { 18 | admin { 19 | sequenceById(_id: $id) { 20 | _id 21 | name 22 | analytics { 23 | sentMessages 24 | sentInvitations 25 | receivedReplies 26 | acceptedInvitations 27 | } 28 | replies { 29 | message 30 | createdAt 31 | _id 32 | } 33 | messages { 34 | _id 35 | content 36 | renderedContent 37 | sendAfterDays 38 | } 39 | tracks { 40 | _id 41 | createdAt 42 | inviteSent 43 | inviteAccepted 44 | contact { 45 | linkedInId 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | Zeus syntax ( type-safe 😋 ) 55 | ```tsx 56 | { 57 | usersQuery: { 58 | admin: { 59 | sequenceById: [ 60 | { id: $("id", "String!") }, 61 | { 62 | _id: true, 63 | name: true, 64 | analytics: { ...fields("SequenceAnalytics") }, 65 | replies: { 66 | ...fields("SequenceTrackReply"), 67 | }, 68 | messages: { 69 | ...fields("Message"), 70 | }, 71 | tracks: { 72 | ...fields("SequenceTrack"), 73 | contact: { 74 | linkedInId: true, 75 | }, 76 | }, 77 | }, 78 | ], 79 | }, 80 | }, 81 | } 82 | ``` 83 | 84 | ## New! Composables 85 | ```ts 86 | import { 87 | Gql, 88 | ComposableSelector, 89 | } from './zeus/index.js'; 90 | 91 | const withComposable = , Z extends T>(id: string, rest: Z | T) => 92 | Gql('query')({ 93 | cardById: [{ cardId: id }, rest], 94 | }); 95 | const c1result = await withComposable('12', { 96 | id: true, 97 | }); 98 | const c2result = await withComposable('12', { 99 | Defense: true, 100 | Attack: true, 101 | }); 102 | ``` 103 | 104 | Both responses and inputs are safely typed 105 | 106 | ## Features 107 | ⚡️ Validates queries and selectors 108 | ⚡️ Types mapped from your schema
109 | ⚡️ Fetch all primitive fields with one function
110 | ⚡️ Works with Apollo Client, React Query, Stucco Subscriptions _(\*more coming soon...)_
111 | ⚡️ Works with Subscriptions
112 | ⚡️ Infer complex response types
113 | ⚡️ Create reusable selection sets (like fragments) for use across multiple queries
114 | ⚡️ Supports GraphQL Unions, Interfaces, Aliases and Variables
115 | ⚡️ Handles **massive** schemas
116 | ⚡️ Supports Browsers, Node.js and React Native in Javascript and Typescript
117 | ⚡️ Schema downloader
118 | ⚡️ JSON schema generation
119 | 120 | ## Full documentation 121 | 122 | Our full documentation has all the use cases of: 123 | 124 | - scalars 125 | - selectors 126 | 127 | and much more... 128 | 129 | [Full documentation is available here](https://graphqleditor.com/docs/zeus/) 130 | 131 | ## Join the Zeus Community and Spread the Word 132 | 133 | ⚡️ [Join the Discussion forum on Dicord](https://discord.gg/bHf2cw8e) 📣 134 | 135 | ⚡️ Leave a GitHub star ⭐️ 👆 136 | 137 | ⚡️ Spread the word on your socials and with your networks! 🗣 138 | 139 | ## Contribute 140 | 141 | For a complete guide to contributing to GraphQL Zeus, see the [Contribution Guide](CONTRIBUTING.md). 142 | 143 | 1. Fork this repo 144 | 2. Create your feature branch: git checkout -b feature-name 145 | 3. Commit your changes: git commit -am 'Add some feature' 146 | 4. Push to the branch: git push origin my-new-feature 147 | 5. Submit a pull request 148 | 149 | 150 | 151 | ## License 152 | 153 | [MIT](https://opensource.org/licenses/MIT) 🕊 154 | -------------------------------------------------------------------------------- /Zeus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-editor/graphql-zeus/238da86f5cf4f0f318c6099f3b8129415bd0c3c2/Zeus.gif -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /examples/typescript-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-node", 3 | "version": "3.0.7", 4 | "description": "", 5 | "private": true, 6 | "main": "index.js", 7 | "type": "module", 8 | "scripts": { 9 | "just-run": "node --loader ts-node/esm src/index.ts", 10 | "start": "npm run generate-typescript-node && node --import=tsx src/index.ts", 11 | "generate-typescript-node-config": "node ../../packages/graphql-zeus/lib/index.js --n -g ./zeus.graphql --td", 12 | "generate-typescript-node": "node ../../packages/graphql-zeus/lib/index.js https://faker.prod.graphqleditor.com/a-team/olympus/graphql ./src --n -g ./zeus.graphql --td", 13 | "generate-typescript-node-get": "node ../../packages/graphql-zeus/lib/index.js https://faker.prod.graphqleditor.com/a-team/olympus/graphql ./src --n -g ./zeus.graphql --method=GET --td" 14 | }, 15 | "author": "Aexol (http://aexol.com)", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@graphql-typed-document-node/core": "^3.1.1", 19 | "tsx": "^4.19.2" 20 | }, 21 | "dependencies": { 22 | "@apollo/client": "^3.4.16", 23 | "graphql-tag": "^2.12.6", 24 | "node-fetch": "^2.6.0", 25 | "react-query": "^3.27.0", 26 | "ws": "^8.5.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/typescript-node/src/index.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import fetch from 'node-fetch'; 3 | import { 4 | Gql, 5 | SpecialSkills, 6 | Thunder, 7 | Zeus, 8 | InputType, 9 | Selector, 10 | GraphQLTypes, 11 | ZeusScalars, 12 | ValueTypes, 13 | $, 14 | FromSelector, 15 | fields, 16 | ComposableSelector, 17 | } from './zeus/index.js'; 18 | import { typedGql } from './zeus/typedDocumentNode.js'; 19 | 20 | export const testApollo = async () => { 21 | const { ApolloClient, InMemoryCache, useMutation } = await import('@apollo/client'); 22 | 23 | const client = new ApolloClient({ 24 | cache: new InMemoryCache(), 25 | }); 26 | 27 | const useMyMutation = () => { 28 | return ({ card }: { card: ValueTypes['createCard'] }) => 29 | client.mutate({ 30 | mutation: typedGql('mutation')({ 31 | addCard: [{ card }, { id: true }], 32 | }), 33 | }); 34 | }; 35 | 36 | const testMutate = () => { 37 | const [mutate] = useMutation( 38 | typedGql('mutation')({ 39 | addCard: [ 40 | { card: { Attack: $('attt', 'Int'), Defense: 2, name: $('name', 'String!'), description: 'Stronk' } }, 41 | { id: true }, 42 | ], 43 | }), 44 | ); 45 | 46 | mutate({ 47 | variables: { 48 | name: 'DDD', 49 | attt: 1, 50 | }, 51 | }); 52 | }; 53 | return { 54 | useMyMutation, 55 | testMutate, 56 | }; 57 | }; 58 | const sel = Selector('Query')({ 59 | drawCard: { 60 | Children: true, 61 | Attack: true, 62 | info: true, 63 | ids: true, 64 | attack: [{ cardID: ['sss'] }, { Attack: true }], 65 | }, 66 | cardById: [{ cardId: '' }, { Attack: true }], 67 | }); 68 | 69 | const decoders = ZeusScalars({ 70 | JSON: { 71 | encode: (e: unknown) => JSON.stringify(e), 72 | decode: (e: unknown) => { 73 | if (!e) return; 74 | return e as { power: number }; 75 | }, 76 | }, 77 | ID: { 78 | decode: (e: unknown) => e as number, 79 | }, 80 | }); 81 | 82 | export type IRT = InputType; 83 | 84 | const printQueryResult = (name: string, result: unknown) => 85 | console.log(`${chalk.greenBright(name)} result:\n${chalk.cyan(JSON.stringify(result, null, 4))}\n\n`); 86 | const printGQLString = (name: string, result: string) => 87 | console.log(`${chalk.blue(name)} query:\n${chalk.magenta(result)}\n\n`); 88 | const run = async () => { 89 | const { addCard: ZeusCard } = await Gql('mutation')( 90 | { 91 | addCard: [ 92 | { 93 | card: { 94 | Attack: 1, 95 | Defense: 1, 96 | description: 'lorem """ \' ipsum \n lorem ipsum', 97 | name: 'SADSD', 98 | skills: [SpecialSkills.FIRE], 99 | }, 100 | }, 101 | { 102 | info: true, 103 | ids: true, 104 | cardImage: { 105 | bucket: true, 106 | region: true, 107 | key: true, 108 | }, 109 | }, 110 | ], 111 | }, 112 | { operationName: 'ZausCard' }, 113 | ); 114 | printQueryResult('ZeusCard', ZeusCard); 115 | const withComposable = , Z extends T>(id: string, rest: Z | T) => 116 | Gql('query')({ 117 | cardById: [{ cardId: id }, rest], 118 | }); 119 | const c1result = await withComposable('12', { 120 | id: true, 121 | }); 122 | const c2result = await withComposable('12', { 123 | Defense: true, 124 | Attack: true, 125 | }); 126 | printQueryResult('composables', `1. ${c1result.cardById?.id} 2. ${c2result.cardById?.Attack}`); 127 | const bbb = await Gql('query')({ 128 | drawCard: { 129 | ...fields('Card'), 130 | }, 131 | }); 132 | printQueryResult('scalarsSelector', bbb.drawCard); 133 | 134 | const blalba = await Gql('query')({ 135 | drawChangeCard: { 136 | __typename: true, 137 | '...on EffectCard': { 138 | effectSize: true, 139 | name: true, 140 | }, 141 | }, 142 | }); 143 | printQueryResult('drawChangeCard', blalba.drawChangeCard); 144 | const blalbaScalars = await Gql('query', { scalars: decoders })({ 145 | drawCard: { 146 | info: true, 147 | id: true, 148 | }, 149 | }); 150 | console.log({ blalbaScalars }); 151 | if (typeof blalbaScalars.drawCard.info?.power !== 'number') { 152 | throw new Error('Invalid scalar decoder'); 153 | } 154 | printQueryResult('blalbaScalars', blalbaScalars.drawCard.info.power); 155 | 156 | // Thunder example 157 | const thunder = Thunder(async (query) => { 158 | const response = await fetch('https://faker.prod.graphqleditor.com/a-team/olympus/graphql', { 159 | body: JSON.stringify({ query }), 160 | method: 'POST', 161 | headers: { 162 | 'Content-Type': 'application/json', 163 | }, 164 | }); 165 | if (!response.ok) { 166 | return new Promise((resolve, reject) => { 167 | response 168 | .text() 169 | .then((text) => { 170 | try { 171 | reject(JSON.parse(text)); 172 | } catch (err) { 173 | reject(text); 174 | } 175 | }) 176 | .catch(reject); 177 | }); 178 | } 179 | const json = await response.json(); 180 | return json.data; 181 | }); 182 | const blalbaThunder = await thunder('query', { 183 | scalars: decoders, 184 | })({ 185 | drawCard: { 186 | Attack: true, 187 | info: true, 188 | }, 189 | drawChangeCard: { 190 | __typename: true, 191 | '...on EffectCard': { 192 | effectSize: true, 193 | name: true, 194 | }, 195 | }, 196 | }); 197 | printQueryResult('drawChangeCard thunder', blalbaThunder.drawChangeCard); 198 | 199 | const resPowerups = await Gql('query')( 200 | { 201 | public: { 202 | powerups: [{ filter: 'dupa' }, { name: true }], 203 | }, 204 | }, 205 | { operationName: 'Powerups' }, 206 | ); 207 | printQueryResult('Powerups', resPowerups?.public?.powerups); 208 | 209 | const { 210 | listCards: stack, 211 | drawCard: newCard, 212 | drawChangeCard, 213 | } = await Gql('query')({ 214 | listCards: { 215 | name: true, 216 | cardImage: { 217 | bucket: true, 218 | }, 219 | }, 220 | drawCard: { 221 | Attack: true, 222 | name: `@skip(if:true)`, 223 | }, 224 | drawChangeCard: { 225 | '...on SpecialCard': { 226 | name: true, 227 | }, 228 | '...on EffectCard': { 229 | effectSize: true, 230 | }, 231 | }, 232 | }); 233 | 234 | printQueryResult('stack', stack); 235 | printQueryResult('newCard', newCard); 236 | printQueryResult('changeCard', drawChangeCard); 237 | 238 | const aliasedQuery = Zeus('query', { 239 | __alias: { 240 | myCards: { 241 | listCards: { 242 | name: true, 243 | }, 244 | }, 245 | }, 246 | listCards: { 247 | __alias: { 248 | atak: { 249 | attack: [ 250 | { cardID: ['aaa'] }, 251 | { 252 | name: true, 253 | description: true, 254 | __alias: { 255 | bbb: { 256 | Defense: true, 257 | }, 258 | }, 259 | }, 260 | ], 261 | }, 262 | }, 263 | }, 264 | }); 265 | printGQLString('aliasedQuery', aliasedQuery); 266 | const operationName = Zeus( 267 | 'query', 268 | { 269 | listCards: { 270 | Attack: true, 271 | }, 272 | }, 273 | { 274 | operationOptions: { 275 | operationName: 'ListCards', 276 | }, 277 | }, 278 | ); 279 | printGQLString('operationName ListCards', operationName); 280 | const aliasedQueryExecute = await Gql('query')( 281 | { 282 | listCards: { 283 | __alias: { 284 | namy: { 285 | name: true, 286 | }, 287 | atak: { 288 | attack: [ 289 | { cardID: $('cardIds', '[String!]!') }, 290 | { 291 | name: true, 292 | __alias: { 293 | bbb: { 294 | Defense: true, 295 | }, 296 | ccc: { 297 | Children: true, 298 | }, 299 | }, 300 | }, 301 | ], 302 | }, 303 | }, 304 | id: true, 305 | }, 306 | }, 307 | { 308 | variables: { 309 | cardIds: ['aaa'], 310 | }, 311 | }, 312 | ); 313 | printQueryResult('aliasedQuery', aliasedQueryExecute); 314 | const Children = undefined; 315 | const emptyTestMutation = Zeus('mutation', { 316 | addCard: [ 317 | { 318 | card: { 319 | Attack: 1, 320 | Defense: 2, 321 | description: 'lorem ipsum \n lorem ipsum', 322 | name: 'SADSD', 323 | Children, 324 | skills: [SpecialSkills.FIRE], 325 | }, 326 | }, 327 | { 328 | id: true, 329 | description: true, 330 | name: true, 331 | Attack: true, 332 | // // skills: true, 333 | Children, 334 | Defense: true, 335 | cardImage: { 336 | bucket: true, 337 | region: true, 338 | key: true, 339 | }, 340 | }, 341 | ], 342 | }); 343 | printQueryResult('emptyTestMutation', emptyTestMutation); 344 | 345 | const interfaceTest = await Gql('query')({ 346 | nameables: { 347 | __typename: true, 348 | name: true, 349 | '...on Card': { 350 | Attack: true, 351 | }, 352 | '...on SpecialCard': { 353 | effect: true, 354 | }, 355 | '...on CardStack': { 356 | cards: { 357 | name: true, 358 | }, 359 | }, 360 | }, 361 | }); 362 | 363 | printQueryResult('interfaceTest', interfaceTest); 364 | // Variable test 365 | 366 | const test = await Gql('mutation')( 367 | { 368 | addCard: [ 369 | { 370 | card: { 371 | Attack: $('Attack', 'Int!'), 372 | Defense: $('Defense', 'Int!'), 373 | name: 'aa', 374 | description: 'aa', 375 | }, 376 | }, 377 | { 378 | id: true, 379 | description: true, 380 | name: true, 381 | Attack: true, 382 | // skills: true, 383 | Children: true, 384 | Defense: true, 385 | cardImage: { 386 | bucket: true, 387 | region: true, 388 | key: true, 389 | }, 390 | }, 391 | ], 392 | }, 393 | { 394 | variables: { Attack: 1, Defense: 1 }, 395 | }, 396 | ); 397 | printQueryResult('variable Test', test); 398 | 399 | const selectorTDD = Selector('Query')({ 400 | drawCard: { 401 | id: true, 402 | Attack: true, 403 | Defense: true, 404 | attacks: true, 405 | }, 406 | cardById: [{ cardId: $('cardId', 'String!') }, { id: true }], 407 | }); 408 | 409 | //interface selector 410 | const inSelector = Selector('Nameable')({ 411 | name: true, 412 | }); 413 | 414 | type aa = FromSelector; 415 | const ab: aa = {} as any; 416 | if (ab.__typename === 'Card') { 417 | ab.name; 418 | } else if (ab.__typename === 'EffectCard') { 419 | ab.name; 420 | } else if (ab.__typename === 'CardStack') { 421 | ab.name; 422 | } 423 | //interface selector 424 | const inSelector2 = Selector('Nameable')({ 425 | __typename: true, 426 | name: true, 427 | '...on Card': { 428 | description: true, 429 | }, 430 | '...on EffectCard': { 431 | effectSize: true, 432 | }, 433 | '...on CardStack': { 434 | cards: { 435 | info: true, 436 | }, 437 | }, 438 | }); 439 | 440 | type bb = FromSelector; 441 | const bc: bb = {} as any; 442 | if (bc.__typename === 'Card') { 443 | bc.name; 444 | bc.description; 445 | } else if (bc.__typename === 'EffectCard') { 446 | bc.name; 447 | bc.effectSize; 448 | } else if (bc.__typename === 'CardStack') { 449 | bc.name; 450 | bc.cards; 451 | } 452 | 453 | const generatedTypedDocumentNode = typedGql('query')(selectorTDD); 454 | printQueryResult('Generated TypedDocumentNode Test', generatedTypedDocumentNode); 455 | }; 456 | run(); 457 | -------------------------------------------------------------------------------- /examples/typescript-node/src/zeus/const.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export const AllTypesProps: Record = { 4 | SpecialSkills: "enum" as const, 5 | Query:{ 6 | cardById:{ 7 | 8 | } 9 | }, 10 | createCard:{ 11 | skills:"SpecialSkills" 12 | }, 13 | Card:{ 14 | attack:{ 15 | 16 | }, 17 | testFn:{ 18 | 19 | } 20 | }, 21 | Mutation:{ 22 | addCard:{ 23 | card:"createCard" 24 | } 25 | }, 26 | JSON: `scalar.JSON` as const, 27 | Public:{ 28 | powerups:{ 29 | 30 | } 31 | }, 32 | ID: `scalar.ID` as const 33 | } 34 | 35 | export const ReturnTypes: Record = { 36 | Nameable:{ 37 | "...on CardStack": "CardStack", 38 | "...on Card": "Card", 39 | "...on SpecialCard": "SpecialCard", 40 | "...on EffectCard": "EffectCard", 41 | name:"String" 42 | }, 43 | Query:{ 44 | cardById:"Card", 45 | drawCard:"Card", 46 | drawChangeCard:"ChangeCard", 47 | listCards:"Card", 48 | myStacks:"CardStack", 49 | nameables:"Nameable", 50 | public:"Public" 51 | }, 52 | CardStack:{ 53 | cards:"Card", 54 | name:"String" 55 | }, 56 | Card:{ 57 | Attack:"Int", 58 | Children:"Int", 59 | Defense:"Int", 60 | attack:"Card", 61 | cardImage:"S3Object", 62 | description:"String", 63 | id:"ID", 64 | image:"String", 65 | info:"JSON", 66 | name:"String", 67 | skills:"SpecialSkills", 68 | testFn:"String", 69 | ids:"ID" 70 | }, 71 | SpecialCard:{ 72 | effect:"String", 73 | name:"String" 74 | }, 75 | EffectCard:{ 76 | effectSize:"Float", 77 | name:"String" 78 | }, 79 | Mutation:{ 80 | addCard:"Card" 81 | }, 82 | JSON: `scalar.JSON` as const, 83 | S3Object:{ 84 | bucket:"String", 85 | key:"String", 86 | region:"String" 87 | }, 88 | Public:{ 89 | powerups:"Powerup" 90 | }, 91 | Powerup:{ 92 | name:"String" 93 | }, 94 | ChangeCard:{ 95 | "...on SpecialCard":"SpecialCard", 96 | "...on EffectCard":"EffectCard" 97 | }, 98 | Subscription:{ 99 | deck:"Card" 100 | }, 101 | ID: `scalar.ID` as const 102 | } 103 | 104 | export const Ops = { 105 | query: "Query" as const, 106 | mutation: "Mutation" as const, 107 | subscription: "Subscription" as const 108 | } -------------------------------------------------------------------------------- /examples/typescript-node/src/zeus/typedDocumentNode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { TypedDocumentNode } from '@graphql-typed-document-node/core'; 3 | import gql from 'graphql-tag'; 4 | import { 5 | ValueTypes, 6 | GenericOperation, 7 | OperationOptions, 8 | GraphQLTypes, 9 | InputType, 10 | ScalarDefinition, 11 | ThunderGraphQLOptions, 12 | Zeus, 13 | ExtractVariables, 14 | } from './index.js'; 15 | import { Ops } from './const.js'; 16 | 17 | export const typedGql = 18 | >( 19 | operation: O, 20 | graphqlOptions?: ThunderGraphQLOptions, 21 | ) => 22 | (o: Z & { 23 | [P in keyof Z]: P extends keyof ValueTypes[R] ? Z[P] : never; 24 | }, ops?: OperationOptions) => { 25 | const str = Zeus(operation, o, { 26 | operationOptions: ops, 27 | scalars: graphqlOptions?.scalars, 28 | }); 29 | return gql(str) as TypedDocumentNode, ExtractVariables>; 30 | }; 31 | -------------------------------------------------------------------------------- /examples/typescript-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "declaration": true, 10 | "incremental": true, 11 | "removeComments": true, 12 | "noUnusedLocals": true, 13 | "strict": true, 14 | "outDir": "./lib", 15 | "rootDir": "./src", 16 | "baseUrl": "./src/" 17 | }, 18 | "exclude": ["lib", "node_modules", "docs", "__tests__", "generated", "examples"] 19 | } 20 | -------------------------------------------------------------------------------- /examples/typescript-node/zeus.graphql: -------------------------------------------------------------------------------- 1 | interface Nameable { 2 | name: String! 3 | } 4 | 5 | enum SpecialSkills { 6 | """Lower enemy defense -5
""" 7 | THUNDER 8 | 9 | """Attack multiple Cards at once
""" 10 | RAIN 11 | 12 | """50% chance to avoid any attack
""" 13 | FIRE 14 | } 15 | 16 | type Query { 17 | cardById(cardId: String): Card 18 | 19 | """Draw a card
""" 20 | drawCard: Card! 21 | drawChangeCard: ChangeCard! 22 | 23 | """list All Cards availble
""" 24 | listCards: [Card!]! 25 | myStacks: [CardStack!] 26 | nameables: [Nameable!]! 27 | public: Public 28 | } 29 | 30 | """create card inputs
""" 31 | input createCard { 32 | """The name of a card
""" 33 | name: String! 34 | 35 | """Description of a card
""" 36 | description: String! 37 | 38 | """
How many children the greek god had
""" 39 | Children: Int 40 | 41 | """The attack power
""" 42 | Attack: Int! 43 | 44 | """The defense power
""" 45 | Defense: Int! 46 | 47 | """input skills""" 48 | skills: [SpecialSkills!] 49 | } 50 | 51 | """Stack of cards""" 52 | type CardStack implements Nameable { 53 | cards: [Card!] 54 | name: String! 55 | } 56 | 57 | """Card used in card game
""" 58 | type Card implements Nameable { 59 | """The attack power
""" 60 | Attack: Int! 61 | 62 | """
How many children the greek god had
""" 63 | Children: Int 64 | 65 | """The defense power
""" 66 | Defense: Int! 67 | 68 | """Attack other cards on the table , returns Cards after attack
""" 69 | attack( 70 | """Attacked card/card ids
""" 71 | cardID: [String!]! 72 | ): [Card!] 73 | 74 | """Put your description here""" 75 | cardImage: S3Object 76 | 77 | """Description of a card
""" 78 | description: String! 79 | id: ID! 80 | image: String! 81 | info: JSON! 82 | 83 | """The name of a card
""" 84 | name: String! 85 | skills: [SpecialSkills!] 86 | testFn(test: String): String 87 | ids: [ID!] 88 | } 89 | 90 | type SpecialCard implements Nameable { 91 | effect: String! 92 | name: String! 93 | } 94 | 95 | type EffectCard implements Nameable { 96 | effectSize: Float! 97 | name: String! 98 | } 99 | 100 | type Mutation { 101 | """add Card to Cards database
""" 102 | addCard(card: createCard!): Card! 103 | } 104 | 105 | scalar JSON 106 | 107 | """Aws S3 File""" 108 | type S3Object { 109 | bucket: String! 110 | key: String! 111 | region: String! 112 | } 113 | 114 | type Public { 115 | powerups(filter: String!): [Powerup!] 116 | } 117 | 118 | type Powerup { 119 | name: String 120 | } 121 | 122 | union ChangeCard = SpecialCard | EffectCard 123 | 124 | type Subscription { 125 | deck: [Card!] 126 | } 127 | schema{ 128 | query: Query, 129 | mutation: Mutation, 130 | subscription: Subscription 131 | } -------------------------------------------------------------------------------- /images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-editor/graphql-zeus/238da86f5cf4f0f318c6099f3b8129415bd0c3c2/images/example.png -------------------------------------------------------------------------------- /images/zeus-bash-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-editor/graphql-zeus/238da86f5cf4f0f318c6099f3b8129415bd0c3c2/images/zeus-bash-command.png -------------------------------------------------------------------------------- /images/zeus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-editor/graphql-zeus/238da86f5cf4f0f318c6099f3b8129415bd0c3c2/images/zeus-logo.png -------------------------------------------------------------------------------- /images/zeus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-editor/graphql-zeus/238da86f5cf4f0f318c6099f3b8129415bd0c3c2/images/zeus.webp -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ 3 | '/packages/graphql-zeus-core/jest.config.js', 4 | '/packages/graphql-zeus-jsonschema/jest.config.js', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /libBuilder.ts: -------------------------------------------------------------------------------- 1 | #!/bin/node 2 | import * as fs from 'fs'; 3 | import path = require('path'); 4 | 5 | const removeImports = (s: string) => s.replace(/import\s(\n|\w|{|}|\s|,)*.*;(?! \/\/ keep)/gm, ''); 6 | const toTemplateString = (s: string) => '`' + s.replace(/\$\{/gm, '\\${').replace(/`/gm, '\\`') + '`'; 7 | 8 | const bundleFunctions = () => { 9 | const baseDirFunctions = path.join(process.cwd(), 'packages/graphql-zeus-core/TreeToTS/functions'); 10 | const baseDir = path.join(baseDirFunctions, 'new'); 11 | const directories = fs.readdirSync(baseDir); 12 | const allFunctions = directories 13 | .filter((d) => d !== 'mocks.ts') 14 | .filter((d) => !d.endsWith('spec.ts')) 15 | .map((d) => path.join(baseDir, d)) 16 | .map((d) => { 17 | const content = fs.readFileSync(d).toString('utf-8'); 18 | return removeImports(content).replace(/\\/gm, '\\\\').trim(); 19 | }); 20 | 21 | const subscriptionDir = path.join(baseDirFunctions, 'apiSubscription'); 22 | const subscriptionFunctions = fs.readdirSync(subscriptionDir).map((file) => { 23 | // The key is the filename without its extension 24 | const key = path.basename(file, '.ts'); 25 | const content = fs.readFileSync(path.join(subscriptionDir, file)).toString('utf-8'); 26 | const code = removeImports(content).replace(/\\/gm, '\\\\').trim(); 27 | return [key, code]; 28 | }); 29 | 30 | const content = ` 31 | export default ${toTemplateString(allFunctions.join('\n\n'))}; 32 | 33 | export const subscriptionFunctions = {${subscriptionFunctions 34 | .map(([key, value]) => JSON.stringify(key) + ': ' + toTemplateString(value)) 35 | .join(',\n')}}`; 36 | 37 | fs.writeFileSync(path.join(baseDirFunctions, 'generated.ts'), content); 38 | }; 39 | 40 | bundleFunctions(); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-zeus-repo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "license": "MIT", 6 | "description": "Generate Client Library for GraphQL Schema", 7 | "homepage": "https://github.com/graphql-editor/graphql-zeus", 8 | "readme": "https://github.com/graphql-editor/graphql-zeus#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 15 | }, 16 | "scripts": { 17 | "build": "npm run build -ws --if-present", 18 | "lint": "ttsc && eslint \"./packages/**/*.{ts,js}\" --quiet --fix", 19 | "run-example-typescript-node": "./run-example.sh examples/typescript-node", 20 | "produce-lib": "ts-node libBuilder.ts && eslint packages/graphql-zeus-core/TreeToTS/functions/generated.ts --quiet --fix", 21 | "cli": "node ./packages/graphql-zeus/lib/index.js", 22 | "test": "jest" 23 | }, 24 | "devDependencies": { 25 | "@commitlint/cli": "^8.3.5", 26 | "@commitlint/config-conventional": "^8.3.4", 27 | "@types/graphql": "^14.5.0", 28 | "@types/jest": "^29.1.2", 29 | "@types/node": "^22.10.3", 30 | "@types/node-fetch": "^2.3.7", 31 | "@types/ws": "^8.5.3", 32 | "@types/yargs": "^15.0.11", 33 | "@typescript-eslint/eslint-plugin": "^5.40.0", 34 | "@typescript-eslint/parser": "^5.40.0", 35 | "cz-conventional-changelog": "^3.1.0", 36 | "eslint": "^8.25.0", 37 | "eslint-config-prettier": "^8.5.0", 38 | "eslint-plugin-prettier": "^4.2.1", 39 | "graphql-ws": "^5.8.2", 40 | "jest": "^29.1.2", 41 | "prettier": "^2.7.1", 42 | "ts-jest": "^29.0.3", 43 | "ts-node": "^10.9.1", 44 | "ttypescript": "^1.5.15", 45 | "typescript": "4.8.4", 46 | "typescript-transform-paths": "^3.3.1" 47 | }, 48 | "workspaces": [ 49 | "./packages/graphql-zeus-jsonschema", 50 | "./packages/graphql-zeus-core", 51 | "./packages/graphql-zeus", 52 | "./examples/typescript-node" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/Models/Environment.ts: -------------------------------------------------------------------------------- 1 | export type Environment = 'browser' | 'node'; 2 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/Models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Environment'; 2 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TranslateGraphQL/index.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '@/Models'; 2 | import { Parser } from 'graphql-js-tree'; 3 | import { TreeToTS } from '@/TreeToTS'; 4 | 5 | export interface TranslateOptions { 6 | schema: string; 7 | env?: Environment; 8 | host?: string; 9 | esModule?: boolean; 10 | subscriptions?: 'legacy' | 'graphql-ws'; 11 | constEnums?: boolean; 12 | } 13 | 14 | export class TranslateGraphQL { 15 | static typescript = ({ schema, env = 'browser', host, subscriptions = 'legacy', constEnums }: TranslateOptions) => { 16 | const tree = Parser.parseAddExtensions(schema); 17 | const ts = TreeToTS.resolveTree({ tree, env, host, subscriptions, constEnums }); 18 | return ts; 19 | }; 20 | 21 | static typescriptSplit = ({ 22 | schema, 23 | env = 'browser', 24 | host, 25 | esModule, 26 | subscriptions = 'legacy', 27 | constEnums, 28 | }: TranslateOptions) => { 29 | const tree = Parser.parseAddExtensions(schema); 30 | const ts = TreeToTS.resolveTreeSplit({ tree, env, host, esModule, subscriptions, constEnums }); 31 | return { 32 | const: TreeToTS.resolveBasisHeader().concat(ts.const), 33 | index: TreeToTS.resolveBasisHeader().concat(ts.indexImports).concat('\n').concat(ts.index), 34 | }; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/apiSubscription/graphql-ws.ts: -------------------------------------------------------------------------------- 1 | import { chainOptions } from '@/TreeToTS/functions/new/models'; 2 | import { createClient, type Sink } from 'graphql-ws'; // keep 3 | 4 | export const apiSubscription = (options: chainOptions) => { 5 | const client = createClient({ 6 | url: String(options[0]), 7 | connectionParams: Object.fromEntries(new Headers(options[1]?.headers).entries()), 8 | }); 9 | 10 | const ws = new Proxy( 11 | { 12 | close: () => client.dispose(), 13 | } as WebSocket, 14 | { 15 | get(target, key) { 16 | if (key === 'close') return target.close; 17 | throw new Error(`Unimplemented property '${String(key)}', only 'close()' is available.`); 18 | }, 19 | }, 20 | ); 21 | 22 | return (query: string) => { 23 | let onMessage: ((event: any) => void) | undefined; 24 | let onError: Sink['error'] | undefined; 25 | let onClose: Sink['complete'] | undefined; 26 | 27 | client.subscribe( 28 | { query }, 29 | { 30 | next({ data }) { 31 | onMessage && onMessage(data); 32 | }, 33 | error(error) { 34 | onError && onError(error); 35 | }, 36 | complete() { 37 | onClose && onClose(); 38 | }, 39 | }, 40 | ); 41 | 42 | return { 43 | ws, 44 | on(listener: typeof onMessage) { 45 | onMessage = listener; 46 | }, 47 | error(listener: typeof onError) { 48 | onError = listener; 49 | }, 50 | open(listener: (socket: unknown) => void) { 51 | client.on('opened', listener); 52 | }, 53 | off(listener: typeof onClose) { 54 | onClose = listener; 55 | }, 56 | }; 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/apiSubscription/legacy.ts: -------------------------------------------------------------------------------- 1 | import { chainOptions } from '@/TreeToTS/functions/new/models'; 2 | 3 | export const apiSubscription = (options: chainOptions) => (query: string) => { 4 | try { 5 | const queryString = options[0] + '?query=' + encodeURIComponent(query); 6 | const wsString = queryString.replace('http', 'ws'); 7 | const host = (options.length > 1 && options[1]?.websocket?.[0]) || wsString; 8 | const webSocketOptions = options[1]?.websocket || [host]; 9 | const ws = new WebSocket(...webSocketOptions); 10 | return { 11 | ws, 12 | on: (e: (args: any) => void) => { 13 | ws.onmessage = (event: any) => { 14 | if (event.data) { 15 | const parsed = JSON.parse(event.data); 16 | const data = parsed.data; 17 | return e(data); 18 | } 19 | }; 20 | }, 21 | off: (e: (args: any) => void) => { 22 | ws.onclose = e; 23 | }, 24 | error: (e: (args: any) => void) => { 25 | ws.onerror = e; 26 | }, 27 | open: (e: () => void) => { 28 | ws.onopen = e; 29 | }, 30 | }; 31 | } catch { 32 | throw new Error('No websockets implemented'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/apiTest/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Chain, ZeusScalars, Selector } from '@/TreeToTS/functions/new/clientFunctions'; 2 | import { FromSelector } from '@/TreeToTS/functions/new/types'; 3 | import { $ } from '@/TreeToTS/functions/new/variableExtract'; 4 | 5 | const cardSelector = Selector('Card')({ name: true, id: true }); 6 | 7 | export type CardType = FromSelector; 8 | 9 | const scalars = ZeusScalars({ 10 | JSON: { 11 | decode: (e: unknown) => { 12 | if (typeof e === 'string') { 13 | return parseInt(e); 14 | } 15 | return undefined; 16 | }, 17 | }, 18 | }); 19 | 20 | export const test1 = async () => { 21 | const ch = await Chain('')('query', { 22 | scalars, 23 | })({ 24 | drawCard: { 25 | info: true, 26 | name: true, 27 | }, 28 | }); 29 | return ch; 30 | }; 31 | export const test2 = async () => { 32 | const sel = Selector('Card')({ 33 | name: $('name', 'String!'), 34 | }); 35 | const ch = await Chain('')('query', { 36 | scalars, 37 | })({ 38 | drawCard: sel, 39 | }); 40 | return ch; 41 | }; 42 | 43 | describe('empty test', () => { 44 | test('should pass', () => { 45 | return; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/apiFetch.ts: -------------------------------------------------------------------------------- 1 | import { fetchOptions, GraphQLError, GraphQLResponse } from '@/TreeToTS/functions/new/models'; 2 | 3 | const handleFetchResponse = (response: Response): Promise => { 4 | if (!response.ok) { 5 | return new Promise((_, reject) => { 6 | response 7 | .text() 8 | .then((text) => { 9 | try { 10 | reject(JSON.parse(text)); 11 | } catch (err) { 12 | reject(text); 13 | } 14 | }) 15 | .catch(reject); 16 | }); 17 | } 18 | return response.json() as Promise; 19 | }; 20 | 21 | export const apiFetch = 22 | (options: fetchOptions) => 23 | (query: string, variables: Record = {}) => { 24 | const fetchOptions = options[1] || {}; 25 | if (fetchOptions.method && fetchOptions.method === 'GET') { 26 | return fetch(`${options[0]}?query=${encodeURIComponent(query)}`, fetchOptions) 27 | .then(handleFetchResponse) 28 | .then((response: GraphQLResponse) => { 29 | if (response.errors) { 30 | throw new GraphQLError(response); 31 | } 32 | return response.data; 33 | }); 34 | } 35 | return fetch(`${options[0]}`, { 36 | body: JSON.stringify({ query, variables }), 37 | method: 'POST', 38 | headers: { 39 | 'Content-Type': 'application/json', 40 | }, 41 | ...fetchOptions, 42 | }) 43 | .then(handleFetchResponse) 44 | .then((response: GraphQLResponse) => { 45 | if (response.errors) { 46 | throw new GraphQLError(response); 47 | } 48 | return response.data; 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/buildQuery.spec.ts: -------------------------------------------------------------------------------- 1 | import { InternalsBuildQuery } from '@/TreeToTS/functions/new/buildQuery'; 2 | import { AllTypesProps, Ops, ReturnTypes } from '@/TreeToTS/functions/new/mocks'; 3 | import { $ } from '@/TreeToTS/functions/new/variableExtract'; 4 | import { replSpace } from '@/__tests__/TestUtils'; 5 | 6 | const builder = InternalsBuildQuery({ props: AllTypesProps, returns: ReturnTypes, ops: Ops }); 7 | 8 | describe('Test generated function buildQuery', () => { 9 | test('Simple query', () => { 10 | const matchExact = replSpace( 11 | builder('query', { 12 | cards: { 13 | name: true, 14 | age: true, 15 | bio: true, 16 | }, 17 | }), 18 | ); 19 | matchExact(`query{ 20 | cards{ 21 | name 22 | age 23 | bio 24 | } 25 | }`); 26 | }); 27 | test('Simple query with operation name', () => { 28 | const builder = InternalsBuildQuery({ 29 | props: AllTypesProps, 30 | returns: ReturnTypes, 31 | ops: Ops, 32 | options: { operationName: 'MyQuery' }, 33 | }); 34 | const matchExact = replSpace( 35 | builder('query', { 36 | cards: { 37 | name: true, 38 | age: true, 39 | bio: true, 40 | }, 41 | }), 42 | ); 43 | matchExact(`query MyQuery{ 44 | cards{ 45 | name 46 | age 47 | bio 48 | } 49 | }`); 50 | }); 51 | test('Query with arguments', () => { 52 | const matchExact = replSpace( 53 | builder('query', { 54 | cardById: [ 55 | { 56 | id: 'a1', 57 | name: 'blabla', 58 | age: 123, 59 | me: true, 60 | }, 61 | { 62 | name: true, 63 | age: true, 64 | bio: true, 65 | }, 66 | ], 67 | }), 68 | ); 69 | matchExact(`query{ 70 | cardById(id: "a1", name: "blabla", age: 123, me: true){ 71 | name 72 | age 73 | bio 74 | } 75 | }`); 76 | }); 77 | test('Query with array and object arguments', () => { 78 | const matchExact = replSpace( 79 | builder('query', { 80 | cards: [ 81 | { 82 | where: { active: true }, 83 | order_by: [{ date: 'asc' }, { age: 'desc' }], 84 | }, 85 | { 86 | name: true, 87 | age: true, 88 | bio: true, 89 | }, 90 | ], 91 | }), 92 | ); 93 | matchExact(`query{ 94 | cards(where: {active: true}, order_by: [{date: "asc"}, {age: "desc"}]){ 95 | name 96 | age 97 | bio 98 | } 99 | }`); 100 | }); 101 | test('Query with arguments and variables', () => { 102 | const builder = InternalsBuildQuery({ 103 | props: AllTypesProps, 104 | returns: ReturnTypes, 105 | ops: Ops, 106 | options: {}, 107 | }); 108 | const matchExact = replSpace( 109 | builder('query', { 110 | cardById: [ 111 | { 112 | id: $('id', 'String!'), 113 | name: 'blabla', 114 | age: 123, 115 | me: true, 116 | }, 117 | { 118 | name: true, 119 | age: true, 120 | bio: true, 121 | }, 122 | ], 123 | }), 124 | ); 125 | matchExact(`query($id: String!){ 126 | cardById(id: $id, name: "blabla", age: 123, me: true){ 127 | name 128 | age 129 | bio 130 | } 131 | }`); 132 | }); 133 | 134 | test('Query with empty arguments params', () => { 135 | const matchExact = replSpace( 136 | builder('query', { 137 | cards: [ 138 | {}, 139 | { 140 | name: true, 141 | age: true, 142 | bio: true, 143 | }, 144 | ], 145 | }), 146 | ); 147 | matchExact(`query{ 148 | cards { 149 | name 150 | age 151 | bio 152 | } 153 | }`); 154 | }); 155 | test('Mutation with arguments', () => { 156 | const enum Status { 157 | CREATED = 'CREATED', 158 | DELETED = 'DELETED', 159 | } 160 | const matchExact = replSpace( 161 | builder('mutation', { 162 | createCard: [ 163 | { 164 | card: { 165 | name: 'Hello', 166 | status: Status.CREATED, 167 | }, 168 | }, 169 | { 170 | name: true, 171 | }, 172 | ], 173 | }), 174 | ); 175 | matchExact(`mutation{ 176 | createCard(card:{ 177 | name: "Hello", 178 | status: CREATED 179 | }){ 180 | name 181 | } 182 | }`); 183 | }); 184 | test('Mutation with complicated string', () => { 185 | const complicated = 'lorem """ \' ipsum \n lorem ipsum'; 186 | const matchExact = replSpace( 187 | builder('mutation', { 188 | createCard: [ 189 | { 190 | card: { 191 | name: complicated, 192 | }, 193 | }, 194 | { 195 | name: true, 196 | }, 197 | ], 198 | }), 199 | ); 200 | matchExact(`mutation{ 201 | createCard(card:{ 202 | name: ${JSON.stringify(complicated)} 203 | }){ 204 | name 205 | } 206 | }`); 207 | }); 208 | 209 | test('Undefined param', () => { 210 | const Children = undefined; 211 | const matchExact = replSpace( 212 | builder('mutation', { 213 | addCard: [ 214 | { 215 | card: { 216 | Attack: 1, 217 | Children, 218 | }, 219 | }, 220 | { 221 | id: true, 222 | }, 223 | ], 224 | }), 225 | ); 226 | matchExact(`mutation { 227 | addCard(card: {Attack:1}){id} 228 | }`); 229 | }); 230 | test('Undefined getter', () => { 231 | const Children: boolean | undefined = undefined; 232 | const matchExact = replSpace( 233 | builder('mutation', { 234 | addCard: [ 235 | { 236 | card: { 237 | Attack: 1, 238 | }, 239 | }, 240 | { 241 | id: true, 242 | Children, 243 | }, 244 | ], 245 | }), 246 | ); 247 | matchExact(`mutation { 248 | addCard(card: {Attack:1}){id} 249 | }`); 250 | }); 251 | test('Simple query with alias', () => { 252 | const matchExact = replSpace( 253 | builder('query', { 254 | __alias: { 255 | play: { 256 | cards: { 257 | name: true, 258 | age: true, 259 | bio: true, 260 | }, 261 | }, 262 | }, 263 | }), 264 | ); 265 | matchExact(`query{ 266 | play:cards{ 267 | name 268 | age 269 | bio 270 | } 271 | }`); 272 | }); 273 | test('Query with multiple aliases', () => { 274 | const matchExact = replSpace( 275 | builder('query', { 276 | __alias: { 277 | play: { 278 | cards: { 279 | name: true, 280 | age: true, 281 | bio: true, 282 | }, 283 | }, 284 | shuffle: { 285 | cards: { 286 | name: true, 287 | age: true, 288 | bio: true, 289 | }, 290 | }, 291 | }, 292 | }), 293 | ); 294 | matchExact(`query{ 295 | play:cards{ 296 | name 297 | age 298 | bio 299 | } 300 | shuffle:cards{ 301 | name 302 | age 303 | bio 304 | } 305 | }`); 306 | }); 307 | test('Simple query with enums', () => { 308 | const enum Status { 309 | CREATED = 'CREATED', 310 | DELETED = 'DELETED', 311 | } 312 | const matchExact = replSpace( 313 | builder('query', { 314 | cardByStatus: [ 315 | { 316 | status: Status.CREATED, 317 | }, 318 | { 319 | name: true, 320 | age: true, 321 | bio: true, 322 | attack: [{ by: Status.CREATED }, { name: true }], 323 | }, 324 | ], 325 | }), 326 | ); 327 | matchExact(`query{ 328 | cardByStatus(status:CREATED){ 329 | name 330 | age 331 | bio 332 | attack(by: CREATED){ 333 | name 334 | } 335 | } 336 | }`); 337 | }); 338 | test('Simple query with scalars string encoder', () => { 339 | const customBuilder = InternalsBuildQuery({ 340 | props: AllTypesProps, 341 | returns: ReturnTypes, 342 | ops: Ops, 343 | scalars: { 344 | JSON: { 345 | encode: (e) => JSON.stringify(e), 346 | }, 347 | }, 348 | }); 349 | const settings = { 350 | mysettingcustom: { 351 | name: 'hello', 352 | values: [1, 2, 3], 353 | }, 354 | }; 355 | const matchExact = replSpace( 356 | customBuilder('mutation', { 357 | createCard: [ 358 | { 359 | card: { 360 | name: 'Hello', 361 | settings, 362 | }, 363 | }, 364 | { 365 | name: true, 366 | }, 367 | ], 368 | }), 369 | ); 370 | matchExact(`mutation{ 371 | createCard(card:{ 372 | name: "Hello", 373 | settings: ${JSON.stringify(settings)} 374 | }){ 375 | name 376 | } 377 | }`); 378 | }); 379 | test('Simple query with alias and enum', () => { 380 | const enum Status { 381 | CREATED = 'CREATED', 382 | DELETED = 'DELETED', 383 | } 384 | const matchExact = replSpace( 385 | builder('query', { 386 | __alias: { 387 | play: { 388 | cardByStatus: [ 389 | { 390 | status: Status.CREATED, 391 | }, 392 | { 393 | name: true, 394 | age: true, 395 | bio: true, 396 | attack: [{ by: Status.CREATED }, { name: true }], 397 | }, 398 | ], 399 | }, 400 | }, 401 | }), 402 | ); 403 | matchExact(`query{ 404 | play:cardByStatus(status:CREATED){ 405 | name 406 | age 407 | bio 408 | attack(by: CREATED){ 409 | name 410 | } 411 | } 412 | }`); 413 | }); 414 | test('Simple query with directives', () => { 415 | const builder = InternalsBuildQuery({ 416 | props: AllTypesProps, 417 | returns: ReturnTypes, 418 | ops: Ops, 419 | options: { operationName: 'MyQuery' }, 420 | }); 421 | const matchExact = replSpace( 422 | builder('query', { 423 | cards: { 424 | name: `@skip(if: true)`, 425 | age: true, 426 | bio: true, 427 | }, 428 | }), 429 | ); 430 | matchExact(`query MyQuery{ 431 | cards{ 432 | name @skip(if: true) 433 | age 434 | bio 435 | } 436 | }`); 437 | }); 438 | test('Simple query with directives on object', () => { 439 | const builder = InternalsBuildQuery({ 440 | props: AllTypesProps, 441 | returns: ReturnTypes, 442 | ops: Ops, 443 | options: { operationName: 'MyQuery' }, 444 | }); 445 | const matchExact = replSpace( 446 | builder('query', { 447 | cards: { 448 | __directives: `@skip(if: true)`, 449 | name: true, 450 | age: true, 451 | bio: true, 452 | }, 453 | }), 454 | ); 455 | matchExact(`query MyQuery{ 456 | cards @skip(if:true){ 457 | name 458 | age 459 | bio 460 | } 461 | }`); 462 | }); 463 | }); 464 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/buildQuery.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AllTypesPropsType, 3 | ReturnTypesType, 4 | InputValueType, 5 | VType, 6 | Operations, 7 | SEPARATOR, 8 | OperationOptions, 9 | } from '@/TreeToTS/functions/new/models'; 10 | import { purifyGraphQLKey } from '@/TreeToTS/functions/new/purifyGraphQLKey'; 11 | import { InternalArgsBuilt } from '@/TreeToTS/functions/new/resolvePath'; 12 | import { ScalarDefinition } from '@/TreeToTS/functions/new/types'; 13 | 14 | export const InternalsBuildQuery = ({ 15 | ops, 16 | props, 17 | returns, 18 | options, 19 | scalars, 20 | }: { 21 | props: AllTypesPropsType; 22 | returns: ReturnTypesType; 23 | ops: Operations; 24 | options?: OperationOptions; 25 | scalars?: ScalarDefinition; 26 | }) => { 27 | const ibb = ( 28 | k: string, 29 | o: InputValueType | VType, 30 | p = '', 31 | root = true, 32 | vars: Array<{ name: string; graphQLType: string }> = [], 33 | ): string => { 34 | const keyForPath = purifyGraphQLKey(k); 35 | const newPath = [p, keyForPath].join(SEPARATOR); 36 | if (!o) { 37 | return ''; 38 | } 39 | if (typeof o === 'boolean' || typeof o === 'number') { 40 | return k; 41 | } 42 | if (typeof o === 'string') { 43 | return `${k} ${o}`; 44 | } 45 | if (Array.isArray(o)) { 46 | const args = InternalArgsBuilt({ 47 | props, 48 | returns, 49 | ops, 50 | scalars, 51 | vars, 52 | })(o[0], newPath); 53 | return `${ibb(args ? `${k}(${args})` : k, o[1], p, false, vars)}`; 54 | } 55 | if (k === '__alias') { 56 | return Object.entries(o) 57 | .map(([alias, objectUnderAlias]) => { 58 | if (typeof objectUnderAlias !== 'object' || Array.isArray(objectUnderAlias)) { 59 | throw new Error( 60 | 'Invalid alias it should be __alias:{ YOUR_ALIAS_NAME: { OPERATION_NAME: { ...selectors }}}', 61 | ); 62 | } 63 | const operationName = Object.keys(objectUnderAlias)[0]; 64 | const operation = objectUnderAlias[operationName]; 65 | return ibb(`${alias}:${operationName}`, operation, p, false, vars); 66 | }) 67 | .join('\n'); 68 | } 69 | const hasOperationName = root && options?.operationName ? ' ' + options.operationName : ''; 70 | const keyForDirectives = o.__directives ?? ''; 71 | const query = `{${Object.entries(o) 72 | .filter(([k]) => k !== '__directives') 73 | .map((e) => ibb(...e, [p, `field<>${keyForPath}`].join(SEPARATOR), false, vars)) 74 | .join('\n')}}`; 75 | if (!root) { 76 | return `${k} ${keyForDirectives}${hasOperationName} ${query}`; 77 | } 78 | const varsString = vars.map((v) => `${v.name}: ${v.graphQLType}`).join(', '); 79 | return `${k} ${keyForDirectives}${hasOperationName}${varsString ? `(${varsString})` : ''} ${query}`; 80 | }; 81 | return ibb; 82 | }; 83 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/clientFunctions.ts: -------------------------------------------------------------------------------- 1 | import { InternalsBuildQuery } from '@/TreeToTS/functions/new/buildQuery'; 2 | import { decodeScalarsInResponse } from '@/TreeToTS/functions/new/decodeScalarsInResponse'; 3 | import { 4 | AllTypesProps, 5 | GraphQLTypes, 6 | Ops, 7 | ReturnTypes, 8 | ValueTypes, 9 | apiFetch, 10 | apiSubscription, 11 | HOST, 12 | ScalarCoders, 13 | ModelTypes, 14 | HEADERS, 15 | } from '@/TreeToTS/functions/new/mocks'; 16 | import { 17 | chainOptions, 18 | FetchFunction, 19 | GenericOperation, 20 | OperationOptions, 21 | SubscriptionFunction, 22 | ThunderGraphQLOptions, 23 | VType, 24 | } from '@/TreeToTS/functions/new/models'; 25 | import { InputType, ScalarDefinition, SelectionFunction, SubscriptionToGraphQL } from '@/TreeToTS/functions/new/types'; 26 | import { ExtractVariables } from '@/TreeToTS/functions/new/variableExtract'; 27 | 28 | type UnionOverrideKeys = Omit & U; 29 | 30 | export const Thunder = 31 | (fn: FetchFunction, thunderGraphQLOptions?: ThunderGraphQLOptions) => 32 | >( 33 | operation: O, 34 | graphqlOptions?: ThunderGraphQLOptions, 35 | ) => 36 | ( 37 | o: Z & { 38 | [P in keyof Z]: P extends keyof ValueTypes[R] ? Z[P] : never; 39 | }, 40 | ops?: OperationOptions & { variables?: Record }, 41 | ) => { 42 | const options = { 43 | ...thunderGraphQLOptions, 44 | ...graphqlOptions, 45 | }; 46 | return fn( 47 | Zeus(operation, o, { 48 | operationOptions: ops, 49 | scalars: options?.scalars, 50 | }), 51 | ops?.variables, 52 | ).then((data) => { 53 | if (options?.scalars) { 54 | return decodeScalarsInResponse({ 55 | response: data, 56 | initialOp: operation, 57 | initialZeusQuery: o as VType, 58 | returns: ReturnTypes, 59 | scalars: options.scalars, 60 | ops: Ops, 61 | }); 62 | } 63 | return data; 64 | }) as Promise>>; 65 | }; 66 | 67 | export const Chain = (...options: chainOptions) => Thunder(apiFetch(options)); 68 | 69 | export const SubscriptionThunder = 70 | (fn: SubscriptionFunction, thunderGraphQLOptions?: ThunderGraphQLOptions) => 71 | >( 72 | operation: O, 73 | graphqlOptions?: ThunderGraphQLOptions, 74 | ) => 75 | ( 76 | o: Z & { 77 | [P in keyof Z]: P extends keyof ValueTypes[R] ? Z[P] : never; 78 | }, 79 | ops?: OperationOptions & { variables?: ExtractVariables }, 80 | ) => { 81 | const options = { 82 | ...thunderGraphQLOptions, 83 | ...graphqlOptions, 84 | }; 85 | type CombinedSCLR = UnionOverrideKeys; 86 | const returnedFunction = fn( 87 | Zeus(operation, o, { 88 | operationOptions: ops, 89 | scalars: options?.scalars, 90 | }), 91 | ) as SubscriptionToGraphQL; 92 | if (returnedFunction?.on && options?.scalars) { 93 | const wrapped = returnedFunction.on; 94 | returnedFunction.on = (fnToCall: (args: InputType) => void) => 95 | wrapped((data: InputType) => { 96 | if (options?.scalars) { 97 | return fnToCall( 98 | decodeScalarsInResponse({ 99 | response: data, 100 | initialOp: operation, 101 | initialZeusQuery: o as VType, 102 | returns: ReturnTypes, 103 | scalars: options.scalars, 104 | ops: Ops, 105 | }), 106 | ); 107 | } 108 | return fnToCall(data); 109 | }); 110 | } 111 | return returnedFunction; 112 | }; 113 | 114 | export const Subscription = (...options: chainOptions) => SubscriptionThunder(apiSubscription(options)); 115 | export const Zeus = < 116 | Z extends ValueTypes[R], 117 | O extends keyof typeof Ops, 118 | R extends keyof ValueTypes = GenericOperation, 119 | >( 120 | operation: O, 121 | o: Z, 122 | ops?: { 123 | operationOptions?: OperationOptions; 124 | scalars?: ScalarDefinition; 125 | }, 126 | ) => 127 | InternalsBuildQuery({ 128 | props: AllTypesProps, 129 | returns: ReturnTypes, 130 | ops: Ops, 131 | options: ops?.operationOptions, 132 | scalars: ops?.scalars, 133 | })(operation, o as VType); 134 | 135 | export const ZeusSelect = () => ((t: unknown) => t) as SelectionFunction; 136 | 137 | export const Selector = (key: T) => key && ZeusSelect(); 138 | 139 | export const TypeFromSelector = (key: T) => key && ZeusSelect(); 140 | export const Gql = Chain(HOST, { 141 | headers: { 142 | 'Content-Type': 'application/json', 143 | ...HEADERS, 144 | }, 145 | }); 146 | 147 | export const ZeusScalars = ZeusSelect(); 148 | 149 | type BaseSymbol = number | string | undefined | boolean | null; 150 | 151 | type ScalarsSelector = { 152 | [X in Required<{ 153 | [P in keyof T]: P extends keyof V 154 | ? V[P] extends Array | undefined 155 | ? never 156 | : T[P] extends BaseSymbol | Array 157 | ? P 158 | : never 159 | : never; 160 | }>[keyof T]]: true; 161 | }; 162 | 163 | export const fields = (k: T) => { 164 | const t = ReturnTypes[k]; 165 | const fnType = k in AllTypesProps ? AllTypesProps[k as keyof typeof AllTypesProps] : undefined; 166 | const hasFnTypes = typeof fnType === 'object' ? fnType : undefined; 167 | const o = Object.fromEntries( 168 | Object.entries(t) 169 | .filter(([k, value]) => { 170 | const isFunctionType = hasFnTypes && k in hasFnTypes && !!hasFnTypes[k as keyof typeof hasFnTypes]; 171 | if (isFunctionType) return false; 172 | const isReturnType = ReturnTypes[value as string]; 173 | if (!isReturnType) return true; 174 | if (typeof isReturnType !== 'string') return false; 175 | if (isReturnType.startsWith('scalar.')) { 176 | return true; 177 | } 178 | return false; 179 | }) 180 | .map(([key]) => [key, true as const]), 181 | ); 182 | return o as ScalarsSelector; 183 | }; 184 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/decodeScalarsInResponse.spec.ts: -------------------------------------------------------------------------------- 1 | import { decodeScalarsInResponse } from '@/TreeToTS/functions/new/decodeScalarsInResponse'; 2 | import { Ops, ReturnTypes } from '@/TreeToTS/functions/new/mocks'; 3 | 4 | describe('Scalars in response get decoded', () => { 5 | test('JSON scalar decoded in response', () => { 6 | const cardInfo = { 7 | power: 9000, 8 | speed: 100, 9 | }; 10 | const response = { 11 | drawCard: { 12 | name: 'Adanos', 13 | info: JSON.stringify(cardInfo), 14 | }, 15 | }; 16 | const decodedResponse = decodeScalarsInResponse({ 17 | ops: Ops, 18 | response, 19 | returns: ReturnTypes, 20 | initialOp: 'query', 21 | initialZeusQuery: { 22 | drawCard: { 23 | name: true, 24 | info: true, 25 | }, 26 | }, 27 | scalars: { 28 | JSON: { 29 | decode: (e) => { 30 | return JSON.parse(e as string) as typeof cardInfo; 31 | }, 32 | }, 33 | }, 34 | }); 35 | expect(decodedResponse['drawCard']?.['info']).toEqual(cardInfo); 36 | }); 37 | test('JSON scalar decoded in response of mutation', () => { 38 | const cardInfo = { 39 | power: 9000, 40 | speed: 100, 41 | }; 42 | const response = { 43 | getCardAndPop: { 44 | name: 'Adanos', 45 | info: JSON.stringify(cardInfo), 46 | }, 47 | }; 48 | const decodedResponse = decodeScalarsInResponse({ 49 | ops: Ops, 50 | response, 51 | returns: ReturnTypes, 52 | initialOp: 'mutation', 53 | initialZeusQuery: { 54 | getCardAndPop: { 55 | name: true, 56 | info: true, 57 | }, 58 | }, 59 | scalars: { 60 | JSON: { 61 | decode: (e) => { 62 | return JSON.parse(e as string) as typeof cardInfo; 63 | }, 64 | }, 65 | }, 66 | }); 67 | expect(decodedResponse['getCardAndPop']?.['info']).toEqual(cardInfo); 68 | }); 69 | 70 | test('Inline fragments get decoded correctly', () => { 71 | const cardInfo = { 72 | power: 9001, 73 | speed: 100, 74 | }; 75 | const response = { 76 | drawCard: { 77 | name: 'Adanos', 78 | info: JSON.stringify(cardInfo), 79 | }, 80 | }; 81 | 82 | const decodedResponse = decodeScalarsInResponse({ 83 | ops: Ops, 84 | response, 85 | returns: ReturnTypes, 86 | initialOp: 'query', 87 | initialZeusQuery: { 88 | drawCard: { 89 | '...on Card': { 90 | name: true, 91 | info: true, 92 | }, 93 | }, 94 | }, 95 | scalars: { 96 | JSON: { 97 | decode: (e) => { 98 | return JSON.parse(e as string) as typeof cardInfo; 99 | }, 100 | }, 101 | }, 102 | }); 103 | expect(decodedResponse['drawCard']?.['info']).toEqual(cardInfo); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/decodeScalarsInResponse.ts: -------------------------------------------------------------------------------- 1 | import { InputValueType, Operations, ReturnTypesType, SEPARATOR, VType } from '@/TreeToTS/functions/new/models'; 2 | import { PrepareScalarPaths } from '@/TreeToTS/functions/new/prepareScalarPaths'; 3 | import { purifyGraphQLKey } from '@/TreeToTS/functions/new/purifyGraphQLKey'; 4 | import { ScalarResolver } from '@/TreeToTS/functions/new/types'; 5 | 6 | export const decodeScalarsInResponse = ({ 7 | response, 8 | scalars, 9 | returns, 10 | ops, 11 | initialZeusQuery, 12 | initialOp, 13 | }: { 14 | ops: O; 15 | response: any; 16 | returns: ReturnTypesType; 17 | scalars?: Record; 18 | initialOp: keyof O; 19 | initialZeusQuery: InputValueType | VType; 20 | }) => { 21 | if (!scalars) { 22 | return response; 23 | } 24 | const builder = PrepareScalarPaths({ 25 | ops, 26 | returns, 27 | }); 28 | 29 | const scalarPaths = builder(initialOp as string, ops[initialOp], initialZeusQuery); 30 | if (scalarPaths) { 31 | const r = traverseResponse({ scalarPaths, resolvers: scalars })(initialOp as string, response, [ops[initialOp]]); 32 | return r; 33 | } 34 | return response; 35 | }; 36 | 37 | export const traverseResponse = ({ 38 | resolvers, 39 | scalarPaths, 40 | }: { 41 | scalarPaths: { [x: string]: `scalar.${string}` }; 42 | resolvers: { 43 | [x: string]: ScalarResolver | undefined; 44 | }; 45 | }) => { 46 | const ibb = (k: string, o: InputValueType | VType, p: string[] = []): unknown => { 47 | if (Array.isArray(o)) { 48 | return o.map((eachO) => ibb(k, eachO, p)); 49 | } 50 | if (o == null) { 51 | return o; 52 | } 53 | const scalarPathString = p.join(SEPARATOR); 54 | const currentScalarString = scalarPaths[scalarPathString]; 55 | if (currentScalarString) { 56 | const currentDecoder = resolvers[currentScalarString.split('.')[1]]?.decode; 57 | if (currentDecoder) { 58 | return currentDecoder(o); 59 | } 60 | } 61 | if (typeof o === 'boolean' || typeof o === 'number' || typeof o === 'string' || !o) { 62 | return o; 63 | } 64 | const entries = Object.entries(o).map(([k, v]) => [k, ibb(k, v, [...p, purifyGraphQLKey(k)])] as const); 65 | const objectFromEntries = entries.reduce>((a, [k, v]) => { 66 | a[k] = v; 67 | return a; 68 | }, {}); 69 | return objectFromEntries; 70 | }; 71 | return ibb; 72 | }; 73 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/mocks.ts: -------------------------------------------------------------------------------- 1 | import { chainOptions, FetchFunction, SubscriptionFunction } from '@/TreeToTS/functions/new/models'; 2 | import { AliasType, ScalarResolver } from '@/TreeToTS/functions/new/types'; 3 | import { Variable } from '@/TreeToTS/functions/new/variableExtract'; 4 | 5 | export const AllTypesProps = { 6 | Query: { 7 | cardByStatus: { 8 | status: 'Status', 9 | }, 10 | cards: { 11 | attack: 'TypeOfAttack', 12 | }, 13 | }, 14 | Mutation: { 15 | createCard: { 16 | card: 'CreateCard', 17 | }, 18 | }, 19 | Card: { 20 | attack: { 21 | by: 'Status', 22 | }, 23 | }, 24 | CreateCard: { 25 | status: 'Status', 26 | settings: 'JSON', 27 | }, 28 | Status: 'enum' as const, 29 | TypeOfAttack: 'enum' as const, 30 | JSON: 'scalar.JSON' as const, 31 | }; 32 | 33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 34 | export const ReturnTypes: Record = { 35 | Query: { 36 | cardByStatus: 'Card', 37 | drawCard: 'Card', 38 | cards: 'Card', 39 | }, 40 | Mutation: { 41 | getCardAndPop: 'Card', 42 | }, 43 | Card: { 44 | attack: 'Card', 45 | info: 'JSON', 46 | }, 47 | JSON: 'scalar.JSON' as const, 48 | }; 49 | 50 | export const Ops = { 51 | query: 'Query' as const, 52 | mutation: 'Mutation' as const, 53 | }; 54 | 55 | export type ZEUS_INTERFACES = GraphQLTypes['Nameable']; 56 | export type ZEUS_UNIONS = GraphQLTypes['ChangeCard']; 57 | export type ZEUS_VARIABLES = { 58 | ['createCard']: ValueTypes['createCard']; 59 | ['JSON']: ValueTypes['JSON']; 60 | }; 61 | 62 | export type ValueTypes = { 63 | ['Nameable']: AliasType<{ 64 | name?: boolean | Variable; 65 | ['...on EffectCard']?: Omit; 66 | ['...on Card']?: Omit; 67 | ['...on SpecialCard']?: Omit; 68 | ['...on CardStack']?: Omit; 69 | __typename?: boolean | Variable; 70 | }>; 71 | ['JSON']: 'unknown'; 72 | /** Aws S3 File */ 73 | ['S3Object']: AliasType<{ 74 | bucket?: boolean | Variable; 75 | key?: boolean | Variable; 76 | region?: boolean | Variable; 77 | __typename?: boolean | Variable; 78 | }>; 79 | ['ChangeCard']: AliasType<{ 80 | ['...on SpecialCard']: ValueTypes['SpecialCard']; 81 | ['...on EffectCard']: ValueTypes['EffectCard']; 82 | __typename?: boolean | Variable; 83 | }>; 84 | ['EffectCard']: AliasType<{ 85 | effectSize?: boolean | Variable; 86 | name?: boolean | Variable; 87 | __typename?: boolean | Variable; 88 | }>; 89 | ['Subscription']: AliasType<{ 90 | deck?: ValueTypes['Card']; 91 | __typename?: boolean | Variable; 92 | }>; 93 | ['Query']: AliasType<{ 94 | cardById?: [{ cardId?: string | undefined | null }, ValueTypes['Card']]; 95 | /** Draw a card
*/ 96 | drawCard?: ValueTypes['Card']; 97 | drawChangeCard?: ValueTypes['ChangeCard']; 98 | /** list All Cards availble
*/ 99 | listCards?: ValueTypes['Card']; 100 | myStacks?: ValueTypes['CardStack']; 101 | nameables?: ValueTypes['Nameable']; 102 | __typename?: boolean | Variable; 103 | }>; 104 | /** Card used in card game
*/ 105 | ['Card']: AliasType<{ 106 | /** The attack power
*/ 107 | Attack?: boolean | Variable; 108 | /**
How many children the greek god had
*/ 109 | Children?: boolean | Variable; 110 | /** The defense power
*/ 111 | Defense?: boolean | Variable; 112 | attack?: [ 113 | { 114 | /** Attacked card/card ids
*/ cardID: string[] | undefined | null; 115 | }, 116 | ValueTypes['Card'], 117 | ]; 118 | /** Put your description here */ 119 | cardImage?: ValueTypes['S3Object']; 120 | /** Description of a card
*/ 121 | description?: boolean | Variable; 122 | id?: boolean | Variable; 123 | image?: boolean | Variable; 124 | info?: boolean | Variable; 125 | /** The name of a card
*/ 126 | name?: boolean | Variable; 127 | skills?: boolean | Variable; 128 | __typename?: boolean | Variable; 129 | }>; 130 | ['SpecialSkills']: SpecialSkills; 131 | ['SpecialCard']: AliasType<{ 132 | effect?: boolean | Variable; 133 | name?: boolean | Variable; 134 | __typename?: boolean | Variable; 135 | }>; 136 | ['Mutation']: AliasType<{ 137 | addCard?: [{ card: ValueTypes['createCard'] }, ValueTypes['Card']]; 138 | __typename?: boolean | Variable; 139 | }>; 140 | /** Stack of cards */ 141 | ['CardStack']: AliasType<{ 142 | cards?: ValueTypes['Card']; 143 | name?: boolean | Variable; 144 | __typename?: boolean | Variable; 145 | }>; 146 | /** create card inputs
*/ 147 | ['createCard']: { 148 | /** The defense power
*/ 149 | Defense: number; 150 | /** input skills */ 151 | skills?: ValueTypes['SpecialSkills'][]; 152 | /** The name of a card
*/ 153 | name: string; 154 | /** Description of a card
*/ 155 | description: string; 156 | /**
How many children the greek god had
*/ 157 | Children?: number | undefined | null; 158 | /** The attack power
*/ 159 | Attack: number; 160 | }; 161 | }; 162 | 163 | export type ModelTypes = { 164 | ['Nameable']: ModelTypes['EffectCard'] | ModelTypes['Card'] | ModelTypes['SpecialCard'] | ModelTypes['CardStack']; 165 | /** Aws S3 File */ 166 | ['S3Object']: { 167 | bucket: string; 168 | key: string; 169 | region: string; 170 | }; 171 | ['ChangeCard']: ModelTypes['SpecialCard'] | ModelTypes['EffectCard']; 172 | ['EffectCard']: { 173 | effectSize: number; 174 | name: string; 175 | }; 176 | ['Subscription']: { 177 | deck?: Array | undefined; 178 | }; 179 | ['Query']: { 180 | cardById?: GraphQLTypes['Card'] | undefined; 181 | /** Draw a card
*/ 182 | drawCard: GraphQLTypes['Card']; 183 | drawChangeCard: GraphQLTypes['ChangeCard']; 184 | /** list All Cards availble
*/ 185 | listCards: Array; 186 | myStacks?: Array | undefined; 187 | nameables: Array; 188 | }; 189 | /** Card used in card game
*/ 190 | ['Card']: { 191 | /** The attack power
*/ 192 | Attack: number; 193 | /**
How many children the greek god had
*/ 194 | Children?: number | undefined; 195 | /** The defense power
*/ 196 | Defense: number; 197 | /** Attack other cards on the table , returns Cards after attack
*/ 198 | attack?: Array | undefined; 199 | /** Put your description here */ 200 | cardImage?: GraphQLTypes['S3Object'] | undefined; 201 | /** Description of a card
*/ 202 | description: string; 203 | id: string; 204 | image: string; 205 | info?: GraphQLTypes['JSON'] | undefined; 206 | /** The name of a card
*/ 207 | name: string; 208 | skills?: Array | undefined; 209 | }; 210 | ['JSON']: unknown; 211 | ['SpecialSkills']: GraphQLTypes['SpecialSkills']; 212 | ['SpecialCard']: { 213 | effect: string; 214 | name: string; 215 | }; 216 | ['Mutation']: { 217 | /** add Card to Cards database
*/ 218 | addCard: GraphQLTypes['Card']; 219 | }; 220 | /** Stack of cards */ 221 | ['CardStack']: { 222 | cards?: Array | undefined; 223 | name: string; 224 | }; 225 | /** create card inputs
*/ 226 | ['createCard']: GraphQLTypes['createCard']; 227 | }; 228 | 229 | export type GraphQLTypes = { 230 | ['Nameable']: { 231 | __typename: 'EffectCard' | 'Card' | 'SpecialCard' | 'CardStack'; 232 | name: string; 233 | ['...on EffectCard']: '__union' & GraphQLTypes['EffectCard']; 234 | ['...on Card']: '__union' & GraphQLTypes['Card']; 235 | ['...on SpecialCard']: '__union' & GraphQLTypes['SpecialCard']; 236 | ['...on CardStack']: '__union' & GraphQLTypes['CardStack']; 237 | }; 238 | /** Aws S3 File */ 239 | ['S3Object']: { 240 | __typename: 'S3Object'; 241 | bucket: string; 242 | key: string; 243 | region: string; 244 | }; 245 | ['JSON']: 'scalar' & { name: 'JSON' }; 246 | ['ChangeCard']: { 247 | __typename: 'SpecialCard' | 'EffectCard'; 248 | ['...on SpecialCard']: '__union' & GraphQLTypes['SpecialCard']; 249 | ['...on EffectCard']: '__union' & GraphQLTypes['EffectCard']; 250 | }; 251 | ['EffectCard']: { 252 | __typename: 'EffectCard'; 253 | effectSize: number; 254 | name: string; 255 | }; 256 | ['Subscription']: { 257 | __typename: 'Subscription'; 258 | deck?: Array | undefined; 259 | }; 260 | ['Query']: { 261 | __typename: 'Query'; 262 | cardById?: GraphQLTypes['Card'] | undefined; 263 | /** Draw a card
*/ 264 | drawCard: GraphQLTypes['Card']; 265 | drawChangeCard: GraphQLTypes['ChangeCard']; 266 | /** list All Cards availble
*/ 267 | listCards: Array; 268 | myStacks?: Array | undefined; 269 | nameables: Array; 270 | }; 271 | /** Card used in card game
*/ 272 | ['Card']: { 273 | __typename: 'Card'; 274 | /** The attack power
*/ 275 | Attack: number; 276 | /**
How many children the greek god had
*/ 277 | Children?: number | undefined; 278 | /** The defense power
*/ 279 | Defense: number; 280 | /** Attack other cards on the table , returns Cards after attack
*/ 281 | attack?: Array | undefined; 282 | /** Put your description here */ 283 | cardImage?: GraphQLTypes['S3Object'] | undefined; 284 | /** Description of a card
*/ 285 | description: string; 286 | id: string; 287 | image: string; 288 | info?: GraphQLTypes['JSON'] | undefined; 289 | /** The name of a card
*/ 290 | name: string; 291 | skills?: Array | undefined; 292 | }; 293 | ['SpecialSkills']: SpecialSkills; 294 | ['SpecialCard']: { 295 | __typename: 'SpecialCard'; 296 | effect: string; 297 | name: string; 298 | }; 299 | ['Mutation']: { 300 | __typename: 'Mutation'; 301 | /** add Card to Cards database
*/ 302 | addCard: GraphQLTypes['Card']; 303 | }; 304 | /** Stack of cards */ 305 | ['CardStack']: { 306 | __typename: 'CardStack'; 307 | cards?: Array | undefined; 308 | name: string; 309 | }; 310 | /** create card inputs
*/ 311 | ['createCard']: { 312 | /** The defense power
*/ 313 | Defense: number; 314 | /** input skills */ 315 | skills?: Array | undefined; 316 | /** The name of a card
*/ 317 | name: string; 318 | /** Description of a card
*/ 319 | description: string; 320 | /**
How many children the greek god had
*/ 321 | Children?: number | undefined; 322 | /** The attack power
*/ 323 | Attack: number; 324 | }; 325 | }; 326 | export const enum SpecialSkills { 327 | THUNDER = 'THUNDER', 328 | RAIN = 'RAIN', 329 | FIRE = 'FIRE', 330 | } 331 | 332 | export type ScalarCoders = { 333 | JSON?: ScalarResolver; 334 | }; 335 | 336 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 337 | export const apiSubscription = (opts: chainOptions) => ((q: string) => 1) as unknown as SubscriptionFunction; 338 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 339 | export const apiFetch = (options: chainOptions) => ((q: string, vars?: unknown) => 1) as unknown as FetchFunction; 340 | export const HOST = 'https://faker.graphqleditor.com/a-team/olympus/graphql'; 341 | export const HEADERS = {}; 342 | 343 | export type ResolverInputTypes = { 344 | ['Query']: AliasType<{ 345 | cardById?: [{ cardId?: string | undefined | null }, ResolverInputTypes['Card']]; 346 | /** Draw a card
*/ 347 | drawCard?: ResolverInputTypes['Card']; 348 | drawChangeCard?: ResolverInputTypes['ChangeCard']; 349 | /** list All Cards availble
*/ 350 | listCards?: ResolverInputTypes['Card']; 351 | myStacks?: ResolverInputTypes['CardStack']; 352 | nameables?: ResolverInputTypes['Nameable']; 353 | __typename?: boolean | `@${string}`; 354 | }>; 355 | ['SpecialSkills']: SpecialSkills; 356 | ['EffectCard']: AliasType<{ 357 | effectSize?: boolean | `@${string}`; 358 | name?: boolean | `@${string}`; 359 | __typename?: boolean | `@${string}`; 360 | }>; 361 | /** create card inputs
*/ 362 | ['createCard']: { 363 | /** input skills */ 364 | skills?: Array | undefined | null; 365 | /** The name of a card
*/ 366 | name: string; 367 | /** Description of a card
*/ 368 | description: string; 369 | /**
How many children the greek god had
*/ 370 | Children?: number | undefined | null; 371 | /** The attack power
*/ 372 | Attack: number; 373 | /** The defense power
*/ 374 | Defense: number; 375 | }; 376 | /** Aws S3 File */ 377 | ['S3Object']: AliasType<{ 378 | bucket?: boolean | `@${string}`; 379 | key?: boolean | `@${string}`; 380 | region?: boolean | `@${string}`; 381 | __typename?: boolean | `@${string}`; 382 | }>; 383 | ['ChangeCard']: AliasType<{ 384 | SpecialCard?: ResolverInputTypes['SpecialCard']; 385 | EffectCard?: ResolverInputTypes['EffectCard']; 386 | __typename?: boolean | `@${string}`; 387 | }>; 388 | ['SpecialCard']: AliasType<{ 389 | effect?: boolean | `@${string}`; 390 | name?: boolean | `@${string}`; 391 | __typename?: boolean | `@${string}`; 392 | }>; 393 | /** Card used in card game
*/ 394 | ['Card']: AliasType<{ 395 | /** The attack power
*/ 396 | Attack?: boolean | `@${string}`; 397 | /**
How many children the greek god had
*/ 398 | Children?: boolean | `@${string}`; 399 | /** The defense power
*/ 400 | Defense?: boolean | `@${string}`; 401 | attack?: [ 402 | { 403 | /** Attacked card/card ids
*/ cardID: Array; 404 | }, 405 | ResolverInputTypes['Card'], 406 | ]; 407 | /** Put your description here */ 408 | cardImage?: ResolverInputTypes['S3Object']; 409 | /** Description of a card
*/ 410 | description?: boolean | `@${string}`; 411 | id?: boolean | `@${string}`; 412 | image?: boolean | `@${string}`; 413 | info?: boolean | `@${string}`; 414 | /** The name of a card
*/ 415 | name?: boolean | `@${string}`; 416 | skills?: boolean | `@${string}`; 417 | __typename?: boolean | `@${string}`; 418 | }>; 419 | ['Nameable']: AliasType<{ 420 | name?: boolean | `@${string}`; 421 | ['...on EffectCard']?: Omit; 422 | ['...on SpecialCard']?: Omit; 423 | ['...on Card']?: Omit; 424 | ['...on CardStack']?: Omit; 425 | __typename?: boolean | `@${string}`; 426 | }>; 427 | /** Stack of cards */ 428 | ['CardStack']: AliasType<{ 429 | cards?: ResolverInputTypes['Card']; 430 | name?: boolean | `@${string}`; 431 | __typename?: boolean | `@${string}`; 432 | }>; 433 | ['Mutation']: AliasType<{ 434 | addCard?: [{ card: ResolverInputTypes['createCard'] }, ResolverInputTypes['Card']]; 435 | __typename?: boolean | `@${string}`; 436 | }>; 437 | ['JSON']: unknown; 438 | ['Subscription']: AliasType<{ 439 | deck?: ResolverInputTypes['Card']; 440 | __typename?: boolean | `@${string}`; 441 | }>; 442 | }; 443 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/models.ts: -------------------------------------------------------------------------------- 1 | import { Ops, ScalarCoders } from '@/TreeToTS/functions/new/mocks'; 2 | import { ScalarDefinition } from '@/TreeToTS/functions/new/types'; 3 | 4 | export type AllTypesPropsType = { 5 | [x: string]: 6 | | undefined 7 | | `scalar.${string}` 8 | | 'enum' 9 | | { 10 | [x: string]: 11 | | undefined 12 | | string 13 | | { 14 | [x: string]: string | undefined; 15 | }; 16 | }; 17 | }; 18 | 19 | export type ReturnTypesType = { 20 | [x: string]: 21 | | { 22 | [x: string]: string | undefined; 23 | } 24 | | `scalar.${string}` 25 | | undefined; 26 | }; 27 | export type InputValueType = { 28 | [x: string]: undefined | boolean | string | number | [any, undefined | boolean | InputValueType] | InputValueType; 29 | }; 30 | export type VType = 31 | | undefined 32 | | boolean 33 | | string 34 | | number 35 | | [any, undefined | boolean | InputValueType] 36 | | InputValueType; 37 | 38 | export type PlainType = boolean | number | string | null | undefined; 39 | export type ZeusArgsType = 40 | | PlainType 41 | | { 42 | [x: string]: ZeusArgsType; 43 | } 44 | | Array; 45 | 46 | export type Operations = Record; 47 | 48 | export type VariableDefinition = { 49 | [x: string]: unknown; 50 | }; 51 | 52 | export const SEPARATOR = '|'; 53 | 54 | export type fetchOptions = Parameters; 55 | type websocketOptions = typeof WebSocket extends new (...args: infer R) => WebSocket ? R : never; 56 | export type chainOptions = [fetchOptions[0], fetchOptions[1] & { websocket?: websocketOptions }] | [fetchOptions[0]]; 57 | export type FetchFunction = (query: string, variables?: Record) => Promise; 58 | export type SubscriptionFunction = (query: string) => any; 59 | type NotUndefined = T extends undefined ? never : T; 60 | export type ResolverType = NotUndefined; 61 | 62 | export type OperationOptions = { 63 | operationName?: string; 64 | }; 65 | 66 | export type ScalarCoder = Record string>; 67 | 68 | export interface GraphQLResponse { 69 | data?: Record; 70 | errors?: Array<{ 71 | message: string; 72 | }>; 73 | } 74 | export class GraphQLError extends Error { 75 | constructor(public response: GraphQLResponse) { 76 | super(''); 77 | console.error(response); 78 | } 79 | toString() { 80 | return 'GraphQL Response Error'; 81 | } 82 | } 83 | export type GenericOperation = O extends keyof typeof Ops ? typeof Ops[O] : never; 84 | export type ThunderGraphQLOptions = { 85 | scalars?: SCLR | ScalarCoders; 86 | }; 87 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/prepareScalarPaths.spec.ts: -------------------------------------------------------------------------------- 1 | import { Ops, ReturnTypes } from '@/TreeToTS/functions/new/mocks'; 2 | import { PrepareScalarPaths } from '@/TreeToTS/functions/new/prepareScalarPaths'; 3 | 4 | const builder = PrepareScalarPaths({ returns: ReturnTypes, ops: Ops }); 5 | 6 | describe('Test PrepareScalarPaths function', () => { 7 | test('Simple query', () => { 8 | const matchExact = builder('query', 'Query', { 9 | cards: { 10 | name: true, 11 | age: true, 12 | info: true, 13 | bio: true, 14 | }, 15 | }); 16 | const o = { 17 | 'Query|cards|info': 'scalar.JSON', 18 | }; 19 | expect(o).toEqual(matchExact); 20 | }); 21 | test('Discards inline fragment from path', () => { 22 | const matchExact = builder('query', 'Query', { 23 | cards: { 24 | '... on Card': { 25 | info: true, 26 | }, 27 | }, 28 | }); 29 | const o = { 30 | 'Query|cards|info': 'scalar.JSON', 31 | }; 32 | expect(o).toEqual(matchExact); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/prepareScalarPaths.ts: -------------------------------------------------------------------------------- 1 | import { ReturnTypesType, InputValueType, VType, Operations, SEPARATOR } from '@/TreeToTS/functions/new/models'; 2 | import { purifyGraphQLKey } from '@/TreeToTS/functions/new/purifyGraphQLKey'; 3 | 4 | const ExtractScalar = (mappedParts: string[], returns: ReturnTypesType): `scalar.${string}` | undefined => { 5 | if (mappedParts.length === 0) { 6 | return; 7 | } 8 | const oKey = mappedParts[0]; 9 | const returnP1 = returns[oKey]; 10 | if (typeof returnP1 === 'object') { 11 | const returnP2 = returnP1[mappedParts[1]]; 12 | if (returnP2) { 13 | return ExtractScalar([returnP2, ...mappedParts.slice(2)], returns); 14 | } 15 | return undefined; 16 | } 17 | return returnP1 as `scalar.${string}` | undefined; 18 | }; 19 | 20 | export const PrepareScalarPaths = ({ ops, returns }: { returns: ReturnTypesType; ops: Operations }) => { 21 | const ibb = ( 22 | k: string, 23 | originalKey: string, 24 | o: InputValueType | VType, 25 | p: string[] = [], 26 | pOriginals: string[] = [], 27 | root = true, 28 | ): { [x: string]: `scalar.${string}` } | undefined => { 29 | if (!o) { 30 | return; 31 | } 32 | if (typeof o === 'boolean' || typeof o === 'number' || typeof o === 'string') { 33 | const extractionArray = [...pOriginals, originalKey]; 34 | const isScalar = ExtractScalar(extractionArray, returns); 35 | if (isScalar?.startsWith('scalar')) { 36 | const partOfTree = { 37 | [[...p, k].join(SEPARATOR)]: isScalar, 38 | }; 39 | return partOfTree; 40 | } 41 | return {}; 42 | } 43 | if (Array.isArray(o)) { 44 | return ibb(k, k, o[1], p, pOriginals, false); 45 | } 46 | if (k === '__alias') { 47 | return Object.entries(o) 48 | .map(([alias, objectUnderAlias]) => { 49 | if (typeof objectUnderAlias !== 'object' || Array.isArray(objectUnderAlias)) { 50 | throw new Error( 51 | 'Invalid alias it should be __alias:{ YOUR_ALIAS_NAME: { OPERATION_NAME: { ...selectors }}}', 52 | ); 53 | } 54 | const operationName = Object.keys(objectUnderAlias)[0]; 55 | const operation = objectUnderAlias[operationName]; 56 | return ibb(alias, operationName, operation, p, pOriginals, false); 57 | }) 58 | .reduce((a, b) => ({ 59 | ...a, 60 | ...b, 61 | })); 62 | } 63 | const keyName = root ? ops[k] : k; 64 | return Object.entries(o) 65 | .filter(([k]) => k !== '__directives') 66 | .map(([k, v]) => { 67 | // Inline fragments shouldn't be added to the path as they aren't a field 68 | const isInlineFragment = originalKey.match(/^...\s*on/) != null; 69 | return ibb( 70 | k, 71 | k, 72 | v, 73 | isInlineFragment ? p : [...p, purifyGraphQLKey(keyName || k)], 74 | isInlineFragment ? pOriginals : [...pOriginals, purifyGraphQLKey(originalKey)], 75 | false, 76 | ); 77 | }) 78 | .reduce((a, b) => ({ 79 | ...a, 80 | ...b, 81 | })); 82 | }; 83 | return ibb; 84 | }; 85 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/purifyGraphQLKey.spec.ts: -------------------------------------------------------------------------------- 1 | import { purifyGraphQLKey } from '@/TreeToTS/functions/new/purifyGraphQLKey'; 2 | 3 | describe('Test purify graphql keys', () => { 4 | test('Remove parentheses', () => { 5 | const t = purifyGraphQLKey('addPerson(name:"Joe")'); 6 | expect(t).toEqual('addPerson'); 7 | }); 8 | test('Remove GraphQL alias', () => { 9 | const t = purifyGraphQLKey('friends:getMyPeople'); 10 | expect(t).toEqual('getMyPeople'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/purifyGraphQLKey.ts: -------------------------------------------------------------------------------- 1 | export const purifyGraphQLKey = (k: string) => k.replace(/\([^)]*\)/g, '').replace(/^[^:]*\:/g, ''); 2 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/resolvePath.spec.ts: -------------------------------------------------------------------------------- 1 | import { AllTypesProps, Ops, ReturnTypes } from '@/TreeToTS/functions/new/mocks'; 2 | import { ResolveFromPath } from '@/TreeToTS/functions/new/resolvePath'; 3 | 4 | const resolver = ResolveFromPath(AllTypesProps, ReturnTypes, Ops); 5 | 6 | describe(`Resolves correct type from pregenerated AllTypesProps and ReturnTypes`, () => { 7 | test('It correctly resolves path for field argument', () => { 8 | const r = resolver('|field<>Query|field<>cardByStatus|status'); 9 | expect(r).toEqual('enum'); 10 | }); 11 | test('It correctly resolves path for input field, which is enum', () => { 12 | const r = resolver('|field<>Mutation|field<>createCard|card|status'); 13 | expect(r).toEqual('enum'); 14 | }); 15 | test('It correctly resolves path for input field, which is scalar', () => { 16 | const r = resolver('|field<>Mutation|field<>createCard|card|settings'); 17 | expect(r).toEqual('scalar.JSON'); 18 | }); 19 | test('It correctly resolves path for input field which is not enum', () => { 20 | const r = resolver('|field<>Mutation|field<>createCard|card|name'); 21 | expect(r).toEqual('not'); 22 | }); 23 | test('It correctly resolves path for TypeOfAttack enum', () => { 24 | const r = resolver('|field<>Query|field<>cards|attack'); 25 | expect(r).toEqual('enum'); 26 | }); 27 | test('It correctly resolves path for type field argument', () => { 28 | const r = resolver('|field<>Query|field<>cards|field<>attack|by'); 29 | expect(r).toEqual('enum'); 30 | }); 31 | test('It correctly resolves path for nested type field argument', () => { 32 | const r = resolver('|field<>Query|field<>cards|field<>attack|field<>attack|by'); 33 | expect(r).toEqual('enum'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/resolvePath.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AllTypesPropsType, 3 | Operations, 4 | ReturnTypesType, 5 | SEPARATOR, 6 | ZeusArgsType, 7 | } from '@/TreeToTS/functions/new/models'; 8 | import { ScalarDefinition } from '@/TreeToTS/functions/new/types'; 9 | import { GRAPHQL_TYPE_SEPARATOR, START_VAR_NAME } from '@/TreeToTS/functions/new/variableExtract'; 10 | 11 | const mapPart = (p: string) => { 12 | const [isArg, isField] = p.split('<>'); 13 | if (isField) { 14 | return { 15 | v: isField, 16 | __type: 'field', 17 | } as const; 18 | } 19 | return { 20 | v: isArg, 21 | __type: 'arg', 22 | } as const; 23 | }; 24 | 25 | type Part = ReturnType; 26 | 27 | export const ResolveFromPath = (props: AllTypesPropsType, returns: ReturnTypesType, ops: Operations) => { 28 | const ResolvePropsType = (mappedParts: Part[]) => { 29 | const oKey = ops[mappedParts[0].v]; 30 | const propsP1 = oKey ? props[oKey] : props[mappedParts[0].v]; 31 | if (propsP1 === 'enum' && mappedParts.length === 1) { 32 | return 'enum'; 33 | } 34 | if (typeof propsP1 === 'string' && propsP1.startsWith('scalar.') && mappedParts.length === 1) { 35 | return propsP1; 36 | } 37 | if (typeof propsP1 === 'object') { 38 | if (mappedParts.length < 2) { 39 | return 'not'; 40 | } 41 | const propsP2 = propsP1[mappedParts[1].v]; 42 | if (typeof propsP2 === 'string') { 43 | return rpp( 44 | `${propsP2}${SEPARATOR}${mappedParts 45 | .slice(2) 46 | .map((mp) => mp.v) 47 | .join(SEPARATOR)}`, 48 | ); 49 | } 50 | if (typeof propsP2 === 'object') { 51 | if (mappedParts.length < 3) { 52 | return 'not'; 53 | } 54 | const propsP3 = propsP2[mappedParts[2].v]; 55 | if (propsP3 && mappedParts[2].__type === 'arg') { 56 | return rpp( 57 | `${propsP3}${SEPARATOR}${mappedParts 58 | .slice(3) 59 | .map((mp) => mp.v) 60 | .join(SEPARATOR)}`, 61 | ); 62 | } 63 | } 64 | } 65 | }; 66 | const ResolveReturnType = (mappedParts: Part[]) => { 67 | if (mappedParts.length === 0) { 68 | return 'not'; 69 | } 70 | const oKey = ops[mappedParts[0].v]; 71 | const returnP1 = oKey ? returns[oKey] : returns[mappedParts[0].v]; 72 | if (typeof returnP1 === 'object') { 73 | if (mappedParts.length < 2) return 'not'; 74 | const returnP2 = returnP1[mappedParts[1].v]; 75 | if (returnP2) { 76 | return rpp( 77 | `${returnP2}${SEPARATOR}${mappedParts 78 | .slice(2) 79 | .map((mp) => mp.v) 80 | .join(SEPARATOR)}`, 81 | ); 82 | } 83 | } 84 | }; 85 | const rpp = (path: string): 'enum' | 'not' | `scalar.${string}` => { 86 | const parts = path.split(SEPARATOR).filter((l) => l.length > 0); 87 | const mappedParts = parts.map(mapPart); 88 | const propsP1 = ResolvePropsType(mappedParts); 89 | if (propsP1) { 90 | return propsP1; 91 | } 92 | const returnP1 = ResolveReturnType(mappedParts); 93 | if (returnP1) { 94 | return returnP1; 95 | } 96 | return 'not'; 97 | }; 98 | return rpp; 99 | }; 100 | 101 | export const InternalArgsBuilt = ({ 102 | props, 103 | ops, 104 | returns, 105 | scalars, 106 | vars, 107 | }: { 108 | props: AllTypesPropsType; 109 | returns: ReturnTypesType; 110 | ops: Operations; 111 | scalars?: ScalarDefinition; 112 | vars: Array<{ name: string; graphQLType: string }>; 113 | }) => { 114 | const arb = (a: ZeusArgsType, p = '', root = true): string => { 115 | if (typeof a === 'string') { 116 | if (a.startsWith(START_VAR_NAME)) { 117 | const [varName, graphQLType] = a.replace(START_VAR_NAME, '$').split(GRAPHQL_TYPE_SEPARATOR); 118 | const v = vars.find((v) => v.name === varName); 119 | if (!v) { 120 | vars.push({ 121 | name: varName, 122 | graphQLType, 123 | }); 124 | } else { 125 | if (v.graphQLType !== graphQLType) { 126 | throw new Error( 127 | `Invalid variable exists with two different GraphQL Types, "${v.graphQLType}" and ${graphQLType}`, 128 | ); 129 | } 130 | } 131 | return varName; 132 | } 133 | } 134 | const checkType = ResolveFromPath(props, returns, ops)(p); 135 | if (checkType.startsWith('scalar.')) { 136 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 137 | const [_, ...splittedScalar] = checkType.split('.'); 138 | const scalarKey = splittedScalar.join('.'); 139 | return (scalars?.[scalarKey]?.encode?.(a) as string) || JSON.stringify(a); 140 | } 141 | if (Array.isArray(a)) { 142 | return `[${a.map((arr) => arb(arr, p, false)).join(', ')}]`; 143 | } 144 | if (typeof a === 'string') { 145 | if (checkType === 'enum') { 146 | return a; 147 | } 148 | return `${JSON.stringify(a)}`; 149 | } 150 | if (typeof a === 'object') { 151 | if (a === null) { 152 | return `null`; 153 | } 154 | const returnedObjectString = Object.entries(a) 155 | .filter(([, v]) => typeof v !== 'undefined') 156 | .map(([k, v]) => `${k}: ${arb(v, [p, k].join(SEPARATOR), false)}`) 157 | .join(',\n'); 158 | if (!root) { 159 | return `{${returnedObjectString}}`; 160 | } 161 | return returnedObjectString; 162 | } 163 | return `${a}`; 164 | }; 165 | return arb; 166 | }; 167 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/resolverFor.ts: -------------------------------------------------------------------------------- 1 | import { ModelTypes, ResolverInputTypes } from '@/TreeToTS/functions/new/mocks'; 2 | 3 | export const resolverFor = ( 4 | type: T, 5 | field: Z, 6 | fn: ( 7 | args: Required[Z] extends [infer Input, any] ? Input : any, 8 | source: any, 9 | ) => Z extends keyof ModelTypes[T] ? ModelTypes[T][Z] | Promise | X : never, 10 | ) => fn as (args?: any, source?: any) => ReturnType; 11 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/types.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLTypes, ZEUS_VARIABLES, ZEUS_INTERFACES, ZEUS_UNIONS, ValueTypes } from '@/TreeToTS/functions/new/mocks'; 2 | import { Variable } from '@/TreeToTS/functions/new/variableExtract'; 3 | 4 | export type UnwrapPromise = T extends Promise ? R : T; 5 | export type ZeusState Promise> = NonNullable>>; 6 | export type ZeusHook< 7 | T extends (...args: any[]) => Record Promise>, 8 | N extends keyof ReturnType, 9 | > = ZeusState[N]>; 10 | 11 | export type WithTypeNameValue = T & { 12 | __typename?: boolean; 13 | __directives?: string; 14 | }; 15 | export type AliasType = WithTypeNameValue & { 16 | __alias?: Record>; 17 | }; 18 | type DeepAnify = { 19 | [P in keyof T]?: any; 20 | }; 21 | type IsPayLoad = T extends [any, infer PayLoad] ? PayLoad : T; 22 | export type ScalarDefinition = Record; 23 | 24 | type IsScalar = S extends 'scalar' & { name: infer T } 25 | ? T extends keyof SCLR 26 | ? SCLR[T]['decode'] extends (s: unknown) => unknown 27 | ? ReturnType 28 | : unknown 29 | : unknown 30 | : S extends Array 31 | ? Array> 32 | : S; 33 | 34 | type IsArray = T extends Array 35 | ? InputType[] 36 | : InputType; 37 | type FlattenArray = T extends Array ? R : T; 38 | type BaseZeusResolver = boolean | 1 | string | Variable; 39 | 40 | type IsInterfaced, DST, SCLR extends ScalarDefinition> = FlattenArray extends 41 | | ZEUS_INTERFACES 42 | | ZEUS_UNIONS 43 | ? { 44 | [P in keyof SRC]: SRC[P] extends '__union' & infer R 45 | ? P extends keyof DST 46 | ? IsArray 47 | : IsArray, SCLR> 48 | : never; 49 | }[keyof SRC] & { 50 | [P in keyof Omit< 51 | Pick< 52 | SRC, 53 | { 54 | [P in keyof DST]: SRC[P] extends '__union' & infer R ? never : P; 55 | }[keyof DST] 56 | >, 57 | '__typename' 58 | >]: IsPayLoad extends BaseZeusResolver ? IsScalar : IsArray; 59 | } 60 | : { 61 | [P in keyof Pick]: IsPayLoad extends BaseZeusResolver 62 | ? IsScalar 63 | : IsArray; 64 | }; 65 | 66 | export type MapType = SRC extends DeepAnify 67 | ? IsInterfaced 68 | : never; 69 | // eslint-disable-next-line @typescript-eslint/ban-types 70 | export type InputType = IsPayLoad extends { __alias: infer R } 71 | ? { 72 | [P in keyof R]: MapType[keyof MapType]; 73 | } & MapType, '__alias'>, SCLR> 74 | : MapType, SCLR>; 75 | export type SubscriptionToGraphQL = { 76 | ws: WebSocket; 77 | on: (fn: (args: InputType) => void) => void; 78 | off: (fn: (e: { data?: InputType; code?: number; reason?: string; message?: string }) => void) => void; 79 | error: (fn: (e: { data?: InputType; errors?: string[] }) => void) => void; 80 | open: () => void; 81 | }; 82 | 83 | // eslint-disable-next-line @typescript-eslint/ban-types 84 | export type FromSelector = InputType< 85 | GraphQLTypes[NAME], 86 | SELECTOR, 87 | SCLR 88 | >; 89 | 90 | export type ScalarResolver = { 91 | encode?: (s: unknown) => string; 92 | decode?: (s: unknown) => unknown; 93 | }; 94 | 95 | export type SelectionFunction = ( 96 | t: Z & { 97 | [P in keyof Z]: P extends keyof V ? Z[P] : never; 98 | }, 99 | ) => Z; 100 | 101 | type BuiltInVariableTypes = { 102 | ['String']: string; 103 | ['Int']: number; 104 | ['Float']: number; 105 | ['Boolean']: boolean; 106 | }; 107 | type AllVariableTypes = keyof BuiltInVariableTypes | keyof ZEUS_VARIABLES; 108 | type VariableRequired = `${T}!` | T | `[${T}]` | `[${T}]!` | `[${T}!]` | `[${T}!]!`; 109 | type VR = VariableRequired>; 110 | 111 | export type GraphQLVariableType = VR; 112 | 113 | type ExtractVariableTypeString = T extends VR 114 | ? R1 extends VR 115 | ? R2 extends VR 116 | ? R3 extends VR 117 | ? R4 extends VR 118 | ? R5 119 | : R4 120 | : R3 121 | : R2 122 | : R1 123 | : T; 124 | 125 | type DecomposeType = T extends `[${infer R}]` 126 | ? Array> | undefined 127 | : T extends `${infer R}!` 128 | ? NonNullable> 129 | : Type | undefined; 130 | 131 | type ExtractTypeFromGraphQLType = T extends keyof ZEUS_VARIABLES 132 | ? ZEUS_VARIABLES[T] 133 | : T extends keyof BuiltInVariableTypes 134 | ? BuiltInVariableTypes[T] 135 | : any; 136 | 137 | export type GetVariableType = DecomposeType< 138 | T, 139 | ExtractTypeFromGraphQLType> 140 | >; 141 | 142 | type UndefinedKeys = { 143 | [K in keyof T]-?: T[K] extends NonNullable ? never : K; 144 | }[keyof T]; 145 | 146 | type WithNullableKeys = Pick>; 147 | type WithNonNullableKeys = Omit>; 148 | 149 | type OptionalKeys = { 150 | [P in keyof T]?: T[P]; 151 | }; 152 | 153 | export type WithOptionalNullables = OptionalKeys> & WithNonNullableKeys; 154 | 155 | export type ComposableSelector = ReturnType>; 156 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/functions/new/variableExtract.ts: -------------------------------------------------------------------------------- 1 | import { GetVariableType, GraphQLVariableType, WithOptionalNullables } from '@/TreeToTS/functions/new/types'; 2 | 3 | export type Variable = { 4 | ' __zeus_name': Name; 5 | ' __zeus_type': T; 6 | }; 7 | 8 | export type ExtractVariablesDeep = Query extends Variable 9 | ? { [key in VName]: GetVariableType } 10 | : Query extends string | number | boolean | Array 11 | ? // eslint-disable-next-line @typescript-eslint/ban-types 12 | {} 13 | : UnionToIntersection<{ [K in keyof Query]: WithOptionalNullables> }[keyof Query]>; 14 | 15 | export type ExtractVariables = Query extends Variable 16 | ? { [key in VName]: GetVariableType } 17 | : Query extends [infer Inputs, infer Outputs] 18 | ? ExtractVariablesDeep & ExtractVariables 19 | : Query extends string | number | boolean | Array 20 | ? // eslint-disable-next-line @typescript-eslint/ban-types 21 | {} 22 | : UnionToIntersection<{ [K in keyof Query]: WithOptionalNullables> }[keyof Query]>; 23 | 24 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; 25 | 26 | export const START_VAR_NAME = `$ZEUS_VAR`; 27 | export const GRAPHQL_TYPE_SEPARATOR = `__$GRAPHQL__`; 28 | 29 | export const $ = (name: Name, graphqlType: Type) => { 30 | return (START_VAR_NAME + name + GRAPHQL_TYPE_SEPARATOR + graphqlType) as unknown as Variable; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/index.ts: -------------------------------------------------------------------------------- 1 | import { resolveModelTypes } from '@/TreeToTS/templates/modelTypes'; 2 | import { resolveOperations } from '@/TreeToTS/templates/operations'; 3 | import { resolveInterfaces } from '@/TreeToTS/templates/returnedTypes/interfaces'; 4 | import { resolveUnions } from '@/TreeToTS/templates/returnedTypes/unions'; 5 | import { generateScalars } from '@/TreeToTS/templates/scalars'; 6 | import { resolveValueTypes } from '@/TreeToTS/templates/valueTypes'; 7 | import { resolveInputTypes } from '@/TreeToTS/templates/valueTypes/inputTypes'; 8 | import { resolveVariableTypes } from '@/TreeToTS/templates/variableTypes'; 9 | import { createParserField, Options, ParserTree, TypeDefinition } from 'graphql-js-tree'; 10 | import { Environment } from '../Models'; 11 | import { default as typescriptFunctions, subscriptionFunctions } from './functions/generated'; 12 | import { resolvePropTypeFromRoot } from './templates/returnedPropTypes'; 13 | import { resolveReturnFromRoot } from './templates/returnedReturns'; 14 | import { resolveTypes } from './templates/returnedTypes'; 15 | 16 | export interface OperationName { 17 | name: string; 18 | type: 'operation'; 19 | } 20 | 21 | export interface ResolvedOperations { 22 | query: OperationDetails; 23 | mutation: OperationDetails; 24 | subscription: OperationDetails; 25 | } 26 | 27 | export interface OperationDetails { 28 | operationName?: OperationName; 29 | operations: string[]; 30 | } 31 | 32 | export interface ResolveOptions { 33 | tree: ParserTree; 34 | env?: Environment; 35 | host?: string; 36 | headers?: Record; 37 | esModule?: boolean; 38 | subscriptions?: 'legacy' | 'graphql-ws'; 39 | constEnums?: boolean; 40 | } 41 | 42 | const disableLintersComments = ['eslint-disable']; 43 | /** 44 | * Class Responsible for generating typescript and javascript code 45 | */ 46 | export class TreeToTS { 47 | static resolveBasisHeader(): string { 48 | return `${disableLintersComments.map((rule) => `/* ${rule} */\n`).join('')}\n`; 49 | } 50 | 51 | static resolveBasisCode(tree: ParserTree): string { 52 | const propTypes = `export const AllTypesProps: Record = {\n${tree.nodes 53 | .concat([idScalarHelper]) 54 | .map(resolvePropTypeFromRoot) 55 | .filter((pt) => pt) 56 | .join(',\n')}\n}`; 57 | const returnTypes = `export const ReturnTypes: Record = {\n${tree.nodes 58 | .concat([idScalarHelper]) 59 | .map((f) => 60 | resolveReturnFromRoot( 61 | f, 62 | f.data.type === TypeDefinition.InterfaceTypeDefinition 63 | ? tree.nodes.filter((n) => n.interfaces?.includes(f.name)).map((n) => n.name) 64 | : undefined, 65 | ), 66 | ) 67 | .filter((pt) => pt) 68 | .join(',\n')}\n}`; 69 | const opsString = resolveOperations(tree); 70 | return propTypes.concat('\n\n').concat(returnTypes).concat('\n\n').concat(opsString); 71 | } 72 | 73 | static resolveBasisTypes(tree: ParserTree, options?: { constEnums?: boolean }): string { 74 | const allNodes = tree.nodes.concat([idScalarHelper]); 75 | const rootTypes = resolveTypes(allNodes, options); 76 | const valueTypes = resolveValueTypes(allNodes); 77 | const inputTypes = resolveInputTypes(allNodes); 78 | const modelTypes = resolveModelTypes(allNodes); 79 | const unionTypes = resolveUnions(allNodes); 80 | const interfaceTypes = resolveInterfaces(allNodes); 81 | const scalarTypes = generateScalars(allNodes); 82 | const variableTypes = resolveVariableTypes(allNodes); 83 | return interfaceTypes 84 | .concat('\n') 85 | .concat(scalarTypes) 86 | .concat('\n') 87 | .concat(unionTypes) 88 | .concat('\n\n') 89 | .concat(valueTypes) 90 | .concat('\n\n') 91 | .concat(inputTypes) 92 | .concat('\n\n') 93 | .concat(modelTypes) 94 | .concat('\n\n') 95 | .concat(rootTypes) 96 | .concat('\n\n') 97 | .concat(variableTypes); 98 | } 99 | 100 | /** 101 | * Generate typescript file 102 | */ 103 | static resolveTreeSplit({ 104 | tree, 105 | env = 'browser', 106 | host, 107 | esModule, 108 | headers, 109 | subscriptions = 'legacy', 110 | constEnums, 111 | }: ResolveOptions) { 112 | return { 113 | indexImports: `import { AllTypesProps, ReturnTypes, Ops } from './const${ 114 | esModule || env === 'node' ? '.js' : '' 115 | }';`.concat( 116 | env === 'node' 117 | ? ` 118 | import fetch, { Response } from 'node-fetch'; 119 | import WebSocket from 'ws';` 120 | : ``, 121 | ), 122 | const: TreeToTS.resolveBasisCode(tree), 123 | index: '' 124 | .concat(host ? `export const HOST = "${host}"` : '\n\nexport const HOST="Specify host"') 125 | .concat('\n') 126 | .concat(headers ? `export const HEADERS = ${JSON.stringify(headers)}` : '\n\nexport const HEADERS = {}') 127 | .concat('\n') 128 | .concat(subscriptionFunctions[subscriptions]) 129 | .concat('\n') 130 | .concat(typescriptFunctions) 131 | .concat('\n') 132 | .concat( 133 | TreeToTS.resolveBasisTypes(tree, { 134 | constEnums, 135 | }), 136 | ), 137 | }; 138 | } 139 | 140 | static resolveTree(options: ResolveOptions) { 141 | const t = TreeToTS.resolveTreeSplit(options); 142 | return TreeToTS.resolveBasisHeader().concat(t.const).concat('\n').concat(t.index); 143 | } 144 | } 145 | 146 | const idScalarHelper = createParserField({ 147 | data: { type: TypeDefinition.ScalarTypeDefinition }, 148 | name: 'ID', 149 | type: { fieldType: { type: Options.name, name: 'scalar' } }, 150 | }); 151 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/modelTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 2 | import { resolveField } from '@/TreeToTS/templates/shared/field'; 3 | import { ParserField, TypeSystemDefinition, TypeDefinition } from 'graphql-js-tree'; 4 | 5 | export const MODEL_TYPES = 'ModelTypes'; 6 | 7 | const resolveTypeFromRoot = (i: ParserField, rootNodes: ParserField[]): string => { 8 | if (i.data.type === TypeSystemDefinition.DirectiveDefinition) { 9 | return ''; 10 | } 11 | if (i.data.type === TypeDefinition.EnumTypeDefinition) { 12 | return `["${i.name}"]:${i.name}`; 13 | } 14 | if (i.data.type === TypeDefinition.ScalarTypeDefinition) { 15 | return `${plusDescription(i.description)}["${i.name}"]:any`; 16 | } 17 | if (!i.args || !i.args.length) { 18 | return ``; 19 | } 20 | if (i.data.type === TypeDefinition.InterfaceTypeDefinition) { 21 | const typesImplementing = rootNodes.filter((rn) => rn.interfaces && rn.interfaces.includes(i.name)); 22 | return `${plusDescription(i.description)}["${i.name}"]: ${ 23 | typesImplementing.length > 0 ? typesImplementing.map((ti) => `${MODEL_TYPES}["${ti.name}"]`).join(' | ') : 'never' 24 | }`; 25 | } 26 | if (i.data.type === TypeDefinition.UnionTypeDefinition) { 27 | return `${plusDescription(i.description)}["${i.name}"]:${i.args 28 | .map((f) => `${MODEL_TYPES}["${f.name}"]`) 29 | .join(' | ')}`; 30 | } 31 | if (i.data.type !== TypeDefinition.ObjectTypeDefinition) { 32 | return `${plusDescription(i.description)}["${i.name}"]: {\n${i.args 33 | .map((f) => resolveField(f, MODEL_TYPES)) 34 | .join(',\n')}\n}`; 35 | } 36 | return `${plusDescription(i.description)}["${i.name}"]: {\n\t${i.args 37 | .map((f) => resolveField(f, MODEL_TYPES)) 38 | .join(',\n')}\n}`; 39 | }; 40 | export const resolveModelTypes = (rootNodes: ParserField[]): string => { 41 | return `export type ${MODEL_TYPES} = { 42 | ${rootNodes 43 | .map((f) => resolveTypeFromRoot(f, rootNodes)) 44 | .filter((v) => v) 45 | .join(';\n\t')} 46 | }`; 47 | }; 48 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/operations/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolveOperations } from '@/TreeToTS/templates/operations'; 2 | import { replSpace } from '@/__tests__/TestUtils'; 3 | import { createParserField, createSchemaDefinition, Options, TypeDefinition } from 'graphql-js-tree'; 4 | 5 | describe('Test operations string generation', () => { 6 | it('Generates correct query string', () => { 7 | const matchExact = replSpace( 8 | resolveOperations({ 9 | nodes: [ 10 | createParserField({ 11 | args: [], 12 | data: { 13 | type: TypeDefinition.ObjectTypeDefinition, 14 | }, 15 | directives: [], 16 | interfaces: [], 17 | name: 'Queryy', 18 | type: { 19 | fieldType: { 20 | type: Options.name, 21 | name: 'type', 22 | }, 23 | }, 24 | }), 25 | createSchemaDefinition({ 26 | operations: { 27 | query: 'Queryy', 28 | }, 29 | }), 30 | ], 31 | }), 32 | ); 33 | matchExact(`query: "Queryy" as const`); 34 | }); 35 | it('Generates correct object of Ops', () => { 36 | const matchExact = replSpace( 37 | resolveOperations({ 38 | nodes: [ 39 | createParserField({ 40 | args: [], 41 | data: { 42 | type: TypeDefinition.ObjectTypeDefinition, 43 | }, 44 | directives: [], 45 | interfaces: [], 46 | name: 'Queryy', 47 | type: { 48 | fieldType: { 49 | type: Options.name, 50 | name: 'type', 51 | }, 52 | }, 53 | }), 54 | createSchemaDefinition({ 55 | operations: { 56 | query: 'Queryy', 57 | }, 58 | }), 59 | ], 60 | }), 61 | ); 62 | matchExact(`const Ops = {query: "Queryy" as const}`); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/operations/index.ts: -------------------------------------------------------------------------------- 1 | import { ParserTree, TypeSystemDefinition, getTypeName } from 'graphql-js-tree'; 2 | 3 | export interface OperationName { 4 | name: string; 5 | type: 'operation'; 6 | } 7 | 8 | export interface ResolvedOperations { 9 | query: OperationDetails; 10 | mutation: OperationDetails; 11 | subscription: OperationDetails; 12 | } 13 | 14 | export interface OperationDetails { 15 | operationName?: OperationName; 16 | operations: string[]; 17 | } 18 | 19 | export const resolveOperations = (tree: ParserTree) => { 20 | const schemaNode = tree.nodes.find((n) => n.data.type === TypeSystemDefinition.SchemaDefinition); 21 | if (!schemaNode) throw new Error('No operations in schema'); 22 | const opsStrings = schemaNode.args.map((a) => `${a.name}: "${getTypeName(a.type.fieldType)}" as const`); 23 | const opsString = `export const Ops = { 24 | ${opsStrings.join(',\n\t')} 25 | }`; 26 | return opsString; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedPropTypes/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolvePropTypeFromRoot } from '@/TreeToTS/templates/returnedPropTypes'; 2 | import { replSpace } from '@/__tests__/TestUtils'; 3 | import { 4 | createParserField, 5 | Options, 6 | ScalarTypes, 7 | TypeDefinition, 8 | TypeSystemDefinition, 9 | ValueDefinition, 10 | } from 'graphql-js-tree'; 11 | 12 | describe('Prop types generation test', () => { 13 | it('Correctly resolves enum type', () => { 14 | const matchExact = replSpace( 15 | resolvePropTypeFromRoot( 16 | createParserField({ 17 | name: 'Status', 18 | args: [], 19 | data: { 20 | type: TypeDefinition.EnumTypeDefinition, 21 | }, 22 | directives: [], 23 | interfaces: [], 24 | type: { 25 | fieldType: { 26 | type: Options.name, 27 | name: 'enum', 28 | }, 29 | }, 30 | }), 31 | ), 32 | ); 33 | matchExact(`Status: "enum" as const`); 34 | }); 35 | it('Correctly resolves type with fields without enums and inputs', () => { 36 | const matchExact = replSpace( 37 | resolvePropTypeFromRoot( 38 | createParserField({ 39 | name: 'Person', 40 | args: [ 41 | createParserField({ 42 | args: [], 43 | data: { 44 | type: TypeSystemDefinition.FieldDefinition, 45 | }, 46 | directives: [], 47 | interfaces: [], 48 | name: 'firstName', 49 | type: { 50 | fieldType: { 51 | name: 'String', 52 | type: Options.name, 53 | }, 54 | }, 55 | }), 56 | ], 57 | data: { 58 | type: TypeDefinition.ObjectTypeDefinition, 59 | }, 60 | directives: [], 61 | interfaces: [], 62 | type: { 63 | fieldType: { 64 | type: Options.name, 65 | name: 'type', 66 | }, 67 | }, 68 | }), 69 | ), 70 | ); 71 | matchExact(``); 72 | }); 73 | it('Correctly resolves type with fields with scalars and inputs', () => { 74 | const matchExact = replSpace( 75 | resolvePropTypeFromRoot( 76 | createParserField({ 77 | name: 'Mutation', 78 | args: [ 79 | createParserField({ 80 | args: [ 81 | createParserField({ 82 | args: [], 83 | data: { 84 | type: ValueDefinition.InputValueDefinition, 85 | }, 86 | directives: [], 87 | interfaces: [], 88 | name: '_id', 89 | type: { 90 | fieldType: { 91 | type: Options.name, 92 | name: ScalarTypes.String, 93 | }, 94 | }, 95 | }), 96 | createParserField({ 97 | args: [], 98 | data: { 99 | type: ValueDefinition.InputValueDefinition, 100 | }, 101 | directives: [], 102 | interfaces: [], 103 | name: 'CreatePerson', 104 | type: { 105 | fieldType: { 106 | type: Options.name, 107 | name: 'CreatePerson', 108 | }, 109 | }, 110 | }), 111 | ], 112 | data: { 113 | type: TypeSystemDefinition.FieldDefinition, 114 | }, 115 | directives: [], 116 | interfaces: [], 117 | name: 'addPerson', 118 | type: { 119 | fieldType: { 120 | name: 'String', 121 | type: Options.name, 122 | }, 123 | }, 124 | }), 125 | ], 126 | data: { 127 | type: TypeDefinition.ObjectTypeDefinition, 128 | }, 129 | directives: [], 130 | interfaces: [], 131 | type: { 132 | fieldType: { 133 | type: Options.name, 134 | name: 'type', 135 | }, 136 | }, 137 | }), 138 | ), 139 | ); 140 | matchExact(`Mutation: { addPerson: { CreatePerson: "CreatePerson" }}`); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedPropTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { ParserField, TypeSystemDefinition, TypeDefinition, getTypeName, ScalarTypes } from 'graphql-js-tree'; 2 | 3 | const resolveArg = (f: ParserField, tabs = '\t\t\t'): string => { 4 | const { 5 | type: { fieldType }, 6 | } = f; 7 | const fType = getTypeName(fieldType); 8 | if (Object.keys(ScalarTypes).includes(fType)) { 9 | return ''; 10 | } 11 | return `${tabs}${f.name}:"${fType}"`; 12 | }; 13 | const resolveField = (f: ParserField): string => { 14 | const { args, name } = f; 15 | return `\t\t${name}:{\n${args 16 | .map((a) => resolveArg(a)) 17 | .filter((f) => !!f) 18 | .join(',\n')}\n\t\t}`; 19 | }; 20 | 21 | export const resolvePropTypeFromRoot = (i: ParserField): string => { 22 | if (i.data.type === TypeSystemDefinition.DirectiveDefinition) { 23 | return ''; 24 | } 25 | if (i.data.type === TypeDefinition.EnumTypeDefinition) { 26 | return `\t${i.name}: "enum" as const`; 27 | } 28 | if (i.data.type === TypeDefinition.ScalarTypeDefinition) { 29 | return `\t${i.name}: \`scalar.${i.name}\` as const`; 30 | } 31 | if (i.data.type === TypeDefinition.InputObjectTypeDefinition) { 32 | return `\t${i.name}:{\n${i.args 33 | .map((f) => resolveArg(f, '\t\t')) 34 | .filter((f) => !!f) 35 | .join(',\n')}\n\t}`; 36 | } 37 | if (!i.args.length) { 38 | return ''; 39 | } 40 | if (i.args.filter((f) => f.args && f.args.length > 0).length === 0) { 41 | return ''; 42 | } 43 | return `\t${i.name}:{\n${i.args 44 | .filter((f) => f.args && f.args.length) 45 | .map((f) => resolveField(f)) 46 | .filter((f) => !!f) 47 | .join(',\n')}\n\t}`; 48 | }; 49 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedReturns.ts: -------------------------------------------------------------------------------- 1 | import { getTypeName, ParserField, TypeDefinition, TypeSystemDefinition } from 'graphql-js-tree'; 2 | 3 | const resolveField = (f: ParserField): string => { 4 | const { type, name } = f; 5 | return `\t\t${name}:"${getTypeName(type.fieldType)}"`; 6 | }; 7 | 8 | export const resolveReturnFromRoot = (i: ParserField, usages?: string[]): string => { 9 | if (i.data.type === TypeDefinition.ScalarTypeDefinition) { 10 | return `\t${i.name}: \`scalar.${i.name}\` as const`; 11 | } 12 | if ( 13 | i.data.type !== TypeDefinition.ObjectTypeDefinition && 14 | i.data.type !== TypeDefinition.UnionTypeDefinition && 15 | i.data.type !== TypeDefinition.InterfaceTypeDefinition && 16 | i.data.type !== TypeSystemDefinition.DirectiveDefinition 17 | ) { 18 | return ''; 19 | } 20 | if (!i.args) { 21 | return ''; 22 | } 23 | if (i.data.type === TypeDefinition.UnionTypeDefinition) { 24 | return `\t${i.name}:{\n${i.args 25 | .map((f) => 26 | resolveField({ 27 | ...f, 28 | name: `"...on ${f.name}"`, 29 | }), 30 | ) 31 | .join(',\n')}\n\t}`; 32 | } 33 | if (i.data.type === TypeDefinition.InterfaceTypeDefinition && usages) { 34 | const usagesStrings = usages.length > 0 ? `${usages.map((u) => `\t\t"...on ${u}": "${u}"`).join(',\n')},\n` : ``; 35 | return `\t${i.name}:{\n${usagesStrings}${i.args.map((f) => resolveField(f)).join(',\n')}\n\t}`; 36 | } 37 | return `\t${i.name}:{\n${i.args.map((f) => resolveField(f)).join(',\n')}\n\t}`; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/enum.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolveEnum } from '@/TreeToTS/templates/returnedTypes/enum'; 2 | import { createParserField, Options, TypeDefinition, ValueDefinition } from 'graphql-js-tree'; 3 | 4 | const mockEnum = createParserField({ 5 | args: [ 6 | createParserField({ 7 | type: { 8 | fieldType: { 9 | name: ValueDefinition.EnumValueDefinition, 10 | type: Options.name, 11 | }, 12 | }, 13 | args: [], 14 | directives: [], 15 | interfaces: [], 16 | name: 'CREATED', 17 | data: { 18 | type: ValueDefinition.EnumValueDefinition, 19 | }, 20 | }), 21 | createParserField({ 22 | type: { 23 | fieldType: { 24 | name: ValueDefinition.EnumValueDefinition, 25 | type: Options.name, 26 | }, 27 | }, 28 | args: [], 29 | directives: [], 30 | interfaces: [], 31 | name: 'DELETED', 32 | data: { 33 | type: ValueDefinition.EnumValueDefinition, 34 | }, 35 | }), 36 | ], 37 | data: { 38 | type: TypeDefinition.EnumTypeDefinition, 39 | }, 40 | directives: [], 41 | interfaces: [], 42 | name: 'Status', 43 | type: { 44 | fieldType: { 45 | name: 'enum', 46 | type: Options.name, 47 | }, 48 | }, 49 | }); 50 | 51 | describe('It creates correct TypeScript const enums', () => { 52 | test('Simple enum with 2 fields', () => { 53 | const enumString = resolveEnum(mockEnum); 54 | const enumConstString = resolveEnum(mockEnum, true); 55 | expect(enumString).toEqual(`export enum Status { 56 | \tCREATED = "CREATED", 57 | \tDELETED = "DELETED" 58 | }`); 59 | expect(enumConstString).toEqual(`export const enum Status { 60 | \tCREATED = "CREATED", 61 | \tDELETED = "DELETED" 62 | }`); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/enum.ts: -------------------------------------------------------------------------------- 1 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 2 | import { ParserField } from 'graphql-js-tree'; 3 | 4 | export const resolveEnum = (i: ParserField, constEnum?: boolean): string => { 5 | if (!i.args) { 6 | throw new Error('Empty enum error'); 7 | } 8 | return `${plusDescription(i.description)}export ${constEnum ? 'const ' : ''}enum ${i.name} {\n${i.args 9 | .map((f) => `\t${f.name} = "${f.name}"`) 10 | .join(',\n')}\n}`; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { resolveEnum } from '@/TreeToTS/templates/returnedTypes/enum'; 2 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 3 | import { resolveUnionMember } from '@/TreeToTS/templates/returnedTypes/unionMember'; 4 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 5 | import { resolveField } from '@/TreeToTS/templates/shared/field'; 6 | import { ParserField, TypeDefinition, TypeSystemDefinition, Helpers } from 'graphql-js-tree'; 7 | 8 | const resolveType = ({ data, name, args }: ParserField, rootNodes: ParserField[]) => { 9 | switch (data.type) { 10 | case TypeDefinition.EnumTypeDefinition: 11 | return `["${name}"]: ${name}`; 12 | case TypeDefinition.InputObjectTypeDefinition: 13 | return `["${name}"]: {\n\t${args.map((f) => resolveField(f)).join(',\n')}\n}`; 14 | 15 | case TypeDefinition.InterfaceTypeDefinition: 16 | const typesImplementing = rootNodes.filter((rn) => rn.interfaces && rn.interfaces.includes(name)); 17 | return `["${name}"]: { 18 | \t__typename:${typesImplementing.length === 0 ? 'never' : typesImplementing.map((ti) => `"${ti.name}"`).join(' | ')}, 19 | ${args.map((f) => resolveField(f)).join(',\n')} 20 | \t${typesImplementing.map((f) => `['...on ${f.name}']: '__union' & ${TYPES}["${f.name}"];`).join('\n\t')}\n}`; 21 | case TypeDefinition.ObjectTypeDefinition: 22 | return `["${name}"]: {\n\t__typename: "${name}",\n${args.map((f) => resolveField(f)).join(',\n')}\n}`; 23 | case TypeDefinition.ScalarTypeDefinition: 24 | return `["${name}"]: "scalar" & { name: "${name}" }`; 25 | case TypeDefinition.UnionTypeDefinition: 26 | return `["${name}"]:{ 27 | \t__typename:${args.length ? args.map((ti) => `"${ti.name}"`).join(' | ') : 'never'} 28 | \t${args.map(resolveUnionMember).join('\n\t')}\n}`; 29 | default: 30 | return ''; 31 | break; 32 | } 33 | }; 34 | 35 | export const resolveTypeFromRoot = (i: ParserField, rootNodes: ParserField[]): string => { 36 | if (i.data.type === TypeSystemDefinition.DirectiveDefinition) { 37 | return ''; 38 | } 39 | if (i.data.type === Helpers.Comment) { 40 | return `// ${i.description}`; 41 | } 42 | 43 | return `${plusDescription(i.description)}${resolveType(i, rootNodes)}`; 44 | }; 45 | export const resolveTypes = (rootNodes: ParserField[], options?: { constEnums?: boolean }): string => { 46 | return `export type ${TYPES} = { 47 | ${rootNodes 48 | .map((f) => resolveTypeFromRoot(f, rootNodes)) 49 | .filter((v) => v) 50 | .join(';\n\t')} 51 | }` 52 | .concat('\n') 53 | .concat( 54 | rootNodes 55 | .filter((rn) => rn.data.type === TypeDefinition.EnumTypeDefinition) 56 | .map((f) => resolveEnum(f, options?.constEnums)) 57 | .join('\n'), 58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/interfaces.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolveInterfaces } from '@/TreeToTS/templates/returnedTypes/interfaces'; 2 | import { TYPES, ZEUS_INTERFACES } from '@/TreeToTS/templates/returnedTypes/models'; 3 | import { createParserField, Options, TypeDefinition } from 'graphql-js-tree'; 4 | 5 | describe('Test interface => ZEUS_INTERFACES generation', () => { 6 | test('Single interface generation', () => { 7 | const interfaceString = resolveInterfaces([ 8 | createParserField({ 9 | type: { 10 | fieldType: { 11 | type: Options.name, 12 | name: 'interface', 13 | }, 14 | }, 15 | args: [], 16 | data: { 17 | type: TypeDefinition.InterfaceTypeDefinition, 18 | }, 19 | directives: [], 20 | interfaces: [], 21 | name: 'WithId', 22 | }), 23 | ]); 24 | expect(interfaceString).toEqual(`type ${ZEUS_INTERFACES} = ${TYPES}["WithId"]`); 25 | }); 26 | test('No interface generation', () => { 27 | const interfaceString = resolveInterfaces([]); 28 | expect(interfaceString).toEqual(`type ${ZEUS_INTERFACES} = never`); 29 | }); 30 | test('Multiple interface generation', () => { 31 | const interfaceString = resolveInterfaces([ 32 | createParserField({ 33 | type: { 34 | fieldType: { 35 | type: Options.name, 36 | name: 'interface', 37 | }, 38 | }, 39 | args: [], 40 | data: { 41 | type: TypeDefinition.InterfaceTypeDefinition, 42 | }, 43 | directives: [], 44 | interfaces: [], 45 | name: 'WithId', 46 | }), 47 | createParserField({ 48 | type: { 49 | fieldType: { 50 | type: Options.name, 51 | name: 'interface', 52 | }, 53 | }, 54 | args: [], 55 | data: { 56 | type: TypeDefinition.InterfaceTypeDefinition, 57 | }, 58 | directives: [], 59 | interfaces: [], 60 | name: 'WithName', 61 | }), 62 | ]); 63 | expect(interfaceString).toEqual(`type ${ZEUS_INTERFACES} = ${TYPES}["WithId"] | ${TYPES}["WithName"]`); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { TYPES, ZEUS_INTERFACES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { ParserField, TypeDefinition } from 'graphql-js-tree'; 3 | 4 | export const resolveInterfaces = (rootNodes: ParserField[]): string => { 5 | const interfaceTypes = rootNodes 6 | .filter((rn) => rn.data.type === TypeDefinition.InterfaceTypeDefinition) 7 | .map((rn) => `${TYPES}["${rn.name}"]`) 8 | .join(' | '); 9 | return `type ${ZEUS_INTERFACES} = ${interfaceTypes || 'never'}`; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/models.ts: -------------------------------------------------------------------------------- 1 | export const ZEUS_INTERFACES = `ZEUS_INTERFACES`; 2 | export const ZEUS_UNIONS = `ZEUS_UNIONS`; 3 | export const TYPES = 'GraphQLTypes'; 4 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/unionMember.spec.ts: -------------------------------------------------------------------------------- 1 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { resolveUnionMember } from '@/TreeToTS/templates/returnedTypes/unionMember'; 3 | import { Options, ParserField, TypeSystemDefinition, getTypeName, createParserField } from 'graphql-js-tree'; 4 | 5 | test('resolve union members', () => { 6 | const ob: ParserField = createParserField({ 7 | data: { 8 | type: TypeSystemDefinition.UnionMemberDefinition, 9 | }, 10 | name: 'Friend', 11 | args: [], 12 | directives: [], 13 | interfaces: [], 14 | type: { 15 | fieldType: { 16 | name: 'Friend', 17 | type: Options.name, 18 | }, 19 | }, 20 | }); 21 | const member = resolveUnionMember(ob); 22 | expect(member).toEqual( 23 | `['...on ${getTypeName(ob.type.fieldType)}']: '__union' & ${TYPES}["${getTypeName(ob.type.fieldType)}"];`, 24 | ); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/unionMember.ts: -------------------------------------------------------------------------------- 1 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { getTypeName, ParserField } from 'graphql-js-tree'; 3 | 4 | export const resolveUnionMember = (f: ParserField) => 5 | `['...on ${getTypeName(f.type.fieldType)}']: '__union' & ${TYPES}["${getTypeName(f.type.fieldType)}"];`; 6 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/unions.spec.ts: -------------------------------------------------------------------------------- 1 | import { TYPES, ZEUS_UNIONS } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { resolveUnions } from '@/TreeToTS/templates/returnedTypes/unions'; 3 | import { createParserField, Options, TypeDefinition } from 'graphql-js-tree'; 4 | 5 | describe('Test union => ZEUS_UNIONS generation', () => { 6 | test('Single type generation', () => { 7 | const interfaceString = resolveUnions([ 8 | createParserField({ 9 | type: { 10 | fieldType: { 11 | type: Options.name, 12 | name: 'union', 13 | }, 14 | }, 15 | args: [], 16 | data: { 17 | type: TypeDefinition.UnionTypeDefinition, 18 | }, 19 | directives: [], 20 | interfaces: [], 21 | name: 'WithId', 22 | }), 23 | ]); 24 | expect(interfaceString).toEqual(`type ${ZEUS_UNIONS} = ${TYPES}["WithId"]`); 25 | }); 26 | test('No union generation', () => { 27 | const interfaceString = resolveUnions([]); 28 | expect(interfaceString).toEqual(`type ${ZEUS_UNIONS} = never`); 29 | }); 30 | test('Multiple union generation', () => { 31 | const interfaceString = resolveUnions([ 32 | createParserField({ 33 | type: { 34 | fieldType: { 35 | type: Options.name, 36 | name: 'union', 37 | }, 38 | }, 39 | args: [], 40 | data: { 41 | type: TypeDefinition.UnionTypeDefinition, 42 | }, 43 | directives: [], 44 | interfaces: [], 45 | name: 'WithId', 46 | }), 47 | createParserField({ 48 | type: { 49 | fieldType: { 50 | type: Options.name, 51 | name: 'union', 52 | }, 53 | }, 54 | args: [], 55 | data: { 56 | type: TypeDefinition.UnionTypeDefinition, 57 | }, 58 | directives: [], 59 | interfaces: [], 60 | name: 'WithName', 61 | }), 62 | ]); 63 | expect(interfaceString).toEqual(`type ${ZEUS_UNIONS} = ${TYPES}["WithId"] | ${TYPES}["WithName"]`); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/returnedTypes/unions.ts: -------------------------------------------------------------------------------- 1 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { ParserField, TypeDefinition } from 'graphql-js-tree'; 3 | 4 | export const resolveUnions = (rootNodes: ParserField[]): string => { 5 | const unionTypes = rootNodes 6 | .filter((rn) => rn.data.type === TypeDefinition.UnionTypeDefinition) 7 | .map((rn) => `${TYPES}["${rn.name}"]`) 8 | .join(' | '); 9 | return `type ZEUS_UNIONS = ${unionTypes || 'never'}`; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/scalars/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { generateScalars, SCALAR_TYPES } from '@/TreeToTS/templates/scalars'; 2 | import { replSpace } from '@/__tests__/TestUtils'; 3 | import { createParserField, Options, TypeDefinition } from 'graphql-js-tree'; 4 | describe('Tests scalars object generation', () => { 5 | test('Generate empty objects if no scalars', () => { 6 | const result = generateScalars([]); 7 | expect(result).toEqual(`export type ${SCALAR_TYPES} = {\n}`); 8 | }); 9 | test('Generate empty objects if no scalars', () => { 10 | const result = replSpace( 11 | generateScalars([ 12 | createParserField({ 13 | args: [], 14 | data: { 15 | type: TypeDefinition.ScalarTypeDefinition, 16 | }, 17 | directives: [], 18 | interfaces: [], 19 | name: 'JSON', 20 | type: { 21 | fieldType: { 22 | name: 'scalar', 23 | type: Options.name, 24 | }, 25 | }, 26 | }), 27 | createParserField({ 28 | args: [], 29 | data: { 30 | type: TypeDefinition.ScalarTypeDefinition, 31 | }, 32 | directives: [], 33 | interfaces: [], 34 | name: 'Date', 35 | type: { 36 | fieldType: { 37 | name: 'scalar', 38 | type: Options.name, 39 | }, 40 | }, 41 | }), 42 | ]), 43 | ); 44 | result(`export type ${SCALAR_TYPES} = { 45 | JSON?: ScalarResolver; 46 | Date?: ScalarResolver; 47 | }`); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/scalars/index.ts: -------------------------------------------------------------------------------- 1 | import { ParserField, TypeDefinition } from 'graphql-js-tree'; 2 | export const SCALAR_TYPES = 'ScalarCoders'; 3 | export const generateScalars = (nodes: ParserField[]) => 4 | `export type ${SCALAR_TYPES} = {${nodes 5 | .filter((n) => n.data.type === TypeDefinition.ScalarTypeDefinition) 6 | .map((n) => [n.name, `ScalarResolver`]) 7 | .reduce((a, b) => { 8 | return `${a}\n\t${b[0]}?: ${b[1]};`; 9 | }, '')}\n}`; 10 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/description.spec.ts: -------------------------------------------------------------------------------- 1 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 2 | 3 | test('It creates correct TypeScript doc', () => { 4 | const desc = plusDescription('Hello world', ''); 5 | expect(desc).toEqual(`/** Hello world */\n`); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/description.ts: -------------------------------------------------------------------------------- 1 | export const plusDescription = (description?: string, prefix = ''): string => 2 | description ? `${prefix}/** ${description} */\n` : ''; 3 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/field.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolveFieldType } from '@/TreeToTS/templates/shared/field'; 2 | import { Options } from 'graphql-js-tree'; 3 | 4 | describe('Test type field generation', () => { 5 | test('Required Field generation', () => { 6 | const resolvedString = resolveFieldType('Person', { 7 | type: Options.required, 8 | nest: { 9 | type: Options.name, 10 | name: 'Person', 11 | }, 12 | }); 13 | expect(resolvedString).toEqual(`Person`); 14 | }); 15 | test('Optional Field generation', () => { 16 | const resolvedString = resolveFieldType('Person', { 17 | type: Options.name, 18 | name: 'Person', 19 | }); 20 | expect(resolvedString).toEqual(`Person | undefined | null`); 21 | }); 22 | test('Optional Array Optional Field generation', () => { 23 | const resolvedString = resolveFieldType('Person', { 24 | type: Options.array, 25 | nest: { 26 | type: Options.name, 27 | name: 'Person', 28 | }, 29 | }); 30 | expect(resolvedString).toEqual(`Array | undefined | null`); 31 | }); 32 | test('Required Array Optional Field generation', () => { 33 | const resolvedString = resolveFieldType('Person', { 34 | type: Options.required, 35 | nest: { 36 | type: Options.array, 37 | nest: { 38 | type: Options.name, 39 | name: 'Person', 40 | }, 41 | }, 42 | }); 43 | expect(resolvedString).toEqual(`Array`); 44 | }); 45 | test('Required Array Required Field generation', () => { 46 | const resolvedString = resolveFieldType('Person', { 47 | type: Options.required, 48 | nest: { 49 | type: Options.array, 50 | nest: { 51 | type: Options.required, 52 | nest: { 53 | type: Options.name, 54 | name: 'Person', 55 | }, 56 | }, 57 | }, 58 | }); 59 | expect(resolvedString).toEqual(`Array`); 60 | }); 61 | test('Required nested Array Required Field generation', () => { 62 | const resolvedString = resolveFieldType('Person', { 63 | type: Options.required, 64 | nest: { 65 | type: Options.array, 66 | nest: { 67 | type: Options.required, 68 | nest: { 69 | type: Options.array, 70 | nest: { 71 | type: Options.required, 72 | nest: { 73 | type: Options.name, 74 | name: 'Int', 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | }); 81 | expect(resolvedString).toEqual(`Array>`); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/field.ts: -------------------------------------------------------------------------------- 1 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 3 | import { toTypeScriptPrimitive } from '@/TreeToTS/templates/shared/primitive'; 4 | import { ParserField, Options, getTypeName, FieldType } from 'graphql-js-tree'; 5 | 6 | export const resolveFieldType = ( 7 | name: string, 8 | fType: FieldType, 9 | fn: (str: string) => string = (x) => x, 10 | isRequired = false, 11 | ): string => { 12 | if (fType.type === Options.name) { 13 | return fn(isRequired ? name : `${name} | undefined | null`); 14 | } 15 | if (fType.type === Options.array) { 16 | return resolveFieldType( 17 | name, 18 | fType.nest, 19 | isRequired ? (x) => `Array<${fn(x)}>` : (x) => `Array<${fn(x)}> | undefined | null`, 20 | false, 21 | ); 22 | } 23 | if (fType.type === Options.required) { 24 | return resolveFieldType(name, fType.nest, fn, true); 25 | } 26 | throw new Error('Invalid field type'); 27 | }; 28 | 29 | export const resolveField = (f: ParserField, t = TYPES): string => { 30 | const isNullType = (type: string): string => { 31 | return f.type.fieldType.type === Options.required ? `: ${type}` : `?: ${type}`; 32 | }; 33 | return `${plusDescription(f.description, '\t')}\t${f.name}${isNullType( 34 | resolveFieldType(toTypeScriptPrimitive(getTypeName(f.type.fieldType), t), f.type.fieldType), 35 | )}`; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/primitive.spec.ts: -------------------------------------------------------------------------------- 1 | import { toTypeScriptPrimitive } from '@/TreeToTS/templates/shared/primitive'; 2 | 3 | describe('TypeScript primitive generation', () => { 4 | test('for inexistent Ts type', () => { 5 | const primitive = toTypeScriptPrimitive('Person'); 6 | expect(primitive).toContain('Person'); 7 | }); 8 | test('for GraphQL String type', () => { 9 | const primitive = toTypeScriptPrimitive('String'); 10 | expect(primitive).toEqual('string'); 11 | }); 12 | test('for GraphQL Int type', () => { 13 | const primitive = toTypeScriptPrimitive('Int'); 14 | expect(primitive).toEqual('number'); 15 | }); 16 | test('for GraphQL Float type', () => { 17 | const primitive = toTypeScriptPrimitive('Float'); 18 | expect(primitive).toEqual('number'); 19 | }); 20 | test('for GraphQL Boolean type', () => { 21 | const primitive = toTypeScriptPrimitive('Boolean'); 22 | expect(primitive).toEqual('boolean'); 23 | }); 24 | test('for GraphQL ID type', () => { 25 | const primitive = toTypeScriptPrimitive('ID'); 26 | expect(primitive).toEqual(`GraphQLTypes["ID"]`); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/shared/primitive.ts: -------------------------------------------------------------------------------- 1 | import { TYPES } from '@/TreeToTS/templates/returnedTypes/models'; 2 | 3 | const typeScriptMap: Record = { 4 | Int: 'number', 5 | Float: 'number', 6 | Boolean: 'boolean', 7 | String: 'string', 8 | }; 9 | export const isTypeScriptPrimitive = (a: string) => !!typeScriptMap[a]; 10 | export const toTypeScriptPrimitive = (a: string, t = TYPES): string => typeScriptMap[a] || `${t}["${a}"]`; 11 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/truthy.ts: -------------------------------------------------------------------------------- 1 | export const truthyType = 'boolean | `@${string}`'; 2 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/arg.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolveValueFieldType } from '@/TreeToTS/templates/valueTypes/arg'; 2 | import { Options } from 'graphql-js-tree'; 3 | 4 | describe('Test type arg generation', () => { 5 | test('Required arg generation', () => { 6 | const resolvedString = resolveValueFieldType('Person', { 7 | type: Options.required, 8 | nest: { 9 | type: Options.name, 10 | name: 'Person', 11 | }, 12 | }); 13 | expect(resolvedString).toEqual(`Person`); 14 | }); 15 | test('Optional arg generation', () => { 16 | const resolvedString = resolveValueFieldType('Person', { 17 | type: Options.name, 18 | name: 'Person', 19 | }); 20 | expect(resolvedString).toEqual(`Person | undefined | null`); 21 | }); 22 | test('Optional Array Optional arg generation', () => { 23 | const resolvedString = resolveValueFieldType('Person', { 24 | type: Options.array, 25 | nest: { 26 | type: Options.name, 27 | name: 'Person', 28 | }, 29 | }); 30 | expect(resolvedString).toEqual(`Array | undefined | null`); 31 | }); 32 | test('Required Array Optional arg generation', () => { 33 | const resolvedString = resolveValueFieldType('Person', { 34 | type: Options.required, 35 | nest: { 36 | type: Options.array, 37 | nest: { 38 | type: Options.name, 39 | name: 'Person', 40 | }, 41 | }, 42 | }); 43 | expect(resolvedString).toEqual(`Array`); 44 | }); 45 | test('Required Array Required arg generation', () => { 46 | const resolvedString = resolveValueFieldType('Person', { 47 | type: Options.required, 48 | nest: { 49 | type: Options.array, 50 | nest: { 51 | type: Options.required, 52 | nest: { 53 | type: Options.name, 54 | name: 'Person', 55 | }, 56 | }, 57 | }, 58 | }); 59 | expect(resolvedString).toEqual(`Array`); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/arg.ts: -------------------------------------------------------------------------------- 1 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 2 | import { isTypeScriptPrimitive, toTypeScriptPrimitive } from '@/TreeToTS/templates/shared/primitive'; 3 | import { truthyType } from '@/TreeToTS/templates/truthy'; 4 | import { VALUETYPES } from '@/TreeToTS/templates/valueTypes/models'; 5 | import { ParserField, Options, getTypeName, FieldType } from 'graphql-js-tree'; 6 | 7 | const orVar = (name: string) => `${name} | Variable`; 8 | 9 | export const resolveArg = (f: ParserField): string => { 10 | const { 11 | type: { fieldType }, 12 | } = f; 13 | const isRequiredName = (name: string): string => { 14 | if (fieldType.type === Options.required) { 15 | return name; 16 | } 17 | return `${name}?`; 18 | }; 19 | const resolveArgsName = (name: string): string => { 20 | return isRequiredName(name) + ': '; 21 | }; 22 | const typeName = getTypeName(f.type.fieldType); 23 | const tsp = toTypeScriptPrimitive(typeName); 24 | return `${plusDescription(f.description, '\t')}\t${resolveArgsName(f.name)}${orVar( 25 | resolveValueFieldType(isTypeScriptPrimitive(typeName) ? tsp : createValueType(typeName), f.type.fieldType), 26 | )}`; 27 | }; 28 | 29 | export const createValueType = (t: string): string => `${VALUETYPES}["${t}"]`; 30 | 31 | export const resolveValueFieldType = ( 32 | name: string, 33 | fType: FieldType, 34 | fn: (str: string) => string = (x) => x, 35 | isRequired = false, 36 | ): string => { 37 | if (fType.type === Options.name) { 38 | return fn(isRequired ? name : `${name} | undefined | null`); 39 | } 40 | if (fType.type === Options.array) { 41 | return resolveValueFieldType( 42 | name, 43 | fType.nest, 44 | isRequired ? (x) => `Array<${fn(x)}>` : (x) => `Array<${fn(x)}> | undefined | null`, 45 | false, 46 | ); 47 | } 48 | if (fType.type === Options.required) { 49 | return resolveValueFieldType(name, fType.nest, fn, true); 50 | } 51 | throw new Error('Invalid field type'); 52 | }; 53 | export const resolveValueField = (f: ParserField, enumsAndScalars: string[]): string => { 54 | const { args } = f; 55 | const typeName = getTypeName(f.type.fieldType); 56 | const resolvedTypeName = 57 | isTypeScriptPrimitive(typeName) || enumsAndScalars.includes(typeName) ? truthyType : createValueType(typeName); 58 | if (args && args.length) { 59 | return `${f.name}?: [{${args.map(resolveArg).join(',')}},${resolvedTypeName}]`; 60 | } 61 | return `${plusDescription(f.description, '\t')}\t${`${f.name}?` + ':'}${resolvedTypeName}`; 62 | }; 63 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { truthyType } from '@/TreeToTS/templates/truthy'; 2 | import { ParserField, TypeSystemDefinition, Helpers, TypeDefinition, getTypeName } from 'graphql-js-tree'; 3 | import { createValueType, resolveArg, resolveValueField } from '@/TreeToTS/templates/valueTypes/arg'; 4 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 5 | import { VALUETYPES } from '@/TreeToTS/templates/valueTypes/models'; 6 | 7 | const AliasType = (code: string): string => `AliasType<${code}>`; 8 | const resolveValueTypeFromRoot = (i: ParserField, rootNodes: ParserField[], enumsAndScalars: string[]): string => { 9 | if (!i.args || !i.args.length) { 10 | return `["${i.name}"]:unknown`; 11 | } 12 | if (i.data.type === TypeDefinition.UnionTypeDefinition) { 13 | return `["${i.name}"]: ${AliasType( 14 | `{${i.args 15 | .map( 16 | (f) => `\t\t["...on ${getTypeName(f.type.fieldType)}"]?: ${VALUETYPES}["${getTypeName(f.type.fieldType)}"]`, 17 | ) 18 | .join(',\n')}\n\t\t__typename?: ${truthyType}\n}`, 19 | )}`; 20 | } 21 | if (i.data.type === TypeDefinition.EnumTypeDefinition) { 22 | return `["${i.name}"]:${i.name}`; 23 | } 24 | if (i.data.type === TypeDefinition.InputObjectTypeDefinition) { 25 | return `["${i.name}"]: {\n${i.args.map((f) => resolveArg(f)).join(',\n')}\n}`; 26 | } 27 | if (i.data.type === TypeDefinition.InterfaceTypeDefinition) { 28 | const typesImplementing = rootNodes.filter((rn) => rn.interfaces && rn.interfaces.includes(i.name)); 29 | return `["${i.name}"]:${AliasType( 30 | `{ 31 | \t${i.args.map((f) => resolveValueField(f, enumsAndScalars)).join(',\n')};\n\t\t${typesImplementing 32 | .map((f) => `['...on ${f.name}']?: Omit<${createValueType(f.name)},keyof ${createValueType(i.name)}>;`) 33 | .join('\n\t\t')}\n\t\t__typename?: ${truthyType}\n}`, 34 | )}`; 35 | } 36 | if (i.data.type === TypeDefinition.ObjectTypeDefinition) { 37 | return `["${i.name}"]: ${AliasType( 38 | `{\n${i.args.map((f) => resolveValueField(f, enumsAndScalars)).join(',\n')},\n\t\t__typename?: ${truthyType}\n}`, 39 | )}`; 40 | } 41 | return ``; 42 | }; 43 | export const resolveValueType = (i: ParserField, rootNodes: ParserField[], enumsAndScalars: string[]): string => { 44 | if ( 45 | i.data.type === TypeSystemDefinition.DirectiveDefinition || 46 | i.data.type === TypeSystemDefinition.SchemaDefinition 47 | ) { 48 | return ''; 49 | } 50 | if (i.data.type === Helpers.Comment) { 51 | return ''; 52 | } 53 | const output = resolveValueTypeFromRoot(i, rootNodes, enumsAndScalars); 54 | return `${plusDescription(i.description)}${output}`; 55 | }; 56 | export const resolveValueTypes = (rootNodes: ParserField[]): string => { 57 | const enumsAndScalars = rootNodes 58 | .filter( 59 | (n) => n.data?.type === TypeDefinition.EnumTypeDefinition || n.data?.type === TypeDefinition.ScalarTypeDefinition, 60 | ) 61 | .map((n) => n.name); 62 | return `export type ${VALUETYPES} = { 63 | ${rootNodes 64 | .map((f) => resolveValueType(f, rootNodes, enumsAndScalars)) 65 | .filter((v) => v) 66 | .join(';\n\t')} 67 | }`; 68 | }; 69 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/inputTypes/arg.ts: -------------------------------------------------------------------------------- 1 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 2 | import { isTypeScriptPrimitive, toTypeScriptPrimitive } from '@/TreeToTS/templates/shared/primitive'; 3 | import { truthyType } from '@/TreeToTS/templates/truthy'; 4 | import { INPUTTYPES } from '@/TreeToTS/templates/valueTypes/models'; 5 | import { ParserField, Options, getTypeName, FieldType } from 'graphql-js-tree'; 6 | 7 | export const resolveInputArg = (f: ParserField): string => { 8 | const { 9 | type: { fieldType }, 10 | } = f; 11 | const isRequiredName = (name: string): string => { 12 | if (fieldType.type === Options.required) { 13 | return name; 14 | } 15 | return `${name}?`; 16 | }; 17 | const resolveArgsName = (name: string): string => { 18 | return isRequiredName(name) + ': '; 19 | }; 20 | const typeName = getTypeName(f.type.fieldType); 21 | const tsp = toTypeScriptPrimitive(typeName); 22 | return `${plusDescription(f.description, '\t')}\t${resolveArgsName(f.name)}${resolveValueFieldType( 23 | isTypeScriptPrimitive(typeName) ? tsp : createInputType(typeName), 24 | f.type.fieldType, 25 | )}`; 26 | }; 27 | 28 | export const createInputType = (t: string): string => `${INPUTTYPES}["${t}"]`; 29 | 30 | export const resolveValueFieldType = ( 31 | name: string, 32 | fType: FieldType, 33 | fn: (str: string) => string = (x) => x, 34 | isRequired = false, 35 | ): string => { 36 | if (fType.type === Options.name) { 37 | return fn(isRequired ? name : `${name} | undefined | null`); 38 | } 39 | if (fType.type === Options.array) { 40 | return resolveValueFieldType( 41 | name, 42 | fType.nest, 43 | isRequired ? (x) => `Array<${fn(x)}>` : (x) => `Array<${fn(x)}> | undefined | null`, 44 | false, 45 | ); 46 | } 47 | if (fType.type === Options.required) { 48 | return resolveValueFieldType(name, fType.nest, fn, true); 49 | } 50 | throw new Error('Invalid field type'); 51 | }; 52 | export const resolveInputField = (f: ParserField, enumsAndScalars: string[]): string => { 53 | const { args } = f; 54 | const typeName = getTypeName(f.type.fieldType); 55 | const resolvedTypeName = 56 | isTypeScriptPrimitive(typeName) || enumsAndScalars.includes(typeName) ? truthyType : createInputType(typeName); 57 | if (args && args.length) { 58 | return `${f.name}?: [{${args.map(resolveInputArg).join(',')}},${resolvedTypeName}]`; 59 | } 60 | return `${plusDescription(f.description, '\t')}\t${`${f.name}?` + ':'}${resolvedTypeName}`; 61 | }; 62 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/inputTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { truthyType } from '@/TreeToTS/templates/truthy'; 2 | import { ParserField, TypeSystemDefinition, Helpers, TypeDefinition } from 'graphql-js-tree'; 3 | import { createInputType, resolveInputArg, resolveInputField } from '@/TreeToTS/templates/valueTypes/inputTypes/arg'; 4 | import { plusDescription } from '@/TreeToTS/templates/shared/description'; 5 | import { INPUTTYPES } from '@/TreeToTS/templates/valueTypes/models'; 6 | 7 | const AliasType = (code: string): string => `AliasType<${code}>`; 8 | const resolveValueTypeFromRoot = (i: ParserField, rootNodes: ParserField[], enumsAndScalars: string[]): string => { 9 | if (!i.args || !i.args.length) { 10 | return `["${i.name}"]:unknown`; 11 | } 12 | if (i.data.type === TypeDefinition.EnumTypeDefinition) { 13 | return `["${i.name}"]:${i.name}`; 14 | } 15 | if (i.data.type === TypeDefinition.InputObjectTypeDefinition) { 16 | return `["${i.name}"]: {\n${i.args.map((f) => resolveInputArg(f)).join(',\n')}\n}`; 17 | } 18 | if (i.data.type === TypeDefinition.InterfaceTypeDefinition) { 19 | const typesImplementing = rootNodes.filter((rn) => rn.interfaces && rn.interfaces.includes(i.name)); 20 | return `["${i.name}"]:${AliasType( 21 | `{ 22 | \t${i.args.map((f) => resolveInputField(f, enumsAndScalars)).join(',\n')};\n\t\t${typesImplementing 23 | .map((f) => `['...on ${f.name}']?: Omit<${createInputType(f.name)},keyof ${createInputType(i.name)}>;`) 24 | .join('\n\t\t')}\n\t\t__typename?: ${truthyType}\n}`, 25 | )}`; 26 | } 27 | return `["${i.name}"]: ${AliasType( 28 | `{\n${i.args.map((f) => resolveInputField(f, enumsAndScalars)).join(',\n')},\n\t\t__typename?: ${truthyType}\n}`, 29 | )}`; 30 | }; 31 | const resolveInputType = (i: ParserField, rootNodes: ParserField[], enumsAndScalars: string[]): string => { 32 | if (i.data.type === TypeSystemDefinition.DirectiveDefinition) { 33 | return ''; 34 | } 35 | if (i.data.type === Helpers.Comment) { 36 | return ''; 37 | } 38 | const output = resolveValueTypeFromRoot(i, rootNodes, enumsAndScalars); 39 | return `${plusDescription(i.description)}${output}`; 40 | }; 41 | export const resolveInputTypes = (rootNodes: ParserField[]): string => { 42 | const enumsAndScalars = rootNodes 43 | .filter( 44 | (n) => n.data?.type === TypeDefinition.EnumTypeDefinition || n.data?.type === TypeDefinition.ScalarTypeDefinition, 45 | ) 46 | .map((n) => n.name); 47 | return `export type ${INPUTTYPES} = { 48 | ${rootNodes 49 | .map((f) => resolveInputType(f, rootNodes, enumsAndScalars)) 50 | .filter((v) => v) 51 | .join(';\n\t')} 52 | }`; 53 | }; 54 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/valueTypes/models.ts: -------------------------------------------------------------------------------- 1 | export const VALUETYPES = 'ValueTypes'; 2 | export const INPUTTYPES = 'ResolverInputTypes'; 3 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/TreeToTS/templates/variableTypes/index.ts: -------------------------------------------------------------------------------- 1 | import { VALUETYPES } from '@/TreeToTS/templates/valueTypes/models'; 2 | import { ParserField, TypeDefinition } from 'graphql-js-tree'; 3 | export const ZEUS_VARIABLES = `ZEUS_VARIABLES`; 4 | 5 | export const resolveVariableTypes = (rootNodes: ParserField[]): string => { 6 | const variableTypes = rootNodes 7 | .filter( 8 | (rn) => 9 | rn.data.type === TypeDefinition.InputObjectTypeDefinition || 10 | rn.data.type === TypeDefinition.EnumTypeDefinition || 11 | rn.data.type === TypeDefinition.ScalarTypeDefinition, 12 | ) 13 | .map((rn) => `\t["${rn.name}"]: ${VALUETYPES}["${rn.name}"];`) 14 | .join('\n'); 15 | return `type ${ZEUS_VARIABLES} = ${variableTypes ? `{\n${variableTypes}\n}` : '{}'}`; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/__tests__/TestUtils.ts: -------------------------------------------------------------------------------- 1 | export const trimGraphQL = (s: string) => s.replace(/\s\s+/g, ' ').replace(/(\r\n|\n|\r)/gm, ''); 2 | 3 | export const replSpace = (baseString: string) => (s: string) => 4 | expect(baseString.replace(/\s+/g, '')).toContain(s.replace(/\s+/g, '')); 5 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TreeToTS'; 2 | export * from './Models'; 3 | export * from './TranslateGraphQL'; 4 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | moduleFileExtensions: ['ts', 'tsx', 'js'], 4 | moduleNameMapper: { 5 | '@/(.*)': ['/$1'], 6 | }, 7 | testMatch: ['/**/*.spec.(ts|tsx)'], 8 | watchPathIgnorePatterns: ['node_modules'], 9 | }; 10 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-zeus-core", 3 | "version": "7.0.7", 4 | "private": false, 5 | "main": "./lib/index.js", 6 | "author": "GraphQL Editor, Artur Czemiel", 7 | "scripts": { 8 | "build": "ttsc --build tsconfig.build.json", 9 | "start": "ttsc --build tsconfig.build.json --watch" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/graphql-editor/graphql-zeus/issues" 17 | }, 18 | "dependencies": { 19 | "graphql": "^16.5.0", 20 | "graphql-js-tree": "^3.0.2" 21 | }, 22 | "peerDependencies": { 23 | "graphql-ws": ">=5" 24 | }, 25 | "peerDependenciesMeta": { 26 | "graphql-ws": { 27 | "optional": true 28 | } 29 | }, 30 | "config": { 31 | "commitizen": { 32 | "path": "./node_modules/cz-conventional-changelog" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["lib", "node_modules", "**/*.spec.ts", "__tests__", "jest.config.js"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphql-zeus-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "incremental": true, 10 | "removeComments": true, 11 | "noUnusedLocals": true, 12 | "strictNullChecks": true, 13 | "skipLibCheck": true, 14 | "strict": true, 15 | "outDir": "./lib", 16 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 17 | "rootDir": "./", 18 | "baseUrl": "./", 19 | "composite": true, 20 | "paths": { 21 | "@/*": ["./*"] 22 | }, 23 | "plugins": [ 24 | { 25 | "transform": "typescript-transform-paths" 26 | }, 27 | { 28 | "transform": "typescript-transform-paths", 29 | "afterDeclarations": true 30 | } 31 | ] 32 | }, 33 | "exclude": ["lib", "node_modules", "jest.config.js"] 34 | } 35 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/TreeToJSONSchema/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema7 } from 'json-schema'; 2 | import { Parser } from 'graphql-js-tree'; 3 | import { TreeToJSONSchema } from '@/TreeToJSONSchema'; 4 | describe('TreeToJSONSchema tests', () => { 5 | it('creates correct input field', () => { 6 | const schema = ` 7 | input CreatePerson{ 8 | firstName: String 9 | lastName: String 10 | } 11 | type Person{ name:String friends: [Person!]! } 12 | type Query{ people: [Person] } 13 | type Mutation{ createPerson(person: CreatePerson): String} 14 | schema{ 15 | query: Query 16 | mutation: Mutation 17 | } 18 | `; 19 | const tree = Parser.parseAddExtensions(schema); 20 | const schemaCode = TreeToJSONSchema.parse(tree); 21 | 22 | expect(schemaCode).toHaveProperty('inputs'); 23 | expect(schemaCode).toHaveProperty('types'); 24 | expect(schemaCode.inputs).toHaveProperty('CreatePerson'); 25 | expect(schemaCode.types).toHaveProperty('Mutation'); 26 | expect(schemaCode.types['Mutation']).toHaveProperty('createPerson'); 27 | expect(schemaCode.types['Mutation']['createPerson']['type']).toEqual('object'); 28 | expect(schemaCode.types['Mutation']['createPerson']['properties']).toMatchObject({ 29 | person: { 30 | $ref: '#/inputs/CreatePerson', 31 | }, 32 | } as JSONSchema7); 33 | 34 | expect(schemaCode.types).not.toHaveProperty('Query'); 35 | expect(schemaCode.types).not.toHaveProperty('Person'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/TreeToJSONSchema/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getTypeName, 3 | Options, 4 | ParserField, 5 | ParserTree, 6 | ScalarTypes, 7 | TypeDefinition, 8 | ValueDefinition, 9 | } from 'graphql-js-tree'; 10 | import { JSONSchema7 } from 'json-schema'; 11 | 12 | export type JSONSchemaOverrideProperties = Omit & { 13 | properties: T extends { 14 | [P in keyof T]: T[P]; 15 | } 16 | ? { 17 | [P in keyof T]: JSONSchema7; 18 | } 19 | : never; 20 | }; 21 | 22 | type ExtractPayLoad = T extends [infer PayLoad, any] ? PayLoad : T; 23 | 24 | export type OverrideFormSchema = { 25 | [P in keyof Y]?: { 26 | [R in keyof Y[P]]?: ( 27 | generated: JSONSchemaOverrideProperties>, 28 | ) => JSONSchemaOverrideProperties>> | undefined; 29 | }; 30 | }; 31 | 32 | type ConvertField = { 33 | f: ParserField; 34 | tree: ParserTree; 35 | parent?: ParserField; 36 | override?: OverrideFormSchema; 37 | }; 38 | 39 | const getDataType = ({ f, tree, override }: ConvertField): JSONSchema7 => { 40 | if (f.data.type === ValueDefinition.InputValueDefinition) { 41 | const typeName = getTypeName(f.type.fieldType); 42 | if (typeName === ScalarTypes.Boolean) { 43 | return { type: 'boolean' }; 44 | } 45 | if (typeName === ScalarTypes.Float) { 46 | return { type: 'number' }; 47 | } 48 | if (typeName === ScalarTypes.Int) { 49 | return { type: 'integer' }; 50 | } 51 | if (typeName === ScalarTypes.ID) { 52 | return { type: 'string' }; 53 | } 54 | if (typeName === ScalarTypes.String) { 55 | return { type: 'string' }; 56 | } 57 | 58 | const lookForField = tree.nodes.find((r) => r.name === typeName); 59 | if (lookForField?.data.type === TypeDefinition.ScalarTypeDefinition) { 60 | return { 61 | type: 'string', 62 | }; 63 | } 64 | if (lookForField?.data.type === TypeDefinition.EnumTypeDefinition) { 65 | return { 66 | type: 'string', 67 | enum: lookForField.args?.map((a) => a.name), 68 | }; 69 | } 70 | return { 71 | $ref: `#/inputs/${lookForField?.name}`, 72 | }; 73 | } 74 | // It must be a field then 75 | return { 76 | type: 'object', 77 | required: f?.args?.filter((a) => a.type.fieldType.type === Options.required).map((n) => n.name), 78 | properties: f.args?.reduce((a, b) => { 79 | a[b.name] = convertField({ f: b, tree, override }); 80 | return a; 81 | }, {} as Required['properties']), 82 | }; 83 | }; 84 | 85 | const convertType = (props: ConvertField): JSONSchema7 => { 86 | const { override, parent, f } = props; 87 | const type = getDataType(props); 88 | if (override && parent) { 89 | const fieldOverride = override[parent.name]?.[f.name]; 90 | if (fieldOverride) { 91 | return fieldOverride(type as JSONSchemaOverrideProperties) || {}; 92 | } 93 | } 94 | return type; 95 | }; 96 | const convertField = (props: ConvertField): JSONSchema7 => { 97 | if (props.f.type.fieldType.type === Options.array) { 98 | return { 99 | type: 'array', 100 | items: convertType(props), 101 | }; 102 | } 103 | return convertType(props); 104 | }; 105 | export class TreeToJSONSchema { 106 | static parse(parserTree: ParserTree) { 107 | const inputs = parserTree.nodes 108 | .filter((n) => n.data.type === TypeDefinition.InputObjectTypeDefinition) 109 | .reduce>((a, b) => { 110 | a[b.name] = convertField({ f: b, tree: parserTree }); 111 | return a; 112 | }, {}); 113 | const types = parserTree.nodes 114 | .filter( 115 | (n) => n.data.type === TypeDefinition.ObjectTypeDefinition && n.args?.some((a) => a.args && a.args.length > 0), 116 | ) 117 | .reduce>>((a, b) => { 118 | if (b.args && b.args.length) { 119 | a[b.name] = b.args?.reduce>((c, d) => { 120 | c[d.name] = convertField({ f: d, tree: parserTree }); 121 | return c; 122 | }, {}); 123 | } 124 | return a; 125 | }, {}); 126 | return { 127 | inputs, 128 | types, 129 | }; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TreeToJSONSchema'; 2 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | moduleFileExtensions: ['ts', 'tsx', 'js'], 4 | moduleNameMapper: { 5 | '@/(.*)': ['/$1'], 6 | }, 7 | testMatch: ['/**/*.spec.(ts|tsx)'], 8 | watchPathIgnorePatterns: ['node_modules'], 9 | }; 10 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-zeus-jsonschema", 3 | "version": "7.0.7", 4 | "private": false, 5 | "main": "./lib/index.js", 6 | "author": "GraphQL Editor, Artur Czemiel", 7 | "scripts": { 8 | "build": "ttsc --build tsconfig.build.json", 9 | "start": "ttsc --build tsconfig.build.json --watch" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/graphql-editor/graphql-zeus/issues" 17 | }, 18 | "dependencies": { 19 | "graphql": "^16.5.0", 20 | "graphql-js-tree": "^1.0.5", 21 | "json-schema": "^0.4.0" 22 | }, 23 | "peerDependencies": { 24 | "graphql-ws": ">=5" 25 | }, 26 | "peerDependenciesMeta": { 27 | "graphql-ws": { 28 | "optional": true 29 | } 30 | }, 31 | "config": { 32 | "commitizen": { 33 | "path": "./node_modules/cz-conventional-changelog" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["lib", "node_modules", "**/*.spec.ts", "__tests__", "jest.config.js"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/graphql-zeus-jsonschema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "incremental": true, 10 | "removeComments": true, 11 | "noUnusedLocals": true, 12 | "strictNullChecks": true, 13 | "skipLibCheck": true, 14 | "strict": true, 15 | "outDir": "./lib", 16 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 17 | "rootDir": "./", 18 | "baseUrl": "./", 19 | "composite": true, 20 | "paths": { 21 | "@/*": ["./*"] 22 | }, 23 | "plugins": [ 24 | { 25 | "transform": "typescript-transform-paths" 26 | }, 27 | { 28 | "transform": "typescript-transform-paths", 29 | "afterDeclarations": true 30 | } 31 | ] 32 | }, 33 | "exclude": ["lib", "node_modules", "jest.config.js"] 34 | } 35 | -------------------------------------------------------------------------------- /packages/graphql-zeus/CLIClass.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { TranslateGraphQL, Environment } from 'graphql-zeus-core'; 4 | import { TreeToJSONSchema } from 'graphql-zeus-jsonschema'; 5 | import { Parser } from 'graphql-js-tree'; 6 | import { pluginTypedDocumentNode } from '@/plugins/typedDocumentNode/index.js'; 7 | import { Utils } from '@/Utils/index.js'; 8 | import { config } from '@/config.js'; 9 | /** 10 | * basic yargs interface 11 | */ 12 | interface Yargs { 13 | [x: string]: unknown; 14 | _: (string | number)[]; 15 | $0: string; 16 | } 17 | 18 | /** 19 | * Interface for yargs arguments 20 | */ 21 | interface CliArgs extends Yargs { 22 | header?: string; 23 | esModule?: boolean; 24 | node?: boolean; 25 | graphql?: string; 26 | jsonSchema?: string; 27 | apollo?: boolean; 28 | constEnums?: boolean; 29 | reactQuery?: boolean; 30 | typedDocumentNode?: boolean; 31 | subscriptions?: string; 32 | method?: string; 33 | } 34 | /** 35 | * Main class for controlling CLI 36 | */ 37 | export class CLI { 38 | /** 39 | * Execute yargs provided args 40 | */ 41 | static execute = async (args: T): Promise => { 42 | const env: Environment = args.node ? 'node' : 'browser'; 43 | let schemaFileContents = ''; 44 | const allArgs = args._ as string[]; 45 | const commandLineProvidedOptions = { ...args, urlOrPath: allArgs[0] }; 46 | const schemaFile = await config.getValueOrThrow('urlOrPath', { 47 | commandLineProvidedOptions, 48 | saveOnInput: true, 49 | }); 50 | let host: string | undefined; 51 | if (schemaFile.startsWith('http://') || schemaFile.startsWith('https://')) { 52 | const { header, method } = args; 53 | host = schemaFile; 54 | schemaFileContents = await Utils.getFromUrl(schemaFile, { header, method: method === 'GET' ? 'GET' : 'POST' }); 55 | } 56 | schemaFileContents = schemaFileContents || fs.readFileSync(schemaFile).toString(); 57 | const pathToFile = allArgs[1] || ''; 58 | const tree = Parser.parse(schemaFileContents); 59 | if (args.graphql) { 60 | const schemaPath = 61 | args.graphql.endsWith('.graphql') || args.graphql.endsWith('.gql') 62 | ? args.graphql 63 | : path.join(args.graphql, 'schema.graphql'); 64 | 65 | const pathToSchema = path.dirname(schemaPath); 66 | const schemaFile = path.basename(schemaPath); 67 | writeFileRecursive(pathToSchema, schemaFile, schemaFileContents); 68 | } 69 | if (args.jsonSchema) { 70 | const schemaPath = args.jsonSchema.endsWith('.json') 71 | ? args.jsonSchema 72 | : path.join(args.jsonSchema, 'schema.json'); 73 | 74 | const pathToSchema = path.dirname(schemaPath); 75 | const schemaFile = path.basename(schemaPath); 76 | 77 | const content = TreeToJSONSchema.parse(tree); 78 | writeFileRecursive(pathToSchema, schemaFile, JSON.stringify(content, null, 4)); 79 | } 80 | const typeScriptDefinition = TranslateGraphQL.typescriptSplit({ 81 | schema: schemaFileContents, 82 | env, 83 | host, 84 | esModule: !!args.esModule, 85 | constEnums: !!args.constEnums, 86 | subscriptions: args.subscriptions === 'graphql-ws' ? 'graphql-ws' : 'legacy', 87 | }); 88 | Object.keys(typeScriptDefinition).forEach((k) => 89 | writeFileRecursive( 90 | path.join(pathToFile, 'zeus'), 91 | `${k}.ts`, 92 | typeScriptDefinition[k as keyof typeof typeScriptDefinition], 93 | ), 94 | ); 95 | if (args.typedDocumentNode) { 96 | writeFileRecursive( 97 | path.join(pathToFile, 'zeus'), 98 | `typedDocumentNode.ts`, 99 | pluginTypedDocumentNode(commandLineProvidedOptions), 100 | ); 101 | } 102 | }; 103 | } 104 | 105 | function writeFileRecursive(pathToFile: string, filename: string, data: string): void { 106 | fs.mkdirSync(pathToFile, { recursive: true }); 107 | fs.writeFileSync(path.join(pathToFile, filename), data); 108 | } 109 | -------------------------------------------------------------------------------- /packages/graphql-zeus/Utils/index.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch'; 2 | import { buildClientSchema, getIntrospectionQuery, GraphQLSchema, printSchema } from 'graphql'; 3 | /** 4 | * Class representing all graphql utils needed in Zeus 5 | */ 6 | export class Utils { 7 | /** 8 | * Get GraphQL Schema by doing introspection on specified URL 9 | */ 10 | static getFromUrl = async ( 11 | url: string, 12 | options: { 13 | header?: string | string[]; 14 | method?: 'POST' | 'GET'; 15 | }, 16 | ): Promise => { 17 | const headers: Record = { 18 | 'Content-Type': 'application/json', 19 | }; 20 | if (options.header) { 21 | const allHeaders: string[] = Array.isArray(options.header) ? options.header : [options.header]; 22 | for (const h of allHeaders) { 23 | const [key, ...val] = h.split(':').map((k) => k.trim()); 24 | if (!val) { 25 | throw new Error(`Incorrect Header ${key}`); 26 | } 27 | headers[key] = val.join(':'); 28 | } 29 | } 30 | if (options.method === 'GET') { 31 | const response = await fetch(url + `?query=${getIntrospectionQuery()}`, { 32 | method: 'GET', 33 | headers, 34 | }); 35 | const { data, errors } = await response.json(); 36 | if (errors) { 37 | throw new Error(JSON.stringify(errors, null, 2)); 38 | } 39 | const c = buildClientSchema(data); 40 | return Utils.printFullSchema(c); 41 | } 42 | const response = await fetch(url, { 43 | method: 'POST', 44 | headers, 45 | body: JSON.stringify({ query: getIntrospectionQuery() }), 46 | }); 47 | const { data, errors } = await response.json(); 48 | if (errors) { 49 | throw new Error(JSON.stringify(errors, null, 2)); 50 | } 51 | const c = buildClientSchema(data); 52 | 53 | return Utils.printFullSchema(c); 54 | }; 55 | static printFullSchema = (schema: GraphQLSchema): string => { 56 | const queryType = schema.getQueryType(); 57 | const mutationType = schema.getMutationType(); 58 | const subscriptionType = schema.getSubscriptionType(); 59 | let schemaClient = printSchema(schema); 60 | const schemaPrintedAtTheBeginning = 61 | (queryType && queryType.name !== 'Query') || 62 | (mutationType && mutationType.name !== 'Mutation') || 63 | (subscriptionType && subscriptionType.name !== 'Subscription'); 64 | 65 | if (!schemaPrintedAtTheBeginning) { 66 | const addons = []; 67 | if (queryType) { 68 | addons.push(`query: ${queryType.name}`); 69 | } 70 | if (mutationType) { 71 | addons.push(`mutation: ${mutationType.name}`); 72 | } 73 | if (subscriptionType) { 74 | addons.push(`subscription: ${subscriptionType.name}`); 75 | } 76 | if (addons.length > 0) { 77 | schemaClient += `\nschema{\n\t${addons.join(',\n\t')}\n}`; 78 | } 79 | } 80 | return schemaClient; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /packages/graphql-zeus/config.ts: -------------------------------------------------------------------------------- 1 | import { ConfigMaker } from 'config-maker'; 2 | 3 | export type ProjectOptions = { 4 | urlOrPath: string; 5 | esModule?: boolean; 6 | headers?: { 7 | [x: string]: string; 8 | }; 9 | typedDocumentNode?: boolean; 10 | jsonSchema?: string; 11 | method?: string; 12 | node?: boolean; 13 | subscriptions?: string; 14 | graphql?: string; 15 | }; 16 | 17 | // eslint-disable-next-line @typescript-eslint/ban-types 18 | export const config = new ConfigMaker('graphql-zeus', { 19 | decoders: {}, 20 | defaultValues: { 21 | esModule: false, 22 | method: 'POST', 23 | headers: {}, 24 | node: false, 25 | typedDocumentNode: false, 26 | }, 27 | config: { 28 | autocomplete: { 29 | method: async () => { 30 | return ['GET', 'POST']; 31 | }, 32 | }, 33 | environment: { 34 | urlOrPath: 'ZEUS_SCHEMA', 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /packages/graphql-zeus/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import yargs from 'yargs'; 3 | import { CLI } from '@/CLIClass.js'; 4 | const args = yargs(process.argv.slice(2)) 5 | .usage( 6 | ` 7 | Zeus⚡⚡⚡ 8 | GraphQL Autocomplete Client Library generator 9 | 10 | Load from file or url (url must start with http:// or https:// ): 11 | zeus [path] [output_path] [options] 12 | `, 13 | ) 14 | .option('node', { 15 | alias: 'n', 16 | describe: 'Generate client for NodeJS( default is for browser and react-native )', 17 | boolean: true, 18 | }) 19 | .option('esModule', { 20 | alias: 'es', 21 | describe: 'Use .js import in TypeScript to use with esModules', 22 | boolean: true, 23 | }) 24 | .option('constEnums', { 25 | alias: 'ce', 26 | describe: 'Use .js import in TypeScript to use with esModules', 27 | boolean: true, 28 | }) 29 | .option('typedDocumentNode', { 30 | alias: 'td', 31 | describe: 'Generate TypedDocumentNode createQuery module', 32 | boolean: true, 33 | }) 34 | .option('method', { 35 | alias: 'm', 36 | describe: 'provide request method "GET" or "POST" post by default', 37 | choices: ['POST', 'GET'], 38 | }) 39 | .option('header', { 40 | alias: 'h', 41 | describe: 42 | 'Additional header flag. You can also pass multiple headers like this -h myheader:123123 -h myheader2:321321', 43 | string: true, 44 | }) 45 | .option('graphql', { 46 | alias: 'g', 47 | describe: 'Download and save schema also. Path where .graphql schema file should be put. ', 48 | string: true, 49 | }) 50 | .option('jsonSchema', { 51 | alias: 'j', 52 | describe: 53 | 'Generate JSON Schema to create forms from inputs and type fields with args. Path where .json schema file should be put. ', 54 | string: true, 55 | }) 56 | .option('subscriptions', { 57 | alias: 's', 58 | describe: 'The underlying implementation of realtime subscriptions.', 59 | choices: ['legacy', 'graphql-ws'], 60 | default: 'legacy', 61 | }) 62 | .demandCommand(1).argv; 63 | CLI.execute(args); 64 | -------------------------------------------------------------------------------- /packages/graphql-zeus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-zeus", 3 | "version": "7.0.7", 4 | "private": false, 5 | "scripts": { 6 | "start": "ttsc --watch", 7 | "build": "ttsc" 8 | }, 9 | "author": "GraphQL Editor, Artur Czemiel", 10 | "license": "MIT", 11 | "description": "Generate Client Library for GraphQL Schema", 12 | "homepage": "https://graphqleditor.com", 13 | "main": "lib/index.js", 14 | "types": "lib/index.d.ts", 15 | "type": "module", 16 | "bin": { 17 | "zeus": "lib/index.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/graphql-editor/graphql-zeus.git" 25 | }, 26 | "dependencies": { 27 | "config-maker": "^0.0.6", 28 | "cross-fetch": "^3.0.4", 29 | "graphql-zeus-core": "^7.0.7", 30 | "graphql-zeus-jsonschema": "^7.0.7", 31 | "yargs": "^16.1.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/graphql-zeus/plugins/typedDocumentNode/index.ts: -------------------------------------------------------------------------------- 1 | import { ProjectOptions } from '@/config.js'; 2 | 3 | export const pluginTypedDocumentNode = ({ esModule, node }: Partial) => `/* eslint-disable */ 4 | import { TypedDocumentNode } from '@graphql-typed-document-node/core'; 5 | import ${esModule ? '{ gql }' : 'gql'} from 'graphql-tag'; 6 | import { 7 | ValueTypes, 8 | GenericOperation, 9 | OperationOptions, 10 | GraphQLTypes, 11 | InputType, 12 | ScalarDefinition, 13 | ThunderGraphQLOptions, 14 | Zeus, 15 | ExtractVariables, 16 | } from './${esModule || !!node ? 'index.js' : ''}'; 17 | import { Ops } from './const${esModule || !!node ? '.js' : ''}'; 18 | 19 | export const typedGql = 20 | >( 21 | operation: O, 22 | graphqlOptions?: ThunderGraphQLOptions, 23 | ) => 24 | (o: Z & { 25 | [P in keyof Z]: P extends keyof ValueTypes[R] ? Z[P] : never; 26 | }, ops?: OperationOptions) => { 27 | const str = Zeus(operation, o, { 28 | operationOptions: ops, 29 | scalars: graphqlOptions?.scalars, 30 | }); 31 | return gql(str) as TypedDocumentNode, ExtractVariables>; 32 | }; 33 | `; 34 | -------------------------------------------------------------------------------- /packages/graphql-zeus/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "incremental": true, 10 | "removeComments": true, 11 | "noUnusedLocals": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strictNullChecks": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "outDir": "./lib", 17 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 18 | "rootDir": "./", 19 | "baseUrl": "./", 20 | "typeRoots": ["../../node_modules/@types"], 21 | "paths": { 22 | "@/*": ["./*"] 23 | }, 24 | "plugins": [ 25 | { 26 | "transform": "typescript-transform-paths" 27 | }, 28 | { 29 | "transform": "typescript-transform-paths", 30 | "afterDeclarations": true 31 | } 32 | ] 33 | }, 34 | "exclude": ["lib", "node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /run-example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $1; 4 | npm run start; 5 | cd ../../ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./packages/graphql-zeus-jsonschema" 6 | }, 7 | { 8 | "path": "./packages/graphql-zeus-core" 9 | }, 10 | { 11 | "path": "./packages/graphql-zeus" 12 | }, 13 | { 14 | "path": "./examples/typescript-node" 15 | } 16 | ] 17 | } 18 | --------------------------------------------------------------------------------