├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASING.md ├── __tests__ └── spec.ts ├── config.yml ├── doc-exampleGenerator.ts ├── index.ts ├── lib ├── fromGraphQLSchema.ts ├── fromIntrospectionQuery.ts ├── reducer.ts ├── typeGuards.ts ├── types.ts └── typesMapping.ts ├── nodemon.json ├── package-lock.json ├── package.json ├── renovate.json ├── test-utils.ts ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:14.1 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # TypeScript compile 37 | - run: 38 | command: | 39 | set +e 40 | ./node_modules/typescript/bin/tsc > /dev/null; echo "typescript ftw" 41 | 42 | # Prettier check 43 | - run: 44 | command: | 45 | npm run prettier:check 46 | 47 | # run tests! 48 | - run: yarn test 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn stuff 55 | yarn.lock 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | dist/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/ 3 | !dist/* 4 | !dist/**/* 5 | !definitions/ 6 | !definitions/* 7 | !*.ts 8 | !lib/*.ts 9 | !package.json 10 | !*.md -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.21 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.10.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.9.1...v0.10.0) (2023-02-01) 6 | 7 | ### [0.9.1](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.9.0...v0.9.1) (2022-03-07) 8 | 9 | ## [0.9.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.8.0...v0.9.0) (2021-09-22) 10 | 11 | ## [0.8.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.7.0...v0.8.0) (2021-09-17) 12 | 13 | ## [0.7.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.6.0...v0.7.0) (2021-07-21) 14 | 15 | ## [0.6.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.5.1...v0.6.0) (2021-02-25) 16 | 17 | ### [0.5.1](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.5.0...v0.5.1) (2021-02-08) 18 | 19 | ## [0.5.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.4.0...v0.5.0) (2021-02-01) 20 | 21 | ## [0.4.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.3.1...v0.4.0) (2021-01-24) 22 | 23 | ### [0.3.1](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.3.1-0...v0.3.1) (2021-01-21) 24 | 25 | ### [0.3.1-0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.3.0...v0.3.1-0) (2021-01-19) 26 | 27 | ## [0.3.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.2.0...v0.3.0) (2021-01-19) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * **reducer:** typing fix ([c697c0c](https://github.com/wittydeveloper/graphql-to-json-schema/commit/c697c0c98a68b01738a9aae69ac5789b0d840ebd)) 33 | 34 | 35 | # [0.2.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.1.2-2...v0.2.0) (2019-11-10) 36 | 37 | 38 | 39 | 40 | ## [0.1.1](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.1.0...v0.1.1) (2019-02-15) 41 | 42 | 43 | 44 | 45 | # [0.1.0](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.1.0-1...v0.1.0) (2018-12-29) 46 | 47 | 48 | 49 | 50 | # [0.1.0-1](https://github.com/wittydeveloper/graphql-to-json-schema/compare/v0.1.0-0...v0.1.0-1) (2018-05-31) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * **packaging:** move lib/index to root (.) ([a359398](https://github.com/wittydeveloper/graphql-to-json-schema/commit/a359398)) 56 | 57 | 58 | 59 | 60 | # 0.1.0-0 (2018-05-31) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **lib:** support NOT_NULL of LIST of type ([6ca0354](https://github.com/wittydeveloper/graphql-to-json-schema/commit/6ca0354)) 66 | 67 | 68 | ### Features 69 | 70 | * **lib:** first working version with basic schema ([1ac5b4a](https://github.com/wittydeveloper/graphql-to-json-schema/commit/1ac5b4a)) 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Charly POLY 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 | # GraphQL Schema to JSON Schema [![npm version](https://badge.fury.io/js/graphql-2-json-schema.svg)](https://badge.fury.io/js/graphql-2-json-schema) 2 | 3 | `graphql-2-json-schema` package 4 | 5 | ----------- 6 | 7 | Transform a GraphQL Schema introspection file to a valid JSON Schema. 8 | 9 | ## Usage 10 | 11 | ```ts 12 | import { 13 | graphqlSync, 14 | getIntrospectionQuery, 15 | IntrospectionQuery 16 | } from 'graphql'; 17 | 18 | import { fromIntrospectionQuery } from 'graphql-2-json-schema'; 19 | 20 | const options = { 21 | // Whether or not to ignore GraphQL internals that are probably not relevant 22 | // to documentation generation. 23 | // Defaults to `true` 24 | ignoreInternals: true, 25 | // Whether or not to properly represent GraphQL Lists with Nullable elements 26 | // as type "array" with items being an "anyOf" that includes the possible 27 | // type and a "null" type. 28 | // Defaults to `false` for backwards compatibility, but in future versions 29 | // the effect of `true` is likely going to be the default and only way. It is 30 | // highly recommended that new implementations set this value to `true`. 31 | nullableArrayItems: true, 32 | // Indicates how to define the `ID` scalar as part of a JSON Schema. Valid options 33 | // are `string`, `number`, or `both`. Defaults to `string` 34 | idTypeMapping: 'string' 35 | } 36 | 37 | // schema is your GraphQL schema. 38 | const introspection = graphqlSync(schema, getIntrospectionQuery()).data as IntrospectionQuery; 39 | 40 | const jsonSchema = fromIntrospectionQuery(introspection, options); 41 | ``` 42 | 43 | ## Example 44 | 45 | ### Input 46 | 47 | ```graphql 48 | type Todo { 49 | id: ID! 50 | name: String! 51 | completed: Boolean 52 | color: Color 53 | 54 | "A field that requires an argument" 55 | colors( 56 | filter: [Color!]! 57 | ): [Color!]! 58 | } 59 | 60 | type SimpleTodo { 61 | id: ID! 62 | name: String! 63 | } 64 | 65 | union TodoUnion = Todo | SimpleTodo 66 | 67 | input TodoInputType { 68 | name: String! 69 | completed: Boolean 70 | color: Color=RED 71 | } 72 | 73 | enum Color { 74 | "Red color" 75 | RED 76 | "Green color" 77 | GREEN 78 | } 79 | 80 | type Query { 81 | "A Query with 1 required argument and 1 optional argument" 82 | todo( 83 | id: ID!, 84 | "A default value of false" 85 | isCompleted: Boolean=false 86 | ): Todo 87 | 88 | "Returns a list (or null) that can contain null values" 89 | todos( 90 | "Required argument that is a list that cannot contain null values" 91 | ids: [String!]! 92 | ): [Todo] 93 | } 94 | 95 | type Mutation { 96 | "A Mutation with 1 required argument" 97 | create_todo( 98 | todo: TodoInputType! 99 | ): Todo! 100 | 101 | "A Mutation with 2 required arguments" 102 | update_todo( 103 | id: ID!, 104 | data: TodoInputType! 105 | ): Todo! 106 | 107 | "Returns a list (or null) that can contain null values" 108 | update_todos( 109 | ids: [String!]! 110 | data: TodoInputType! 111 | ): [Todo] 112 | } 113 | ``` 114 | 115 | ### Output 116 | 117 | ```js 118 | // Output is from call to fromIntrospectionQuery with the following options: 119 | const options = { nullableArrayItems: true } 120 | 121 | { 122 | '$schema': 'http://json-schema.org/draft-06/schema#', 123 | properties: { 124 | Query: { 125 | type: 'object', 126 | properties: { 127 | todo: { 128 | description: 'A Query with 1 required argument and 1 optional argument', 129 | type: 'object', 130 | properties: { 131 | return: { '$ref': '#/definitions/Todo' }, 132 | arguments: { 133 | type: 'object', 134 | properties: { 135 | id: { '$ref': '#/definitions/ID' }, 136 | isCompleted: { 137 | description: 'A default value of false', 138 | '$ref': '#/definitions/Boolean', 139 | default: false 140 | } 141 | }, 142 | required: [ 'id' ] 143 | } 144 | }, 145 | required: [] 146 | }, 147 | todos: { 148 | description: 'Returns a list (or null) that can contain null values', 149 | type: 'object', 150 | properties: { 151 | return: { 152 | type: 'array', 153 | items: { 154 | anyOf: [ { '$ref': '#/definitions/Todo' }, { type: 'null' } ] 155 | } 156 | }, 157 | arguments: { 158 | type: 'object', 159 | properties: { 160 | ids: { 161 | description: 'Required argument that is a list that cannot contain null values', 162 | type: 'array', 163 | items: { '$ref': '#/definitions/String' } 164 | } 165 | }, 166 | required: [ 'ids' ] 167 | } 168 | }, 169 | required: [] 170 | } 171 | }, 172 | required: [] 173 | }, 174 | Mutation: { 175 | type: 'object', 176 | properties: { 177 | create_todo: { 178 | description: 'A Mutation with 1 required argument', 179 | type: 'object', 180 | properties: { 181 | return: { '$ref': '#/definitions/Todo' }, 182 | arguments: { 183 | type: 'object', 184 | properties: { todo: { '$ref': '#/definitions/TodoInputType' } }, 185 | required: [ 'todo' ] 186 | } 187 | }, 188 | required: [] 189 | }, 190 | update_todo: { 191 | description: 'A Mutation with 2 required arguments', 192 | type: 'object', 193 | properties: { 194 | return: { '$ref': '#/definitions/Todo' }, 195 | arguments: { 196 | type: 'object', 197 | properties: { 198 | id: { '$ref': '#/definitions/ID' }, 199 | data: { '$ref': '#/definitions/TodoInputType' } 200 | }, 201 | required: [ 'id', 'data' ] 202 | } 203 | }, 204 | required: [] 205 | }, 206 | update_todos: { 207 | description: 'Returns a list (or null) that can contain null values', 208 | type: 'object', 209 | properties: { 210 | return: { 211 | type: 'array', 212 | items: { 213 | anyOf: [ { '$ref': '#/definitions/Todo' }, { type: 'null' } ] 214 | } 215 | }, 216 | arguments: { 217 | type: 'object', 218 | properties: { 219 | ids: { 220 | type: 'array', 221 | items: { '$ref': '#/definitions/String' } 222 | }, 223 | data: { '$ref': '#/definitions/TodoInputType' } 224 | }, 225 | required: [ 'ids', 'data' ] 226 | } 227 | }, 228 | required: [] 229 | } 230 | }, 231 | required: [] 232 | } 233 | }, 234 | definitions: { 235 | Todo: { 236 | type: 'object', 237 | properties: { 238 | id: { 239 | type: 'object', 240 | properties: { 241 | return: { '$ref': '#/definitions/ID' }, 242 | arguments: { type: 'object', properties: {}, required: [] } 243 | }, 244 | required: [] 245 | }, 246 | name: { 247 | type: 'object', 248 | properties: { 249 | return: { '$ref': '#/definitions/String' }, 250 | arguments: { type: 'object', properties: {}, required: [] } 251 | }, 252 | required: [] 253 | }, 254 | completed: { 255 | type: 'object', 256 | properties: { 257 | return: { '$ref': '#/definitions/Boolean' }, 258 | arguments: { type: 'object', properties: {}, required: [] } 259 | }, 260 | required: [] 261 | }, 262 | color: { 263 | type: 'object', 264 | properties: { 265 | return: { '$ref': '#/definitions/Color' }, 266 | arguments: { type: 'object', properties: {}, required: [] } 267 | }, 268 | required: [] 269 | }, 270 | colors: { 271 | description: 'A field that requires an argument', 272 | type: 'object', 273 | properties: { 274 | return: { type: 'array', items: { '$ref': '#/definitions/Color' } }, 275 | arguments: { 276 | type: 'object', 277 | properties: { 278 | filter: { 279 | type: 'array', 280 | items: { '$ref': '#/definitions/Color' } 281 | } 282 | }, 283 | required: [ 'filter' ] 284 | } 285 | }, 286 | required: [] 287 | } 288 | }, 289 | required: [ 'id', 'name', 'colors' ] 290 | }, 291 | ID: { 292 | description: 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', 293 | type: 'string', 294 | title: 'ID' 295 | }, 296 | SimpleTodo: { 297 | type: 'object', 298 | properties: { 299 | id: { 300 | type: 'object', 301 | properties: { 302 | return: { '$ref': '#/definitions/ID' }, 303 | arguments: { type: 'object', properties: {}, required: [] } 304 | }, 305 | required: [] 306 | }, 307 | name: { 308 | type: 'object', 309 | properties: { 310 | return: { '$ref': '#/definitions/String' }, 311 | arguments: { type: 'object', properties: {}, required: [] } 312 | }, 313 | required: [] 314 | } 315 | }, 316 | required: [ 'id', 'name' ] 317 | }, 318 | TodoUnion: { 319 | oneOf: [ 320 | { '$ref': '#/definitions/Todo' }, 321 | { '$ref': '#/definitions/SimpleTodo' } 322 | ] 323 | }, 324 | TodoInputType: { 325 | type: 'object', 326 | properties: { 327 | name: { '$ref': '#/definitions/String' }, 328 | completed: { '$ref': '#/definitions/Boolean' }, 329 | color: { '$ref': '#/definitions/Color', default: 'RED' } 330 | }, 331 | required: [ 'name' ] 332 | }, 333 | Color: { 334 | type: 'string', 335 | anyOf: [ 336 | { 337 | description: 'Red color', 338 | enum: [ 'RED' ], 339 | title: 'Red color' 340 | }, 341 | { 342 | description: 'Green color', 343 | enum: [ 'GREEN' ], 344 | title: 'Green color' 345 | } 346 | ] 347 | }, 348 | String: { 349 | description: 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', 350 | type: 'string', 351 | title: 'String' 352 | }, 353 | Boolean: { 354 | description: 'The `Boolean` scalar type represents `true` or `false`.', 355 | type: 'boolean', 356 | title: 'Boolean' 357 | } 358 | } 359 | } 360 | ``` 361 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | - when you land commits on your master branch, select the Squash and Merge option. 2 | - add a title and body that follows the conventional-changelog-standard conventions. 3 | - when you're ready to release to npm: 4 | - `git checkout master; git pull origin master` 5 | - `standard-version --release-as ` 6 | - `git push --follow-tags origin master; npm publish` 7 | -------------------------------------------------------------------------------- /__tests__/spec.ts: -------------------------------------------------------------------------------- 1 | import ajv from 'ajv' 2 | import { JSONSchema6 } from 'json-schema' 3 | import { fromIntrospectionQuery } from '../lib/fromIntrospectionQuery' 4 | import type { IDTypeMapping as IDTypeMappingType } from '../lib/types' 5 | import { 6 | getTodoSchemaIntrospection, 7 | todoSchemaAsJsonSchema, 8 | todoSchemaAsJsonSchemaWithoutNullableArrayItems, 9 | todoSchemaAsJsonSchemaWithIdTypeNumber, 10 | todoSchemaAsJsonSchemaWithIdTypeStringOrNumber, 11 | } from '../test-utils' 12 | 13 | describe('GraphQL to JSON Schema', () => { 14 | const { introspection } = getTodoSchemaIntrospection() 15 | 16 | test('from IntrospectionQuery object', () => { 17 | const result = fromIntrospectionQuery(introspection) 18 | expect(result).toEqual( 19 | todoSchemaAsJsonSchemaWithoutNullableArrayItems 20 | ) 21 | const validator = new ajv() 22 | validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) 23 | expect(validator.validateSchema(result)).toBe(true) 24 | }) 25 | 26 | test('from IntrospectionQuery object with nullableArrayItems = true', () => { 27 | const options = { 28 | nullableArrayItems: true, 29 | } 30 | const result = fromIntrospectionQuery(introspection, options) 31 | expect(result).toEqual(todoSchemaAsJsonSchema) 32 | const validator = new ajv() 33 | validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) 34 | expect(validator.validateSchema(result)).toBe(true) 35 | }) 36 | 37 | test('from IntrospectionQuery object with idTypeMapping = "number"', () => { 38 | const options = { 39 | nullableArrayItems: true, 40 | idTypeMapping: 'number' as IDTypeMappingType, 41 | } 42 | const result = fromIntrospectionQuery(introspection, options) 43 | expect(result).toEqual(todoSchemaAsJsonSchemaWithIdTypeNumber) 44 | const validator = new ajv() 45 | validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) 46 | expect(validator.validateSchema(result)).toBe(true) 47 | }) 48 | 49 | test('from IntrospectionQuery object with idTypeMapping = "both"', () => { 50 | const options = { 51 | nullableArrayItems: true, 52 | idTypeMapping: 'both' as IDTypeMappingType, 53 | } 54 | const result = fromIntrospectionQuery(introspection, options) 55 | expect(result).toEqual( 56 | todoSchemaAsJsonSchemaWithIdTypeStringOrNumber 57 | ) 58 | const validator = new ajv() 59 | validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) 60 | expect(validator.validateSchema(result)).toBe(true) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:latest 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn test 38 | -------------------------------------------------------------------------------- /doc-exampleGenerator.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from 'util' 2 | import { spawn } from 'child_process' 3 | import { filter } from 'lodash' 4 | import { 5 | buildSchema, 6 | graphqlSync, 7 | IntrospectionQuery, 8 | getIntrospectionQuery, 9 | } from 'graphql' 10 | 11 | import { fromIntrospectionQuery } from './lib/fromIntrospectionQuery' 12 | 13 | const nativeScalarsToFilter = ['String', 'Int', 'Boolean'] 14 | 15 | const readmeSDL: string = ` 16 | type Todo { 17 | id: ID! 18 | name: String! 19 | completed: Boolean 20 | color: Color 21 | 22 | "A field that requires an argument" 23 | colors( 24 | filter: [Color!]! 25 | ): [Color!]! 26 | } 27 | 28 | type SimpleTodo { 29 | id: ID! 30 | name: String! 31 | } 32 | 33 | union TodoUnion = Todo | SimpleTodo 34 | 35 | input TodoInputType { 36 | name: String! 37 | completed: Boolean 38 | color: Color=RED 39 | } 40 | 41 | enum Color { 42 | "Red color" 43 | RED 44 | "Green color" 45 | GREEN 46 | } 47 | 48 | type Query { 49 | "A Query with 1 required argument and 1 optional argument" 50 | todo( 51 | id: ID!, 52 | "A default value of false" 53 | isCompleted: Boolean=false 54 | ): Todo 55 | 56 | "Returns a list (or null) that can contain null values" 57 | todos( 58 | "Required argument that is a list that cannot contain null values" 59 | ids: [String!]! 60 | ): [Todo] 61 | } 62 | 63 | type Mutation { 64 | "A Mutation with 1 required argument" 65 | create_todo( 66 | todo: TodoInputType! 67 | ): Todo! 68 | 69 | "A Mutation with 2 required arguments" 70 | update_todo( 71 | id: ID!, 72 | data: TodoInputType! 73 | ): Todo! 74 | 75 | "Returns a list (or null) that can contain null values" 76 | update_todos( 77 | ids: [String!]! 78 | data: TodoInputType! 79 | ): [Todo] 80 | } 81 | ` 82 | 83 | const readmeSchema = buildSchema(readmeSDL) 84 | const introspectionQueryJSON = graphqlSync( 85 | readmeSchema, 86 | getIntrospectionQuery() 87 | ).data as IntrospectionQuery 88 | 89 | const options = { 90 | nullableArrayItems: true, 91 | } 92 | 93 | const readmeResult = fromIntrospectionQuery(introspectionQueryJSON, options) 94 | 95 | // Get rid of undefined values this way 96 | const cleanedUpReadmeResult = JSON.parse(JSON.stringify(readmeResult)) 97 | 98 | const startsWithTestGenerator = (stringToTest: string) => { 99 | return (stringToLookFor: string) => 100 | stringToTest.startsWith(`${stringToLookFor}:`) 101 | } 102 | 103 | const keyComparator = (a: string, b: string) => { 104 | // description to the top 105 | if (['description'].some(startsWithTestGenerator(a))) { 106 | // If the other one also satisfies the test (which is impossible, but ok) then 107 | // there is no sort change 108 | return ['description'].some(startsWithTestGenerator(b)) ? 0 : -1 109 | } 110 | if (['description'].some(startsWithTestGenerator(b))) { 111 | return 1 112 | } 113 | 114 | // Native Scalars to the bottom 115 | if (nativeScalarsToFilter.some(startsWithTestGenerator(a))) { 116 | // If the other one also satisfies the test, then no sort change 117 | return nativeScalarsToFilter.some(startsWithTestGenerator(b)) ? 0 : 1 118 | } 119 | if (nativeScalarsToFilter.some(startsWithTestGenerator(b))) { 120 | return -1 121 | } 122 | 123 | // Stay the same 124 | return 0 125 | } 126 | 127 | const output = `### Input 128 | 129 | \`\`\`graphql${readmeSDL}\`\`\` 130 | 131 | ### Output 132 | 133 | \`\`\`js 134 | // Output is from call to fromIntrospectionQuery with the following options: 135 | const options = ${inspect(options)} 136 | 137 | ${inspect(cleanedUpReadmeResult, { depth: null, sorted: keyComparator })} 138 | \`\`\` 139 | ` 140 | 141 | console.log(` 142 | 143 | ${output} 144 | 145 | `) 146 | 147 | if (process.platform === 'darwin') { 148 | const proc = spawn('pbcopy') 149 | proc.stdin.write(output) 150 | proc.stdin.end() 151 | console.log('OUTPUT COPIED TO YOUR CLIPBOARD!!!\n') 152 | } 153 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/fromIntrospectionQuery' 2 | // export * from './fromGraphQLSchema'; 3 | -------------------------------------------------------------------------------- /lib/fromGraphQLSchema.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql' 2 | import { JSONSchema6 } from 'json-schema' 3 | 4 | export const fromIntrospectionQuery = (schema: GraphQLSchema): JSONSchema6 => { 5 | // TODO: implement. 6 | return {} 7 | } 8 | -------------------------------------------------------------------------------- /lib/fromIntrospectionQuery.ts: -------------------------------------------------------------------------------- 1 | import { IntrospectionQuery, IntrospectionType } from 'graphql' 2 | import { JSONSchema6 } from 'json-schema' 3 | import { includes, partition, reduce } from 'lodash' 4 | import { introspectionTypeReducer, JSONSchema6Acc } from './reducer' 5 | import { 6 | ID_TYPE_MAPPING_OPTION_DEFAULT, 7 | filterDefinitionsTypes, 8 | isIntrospectionObjectType, 9 | } from './typeGuards' 10 | 11 | import type { IDTypeMapping as IDTypeMappingType } from './types' 12 | 13 | // FIXME: finish this type 14 | export interface GraphQLJSONSchema6 extends JSONSchema6 { 15 | properties: { 16 | Query: JSONSchema6Acc 17 | Mutation: JSONSchema6Acc 18 | } 19 | definitions: JSONSchema6Acc 20 | } 21 | 22 | export interface FromIntrospectionQueryOptions { 23 | ignoreInternals?: boolean 24 | nullableArrayItems?: boolean 25 | idTypeMapping?: IDTypeMappingType 26 | } 27 | 28 | export const fromIntrospectionQuery = ( 29 | introspection: IntrospectionQuery, 30 | opts?: FromIntrospectionQueryOptions 31 | ): JSONSchema6 => { 32 | const options = { 33 | // Defaults 34 | ignoreInternals: true, 35 | nullableArrayItems: false, 36 | idTypeMapping: ID_TYPE_MAPPING_OPTION_DEFAULT, 37 | // User-specified 38 | ...(opts || {}), 39 | } 40 | const { queryType, mutationType } = introspection.__schema 41 | 42 | if (mutationType) { 43 | const rootMutationType = (introspection.__schema.types as any).find( 44 | (t: any) => t.name == mutationType.name 45 | ) 46 | if (rootMutationType) { 47 | ;(introspection.__schema.types as any).Mutation = rootMutationType 48 | ;(introspection.__schema.types as any).Mutation.name = 'Mutation' 49 | } 50 | } 51 | 52 | if (queryType) { 53 | const rootQueryType = (introspection.__schema.types as any).find( 54 | (t: any) => t.name == queryType.name 55 | ) 56 | if (rootQueryType) { 57 | ;(introspection.__schema.types as any).Query = rootQueryType 58 | ;(introspection.__schema.types as any).Query.name = 'Query' 59 | } 60 | } 61 | ////////////////////////////////////////////////////////////////////// 62 | //// Query and Mutation are properties, custom Types are definitions 63 | ////////////////////////////////////////////////////////////////////// 64 | const [properties, definitions] = partition( 65 | introspection.__schema.types, 66 | (type) => 67 | isIntrospectionObjectType(type) && 68 | includes(['Query', 'Mutation'], type.name) 69 | ) 70 | 71 | return { 72 | $schema: 'http://json-schema.org/draft-06/schema#', 73 | properties: reduce( 74 | properties, 75 | introspectionTypeReducer('properties', options), 76 | {} 77 | ), 78 | definitions: reduce( 79 | filterDefinitionsTypes(definitions, options), 80 | introspectionTypeReducer('definitions', options), 81 | {} 82 | ), 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/reducer.ts: -------------------------------------------------------------------------------- 1 | import JSON5 from 'json5' 2 | import { 3 | IntrospectionField, 4 | IntrospectionInputValue, 5 | IntrospectionScalarType, 6 | IntrospectionType, 7 | } from 'graphql' 8 | import { JSONSchema6 } from 'json-schema' 9 | import { filter, map, MemoListIterator, reduce } from 'lodash' 10 | import { 11 | isIntrospectionEnumType, 12 | isIntrospectionField, 13 | isIntrospectionInputObjectType, 14 | isIntrospectionInterfaceType, 15 | isIntrospectionInputValue, 16 | isIntrospectionListTypeRef, 17 | isIntrospectionObjectType, 18 | isIntrospectionUnionType, 19 | isNonNullIntrospectionType, 20 | isIntrospectionScalarType, 21 | isIntrospectionDefaultScalarType, 22 | } from './typeGuards' 23 | import { graphqlToJSONType, scalarToJsonType } from './typesMapping' 24 | 25 | import type { 26 | GraphQLTypeNames, 27 | IDTypeMapping as IDTypeMappingType, 28 | } from './types' 29 | 30 | export type JSONSchema6Acc = { 31 | [k: string]: JSONSchema6 32 | } 33 | 34 | type ReducerOptions = { 35 | nullableArrayItems?: boolean 36 | idTypeMapping?: IDTypeMappingType 37 | } 38 | 39 | type GetRequiredFieldsType = ReadonlyArray< 40 | IntrospectionInputValue | IntrospectionField 41 | > 42 | // Extract GraphQL no-nullable types 43 | export const getRequiredFields = (fields: GetRequiredFieldsType) => 44 | reduce( 45 | fields, 46 | (acc: string[], f) => { 47 | if (isNonNullIntrospectionType(f.type)) { 48 | acc.push(f.name) 49 | } 50 | return acc 51 | }, 52 | [] 53 | ) 54 | 55 | export type IntrospectionFieldReducerItem = 56 | | IntrospectionField 57 | | IntrospectionInputValue 58 | 59 | // Wrapper for creating a reducer that allows for passing options 60 | // to the reducer 61 | export const introspectionFieldReducerGenerator: ( 62 | options: ReducerOptions 63 | ) => MemoListIterator< 64 | IntrospectionFieldReducerItem, 65 | JSONSchema6Acc, 66 | ReadonlyArray 67 | > = (options) => { 68 | // reducer for a types and inputs 69 | const introspectionFieldReducer: MemoListIterator< 70 | IntrospectionFieldReducerItem, 71 | JSONSchema6Acc, 72 | ReadonlyArray 73 | > = (acc, curr: IntrospectionFieldReducerItem): JSONSchema6Acc => { 74 | if (isIntrospectionField(curr)) { 75 | const returnType = isNonNullIntrospectionType(curr.type) 76 | ? graphqlToJSONType(curr.type.ofType, options) 77 | : graphqlToJSONType(curr.type, options) 78 | 79 | acc[curr.name] = { 80 | type: 'object', 81 | properties: { 82 | return: returnType, 83 | arguments: { 84 | type: 'object', 85 | properties: reduce( 86 | curr.args as IntrospectionFieldReducerItem[], 87 | introspectionFieldReducer, 88 | {} 89 | ), 90 | required: getRequiredFields(curr.args), 91 | }, 92 | }, 93 | required: [], 94 | } 95 | } else if (isIntrospectionInputValue(curr)) { 96 | const returnType = isNonNullIntrospectionType(curr.type) 97 | ? graphqlToJSONType(curr.type.ofType, options) 98 | : graphqlToJSONType(curr.type, options) 99 | 100 | if (curr.defaultValue) { 101 | returnType.default = resolveDefaultValue(curr) 102 | } 103 | acc[curr.name] = returnType 104 | } 105 | 106 | acc[curr.name].description = curr.description || undefined 107 | return acc 108 | } 109 | 110 | return introspectionFieldReducer 111 | } 112 | 113 | // ENUM type defaults will not JSON.parse correctly, so we have to do some work 114 | // TODO: fix typing here 115 | export const resolveDefaultValue = (curr: any) => { 116 | let type = curr.type 117 | let isList = false 118 | 119 | // Dig out the underlying type in case it's a LIST or a NON_NULL or both 120 | while (true) { 121 | if (isIntrospectionListTypeRef(type)) { 122 | isList = true 123 | type = type.ofType 124 | } else if (isNonNullIntrospectionType(type)) { 125 | type = type.ofType 126 | } else { 127 | break 128 | } 129 | } 130 | // Not an ENUM? No problem...just JSON parse it 131 | if (typeof curr.defaultValue === 'string' && !isIntrospectionEnumType(type)) { 132 | return JSON5.parse(curr.defaultValue) 133 | } 134 | 135 | if (!isList || !curr.defaultValue || typeof curr.defaultValue !== 'string') { 136 | return curr.defaultValue 137 | } 138 | 139 | // Take a string like "[RED, GREEN]" and convert it to ["RED", "GREEN"] 140 | return curr.defaultValue 141 | .substr(1, curr.defaultValue.length - 2) 142 | .split(',') 143 | .map((val: string) => val.trim()) 144 | } 145 | 146 | // Reducer for each type exposed by the GraphQL Schema 147 | export const introspectionTypeReducer: ( 148 | type: 'definitions' | 'properties', 149 | options: ReducerOptions 150 | ) => MemoListIterator< 151 | IntrospectionType, 152 | JSONSchema6Acc, 153 | IntrospectionType[] 154 | > = (type, options) => (acc, curr: IntrospectionType): JSONSchema6Acc => { 155 | const isQueriesOrMutations = type === 'properties' 156 | 157 | if (isIntrospectionObjectType(curr)) { 158 | acc[curr.name] = { 159 | type: 'object', 160 | properties: reduce( 161 | curr.fields as IntrospectionFieldReducerItem[], 162 | introspectionFieldReducerGenerator(options), 163 | {} 164 | ), 165 | // Query and Mutation are special Types, whose fields represent the individual 166 | // queries and mutations. None of them ought to not be considered required, even if 167 | // their return value is a NON_NULL one. 168 | required: isQueriesOrMutations ? [] : getRequiredFields(curr.fields), 169 | } 170 | } else if (isIntrospectionInputObjectType(curr)) { 171 | acc[curr.name] = { 172 | type: 'object', 173 | properties: reduce( 174 | curr.inputFields as IntrospectionFieldReducerItem[], 175 | introspectionFieldReducerGenerator(options), 176 | {} 177 | ), 178 | required: getRequiredFields(curr.inputFields), 179 | } 180 | } else if (isIntrospectionInterfaceType(curr)) { 181 | acc[curr.name] = { 182 | type: 'object', 183 | properties: reduce( 184 | curr.fields as IntrospectionFieldReducerItem[], 185 | introspectionFieldReducerGenerator(options), 186 | {} 187 | ), 188 | // ignore required for Mutations/Queries 189 | required: type === 'definitions' ? getRequiredFields(curr.fields) : [], 190 | } 191 | } else if (isIntrospectionUnionType(curr)) { 192 | acc[curr.name] = { 193 | oneOf: curr.possibleTypes.map((type) => graphqlToJSONType(type, options)), 194 | } 195 | } else if (isIntrospectionEnumType(curr)) { 196 | acc[curr.name] = { 197 | type: 'string', 198 | anyOf: curr.enumValues.map((item) => { 199 | return { 200 | enum: [item.name], 201 | title: item.description || item.name, 202 | description: item.description || undefined, 203 | } 204 | }), 205 | } 206 | } else if (isIntrospectionDefaultScalarType(curr)) { 207 | acc[curr.name] = { 208 | type: scalarToJsonType(curr.name as GraphQLTypeNames, options), 209 | title: curr.name, 210 | } 211 | } else if (isIntrospectionScalarType(curr)) { 212 | acc[(curr as IntrospectionScalarType).name] = { 213 | type: 'object', 214 | title: (curr as IntrospectionScalarType).name, 215 | } 216 | } 217 | 218 | acc[curr.name].description = curr.description || undefined 219 | return acc 220 | } 221 | -------------------------------------------------------------------------------- /lib/typeGuards.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionEnumType, 3 | IntrospectionField, 4 | IntrospectionInputObjectType, 5 | IntrospectionInterfaceType, 6 | IntrospectionInputTypeRef, 7 | IntrospectionInputValue, 8 | IntrospectionListTypeRef, 9 | IntrospectionNamedTypeRef, 10 | IntrospectionNonNullTypeRef, 11 | IntrospectionObjectType, 12 | IntrospectionOutputTypeRef, 13 | IntrospectionSchema, 14 | IntrospectionType, 15 | IntrospectionTypeRef, 16 | IntrospectionUnionType, 17 | IntrospectionScalarType, 18 | TypeKind, 19 | } from 'graphql' 20 | import { filter, has, startsWith, includes } from 'lodash' 21 | 22 | export { ID_TYPE_MAPPING_OPTION_DEFAULT } from './typesMapping' 23 | 24 | export const SUPPORTED_SCALARS = Object.freeze([ 25 | 'Boolean', 26 | 'String', 27 | 'Int', 28 | 'Float', 29 | 'ID', 30 | ]) 31 | 32 | export const SUPPORTED_KINDS = Object.freeze([ 33 | TypeKind.SCALAR, 34 | TypeKind.OBJECT, 35 | TypeKind.INPUT_OBJECT, 36 | TypeKind.INTERFACE, 37 | TypeKind.ENUM, 38 | TypeKind.UNION, 39 | ]) 40 | 41 | /////////////////// 42 | /// Type guards /// 43 | /////////////////// 44 | 45 | export const isIntrospectionField = ( 46 | type: IntrospectionField | IntrospectionInputValue 47 | ): type is IntrospectionField => has(type, 'args') 48 | 49 | export const isIntrospectionInputValue = ( 50 | type: IntrospectionField | IntrospectionInputValue 51 | ): type is IntrospectionInputValue => has(type, 'defaultValue') 52 | 53 | // @ts-ignore 54 | export const isIntrospectionListTypeRef = ( 55 | type: 56 | | IntrospectionTypeRef 57 | | IntrospectionInputTypeRef 58 | | IntrospectionOutputTypeRef 59 | ): type is IntrospectionListTypeRef => type.kind === TypeKind.LIST 60 | 61 | export const isNonNullIntrospectionType = ( 62 | type: IntrospectionTypeRef 63 | ): type is IntrospectionNonNullTypeRef< 64 | IntrospectionNamedTypeRef 65 | > => type.kind === TypeKind.NON_NULL 66 | 67 | export const isIntrospectionScalarType = ( 68 | type: IntrospectionSchema['types'][0] 69 | ): type is IntrospectionScalarType => type.kind === TypeKind.SCALAR 70 | 71 | export const isIntrospectionObjectType = ( 72 | type: IntrospectionSchema['types'][0] 73 | ): type is IntrospectionObjectType => type.kind === TypeKind.OBJECT 74 | 75 | export const isIntrospectionInputObjectType = ( 76 | type: IntrospectionSchema['types'][0] 77 | ): type is IntrospectionInputObjectType => type.kind === TypeKind.INPUT_OBJECT 78 | 79 | export const isIntrospectionInterfaceType = ( 80 | type: IntrospectionSchema['types'][0] 81 | ): type is IntrospectionInterfaceType => type.kind === TypeKind.INTERFACE 82 | 83 | export const isIntrospectionEnumType = ( 84 | type: IntrospectionSchema['types'][0] 85 | ): type is IntrospectionEnumType => type.kind === TypeKind.ENUM 86 | 87 | export const isIntrospectionUnionType = ( 88 | type: IntrospectionSchema['types'][0] 89 | ): type is IntrospectionUnionType => type.kind === TypeKind.UNION 90 | 91 | export const isIntrospectionDefaultScalarType = ( 92 | type: IntrospectionSchema['types'][0] 93 | ): type is IntrospectionScalarType => 94 | type.kind === TypeKind.SCALAR && includes(SUPPORTED_SCALARS, type.name) 95 | 96 | // Ignore all GraphQL native Scalars, directives, etc... 97 | export interface FilterDefinitionsTypesOptions { 98 | ignoreInternals?: boolean 99 | } 100 | export const filterDefinitionsTypes = ( 101 | types: IntrospectionType[], 102 | opts?: FilterDefinitionsTypesOptions 103 | ): IntrospectionType[] => { 104 | const ignoreInternals = opts && opts.ignoreInternals 105 | return filter( 106 | types, 107 | (type) => 108 | ((isIntrospectionScalarType(type) && !!type.name) || 109 | (isIntrospectionObjectType(type) && !!type.fields) || 110 | (isIntrospectionInputObjectType(type) && !!type.inputFields) || 111 | (isIntrospectionInterfaceType(type) && !!type.fields) || 112 | (isIntrospectionEnumType(type) && !!type.enumValues) || 113 | (isIntrospectionUnionType(type) && !!type.possibleTypes)) && 114 | (!ignoreInternals || (ignoreInternals && !startsWith(type.name, '__'))) 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | export type GraphQLTypeNames = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' 2 | export type IDTypeMapping = 'string' | 'number' | 'both' 3 | -------------------------------------------------------------------------------- /lib/typesMapping.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IntrospectionInputType, 3 | IntrospectionInputTypeRef, 4 | IntrospectionNamedTypeRef, 5 | IntrospectionOutputType, 6 | IntrospectionOutputTypeRef, 7 | IntrospectionTypeRef, 8 | } from 'graphql' 9 | import { JSONSchema6, JSONSchema6TypeName } from 'json-schema' 10 | import { includes } from 'lodash' 11 | import { 12 | SUPPORTED_KINDS, 13 | isIntrospectionListTypeRef, 14 | isNonNullIntrospectionType, 15 | } from './typeGuards' 16 | 17 | import { GraphQLTypeNames, IDTypeMapping as IDTypeMappingType } from './types' 18 | 19 | export const ID_TYPE_MAPPING_OPTION_DEFAULT = 'string' as IDTypeMappingType 20 | 21 | const ID_TYPES: { 22 | [k in IDTypeMappingType]: JSONSchema6TypeName | JSONSchema6TypeName[] 23 | } = { 24 | string: 'string', 25 | number: 'number', 26 | both: ['string', 'number'], 27 | } 28 | 29 | const SCALAR_TO_JSON: { 30 | [k in GraphQLTypeNames]: JSONSchema6TypeName | JSONSchema6TypeName[] 31 | } = { 32 | Boolean: 'boolean', 33 | String: 'string', 34 | Int: 'number', 35 | Float: 'number', 36 | ID: ID_TYPES[ID_TYPE_MAPPING_OPTION_DEFAULT], 37 | } 38 | 39 | export const scalarToJsonType = ( 40 | scalarName: GraphQLTypeNames, 41 | options: GraphqlToJSONTypeOptions = {} 42 | ): JSONSchema6TypeName | JSONSchema6TypeName[] => 43 | Object.assign({}, SCALAR_TO_JSON, { 44 | ID: ID_TYPES[options.idTypeMapping || ID_TYPE_MAPPING_OPTION_DEFAULT], 45 | })[scalarName] 46 | 47 | // Convert a GraphQL Type to a valid JSON Schema type 48 | export type GraphqlToJSONTypeArg = 49 | | IntrospectionTypeRef 50 | | IntrospectionInputTypeRef 51 | | IntrospectionOutputTypeRef 52 | 53 | export type GraphqlToJSONTypeOptions = { 54 | nullableArrayItems?: boolean 55 | isArray?: boolean 56 | isNonNull?: boolean 57 | idTypeMapping?: IDTypeMappingType 58 | } 59 | 60 | export const graphqlToJSONType = ( 61 | k: GraphqlToJSONTypeArg, 62 | options: GraphqlToJSONTypeOptions = {} 63 | ): JSONSchema6 => { 64 | if (isIntrospectionListTypeRef(k)) { 65 | return { 66 | type: 'array', 67 | items: graphqlToJSONType(k.ofType, { ...options, isArray: true }), 68 | } 69 | } else if (isNonNullIntrospectionType(k)) { 70 | return graphqlToJSONType(k.ofType, { ...options, isNonNull: true }) 71 | } else { 72 | const name = (k as IntrospectionNamedTypeRef< 73 | IntrospectionInputType | IntrospectionOutputType 74 | >).name 75 | 76 | const { isArray, isNonNull, nullableArrayItems } = options 77 | 78 | const jsonType = {} as JSONSchema6 79 | 80 | if (includes(SUPPORTED_KINDS, k.kind)) { 81 | jsonType.$ref = `#/definitions/${name}` 82 | } else { 83 | jsonType.type = scalarToJsonType(name as GraphQLTypeNames, options) 84 | } 85 | 86 | // Only if the option allows for it, represent an array with nullable items 87 | // using the "anyOf" 88 | if (nullableArrayItems && isArray && !isNonNull) { 89 | return { 90 | anyOf: [jsonType, { type: 'null' }], 91 | } 92 | } 93 | 94 | return jsonType 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "delay": 2000, 3 | "verbose": true, 4 | "ext": "ts,js,json", 5 | "ignore": [ 6 | ".git", 7 | "dist" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-2-json-schema", 3 | "version": "0.10.0", 4 | "main": "dist/index.js", 5 | "repository": "git@github.com:wittydeveloper/graphql-to-json-schema.git", 6 | "author": "Charly POLY ", 7 | "license": "MIT", 8 | "engines": { 9 | "node": ">=8" 10 | }, 11 | "dependencies": { 12 | "json5": "^2.2.0", 13 | "lodash": "^4.17.20" 14 | }, 15 | "scripts": { 16 | "test": "jest", 17 | "test:watch": "nodemon -x 'npm run test'", 18 | "prettier:check": "prettier -c \"**/*.ts\"", 19 | "prettier:format": "prettier -w \"**/*.ts\"", 20 | "generateReadmeExample": "npx ts-node doc-exampleGenerator.ts", 21 | "prepare": "npx tsc" 22 | }, 23 | "devDependencies": { 24 | "@types/ajv": "1.0.0", 25 | "@types/jest": "26.0.20", 26 | "@types/json-schema": "7.0.6", 27 | "@types/lodash": "4.14.168", 28 | "@types/node": "14.18.63", 29 | "ajv": "7.2.4", 30 | "graphql": "15.9.0", 31 | "jest": "26.6.3", 32 | "nodemon": "2.0.22", 33 | "prettier": "2.2.1", 34 | "commit-and-tag-version": "9.6.0", 35 | "ts-jest": "26.5.6", 36 | "tslint": "5.20.1", 37 | "typescript": "4.1.3" 38 | }, 39 | "jest": { 40 | "transform": { 41 | "^.+\\.tsx?$": "ts-jest" 42 | }, 43 | "testEnvironment": "node", 44 | "testRegex": "(/__tests__/([^\\.d]*)|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 45 | "moduleFileExtensions": [ 46 | "ts", 47 | "tsx", 48 | "js", 49 | "jsx", 50 | "json", 51 | "node" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>the-guild-org/shared-config:renovate"] 3 | } 4 | -------------------------------------------------------------------------------- /test-utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | buildSchema, 3 | GraphQLSchema, 4 | graphqlSync, 5 | IntrospectionQuery, 6 | getIntrospectionQuery, 7 | } from 'graphql' 8 | import { JSONSchema6 } from 'json-schema' 9 | 10 | import { isEqual, cloneDeepWith } from 'lodash' 11 | 12 | type GetTodoSchemaIntrospectionResult = { 13 | schema: GraphQLSchema 14 | introspection: IntrospectionQuery 15 | } 16 | export const getTodoSchemaIntrospection = (): GetTodoSchemaIntrospectionResult => { 17 | const schema = buildSchema(` 18 | "A ToDo Object" 19 | type Todo implements Node { 20 | "A unique identifier" 21 | id: String! 22 | name: String! 23 | completed: Boolean 24 | color: Color 25 | 26 | "A required list containing colors that cannot contain nulls" 27 | requiredColors: [Color!]! 28 | 29 | "A non-required list containing colors that cannot contain nulls" 30 | optionalColors: [Color!] 31 | 32 | fieldWithOptionalArgument( 33 | optionalFilter: [String!] 34 | ): [String!] 35 | 36 | fieldWithRequiredArgument( 37 | requiredFilter: [String!]! 38 | ): [String!] 39 | 40 | nullableFieldThatReturnsListOfNonNullStrings( 41 | nonRequiredArgumentOfNullableStrings: [String] 42 | nonRequiredArgumentOfNonNullableStrings: [String!] 43 | requiredArgumentOfNullableStrings: [String]! 44 | requiredArgumentOfNonNullableStrings: [String!]! 45 | ): [String!] 46 | 47 | nullableFieldThatReturnsListOfNullableStrings: [String] 48 | } 49 | 50 | "A simpler ToDo Object" 51 | type SimpleTodo { 52 | id: ID! 53 | name: String! 54 | } 55 | 56 | "A Union of Todo and SimpleTodo" 57 | union TodoUnion = Todo | SimpleTodo 58 | 59 | enum Color { 60 | "Red color" 61 | RED 62 | "Green color" 63 | GREEN 64 | } 65 | 66 | """ 67 | A type that describes ToDoInputType. Its description might not 68 | fit within the bounds of 80 width and so you want MULTILINE 69 | """ 70 | input TodoInputType { 71 | name: String! 72 | completed: Boolean 73 | color: Color=RED 74 | contactInfo: ContactInfoInputType = { 75 | email: "spam@example.dev" 76 | } 77 | } 78 | 79 | """ 80 | Description of ContactInfoInputType. 81 | """ 82 | input ContactInfoInputType { 83 | email: String 84 | } 85 | 86 | "Anything with an ID can be a node" 87 | interface Node { 88 | "A unique identifier" 89 | id: String! 90 | } 91 | 92 | type Query { 93 | todo( 94 | "todo identifier" 95 | id: String! 96 | isCompleted: Boolean=false 97 | requiredNonNullStrings: [String!]! 98 | optionalNonNullStrings: [String!] 99 | 100 | requiredNullableStrings: [String]! 101 | optionalNullableStringsWithDefault: [String]=["foo"] 102 | 103 | color: Color 104 | requiredColor: Color! 105 | requiredColorWithDefault: Color! = RED 106 | 107 | colors: [Color] 108 | requiredColors: [Color]! 109 | requiredColorsNonNullable: [Color!]! 110 | requiredColorsWithDefault: [Color]! = [GREEN, RED] 111 | requiredColorsNonNullableWithDefault: [Color!]! = [GREEN, RED] 112 | ): Todo! 113 | todos: [Todo!]! 114 | todoUnions: [TodoUnion] 115 | node( 116 | "Node identifier" 117 | id: String! 118 | ): Node 119 | } 120 | 121 | type Mutation { 122 | update_todo(id: String!, todo: TodoInputType!): Todo 123 | create_todo(todo: TodoInputType!): Todo 124 | create_todo_union(id: String!): TodoUnion 125 | } 126 | `) 127 | 128 | const result = graphqlSync(schema, getIntrospectionQuery()) 129 | 130 | return { 131 | introspection: result.data as IntrospectionQuery, 132 | schema, 133 | } 134 | } 135 | 136 | export const todoSchemaAsJsonSchema: JSONSchema6 = generateBaseSchema() 137 | 138 | export const todoSchemaAsJsonSchemaWithoutNullableArrayItems: JSONSchema6 = cloneDeepWith( 139 | generateBaseSchema(), 140 | (value, key, object, stack) => { 141 | // Convert the new way back to the old way 142 | if ( 143 | key === 'items' && 144 | isEqual(Object.keys(value), ['anyOf']) && 145 | value.anyOf.length === 2 && 146 | value.anyOf.find((e: any) => isEqual(e, { type: 'null' })) 147 | ) { 148 | return value.anyOf.find((e: any) => !isEqual(e, { type: 'null' })) 149 | } 150 | } 151 | ) 152 | 153 | export const todoSchemaAsJsonSchemaWithIdTypeNumber: JSONSchema6 = cloneDeepWith( 154 | generateBaseSchema(), 155 | (value, key, object, stack) => { 156 | if (key === 'type' && object?.title === 'ID') { 157 | return 'number' 158 | } 159 | } 160 | ) 161 | 162 | export const todoSchemaAsJsonSchemaWithIdTypeStringOrNumber: JSONSchema6 = cloneDeepWith( 163 | generateBaseSchema(), 164 | (value, key, object, stack) => { 165 | if (key === 'type' && object?.title === 'ID') { 166 | return ['string', 'number'] 167 | } 168 | } 169 | ) 170 | 171 | function generateBaseSchema() { 172 | return { 173 | $schema: 'http://json-schema.org/draft-06/schema#', 174 | properties: { 175 | Query: { 176 | type: 'object', 177 | properties: { 178 | todo: { 179 | type: 'object', 180 | properties: { 181 | arguments: { 182 | type: 'object', 183 | properties: { 184 | id: { 185 | $ref: '#/definitions/String', 186 | description: 'todo identifier', 187 | }, 188 | isCompleted: { 189 | $ref: '#/definitions/Boolean', 190 | default: false, 191 | }, 192 | requiredNonNullStrings: { 193 | type: 'array', 194 | items: { $ref: '#/definitions/String' }, 195 | }, 196 | optionalNonNullStrings: { 197 | type: 'array', 198 | items: { 199 | $ref: '#/definitions/String', 200 | }, 201 | }, 202 | requiredNullableStrings: { 203 | type: 'array', 204 | items: { 205 | anyOf: [ 206 | { $ref: '#/definitions/String' }, 207 | { type: 'null' }, 208 | ], 209 | }, 210 | }, 211 | optionalNullableStringsWithDefault: { 212 | type: 'array', 213 | items: { 214 | anyOf: [ 215 | { $ref: '#/definitions/String' }, 216 | { type: 'null' }, 217 | ], 218 | }, 219 | default: ['foo'], 220 | }, 221 | 222 | color: { $ref: '#/definitions/Color' }, 223 | requiredColor: { $ref: '#/definitions/Color' }, 224 | requiredColorWithDefault: { 225 | $ref: '#/definitions/Color', 226 | default: 'RED', 227 | }, 228 | 229 | colors: { 230 | type: 'array', 231 | items: { 232 | anyOf: [ 233 | { $ref: '#/definitions/Color' }, 234 | { type: 'null' }, 235 | ], 236 | }, 237 | }, 238 | requiredColors: { 239 | type: 'array', 240 | items: { 241 | anyOf: [ 242 | { $ref: '#/definitions/Color' }, 243 | { type: 'null' }, 244 | ], 245 | }, 246 | }, 247 | requiredColorsNonNullable: { 248 | type: 'array', 249 | items: { $ref: '#/definitions/Color' }, 250 | }, 251 | requiredColorsWithDefault: { 252 | type: 'array', 253 | items: { 254 | anyOf: [ 255 | { $ref: '#/definitions/Color' }, 256 | { type: 'null' }, 257 | ], 258 | }, 259 | default: ['GREEN', 'RED'], 260 | }, 261 | requiredColorsNonNullableWithDefault: { 262 | type: 'array', 263 | items: { $ref: '#/definitions/Color' }, 264 | default: ['GREEN', 'RED'], 265 | }, 266 | }, 267 | required: [ 268 | 'id', 269 | 'requiredNonNullStrings', 270 | 'requiredNullableStrings', 271 | 'requiredColor', 272 | 'requiredColorWithDefault', 273 | 'requiredColors', 274 | 'requiredColorsNonNullable', 275 | 'requiredColorsWithDefault', 276 | 'requiredColorsNonNullableWithDefault', 277 | ], 278 | }, 279 | return: { 280 | $ref: '#/definitions/Todo', 281 | }, 282 | }, 283 | required: [], 284 | }, 285 | todos: { 286 | type: 'object', 287 | properties: { 288 | arguments: { 289 | type: 'object', 290 | properties: {}, 291 | required: [], 292 | }, 293 | return: { 294 | type: 'array', 295 | items: { $ref: '#/definitions/Todo' }, 296 | }, 297 | }, 298 | required: [], 299 | }, 300 | todoUnions: { 301 | type: 'object', 302 | properties: { 303 | arguments: { 304 | type: 'object', 305 | properties: {}, 306 | required: [], 307 | }, 308 | return: { 309 | type: 'array', 310 | items: { 311 | anyOf: [ 312 | { $ref: '#/definitions/TodoUnion' }, 313 | { type: 'null' }, 314 | ], 315 | }, 316 | }, 317 | }, 318 | required: [], 319 | }, 320 | node: { 321 | type: 'object', 322 | properties: { 323 | arguments: { 324 | type: 'object', 325 | properties: { 326 | id: { 327 | description: 'Node identifier', 328 | $ref: '#/definitions/String', 329 | }, 330 | }, 331 | required: ['id'], 332 | }, 333 | return: { 334 | $ref: '#/definitions/Node', 335 | }, 336 | }, 337 | required: [], 338 | }, 339 | }, 340 | // Inappropriate for individual queries to be required, despite possibly having 341 | // NON_NULL return types 342 | required: [], 343 | }, 344 | Mutation: { 345 | type: 'object', 346 | properties: { 347 | update_todo: { 348 | type: 'object', 349 | properties: { 350 | arguments: { 351 | type: 'object', 352 | properties: { 353 | id: { $ref: '#/definitions/String' }, 354 | todo: { $ref: '#/definitions/TodoInputType' }, 355 | }, 356 | required: ['id', 'todo'], 357 | }, 358 | return: { 359 | $ref: '#/definitions/Todo', 360 | }, 361 | }, 362 | required: [], 363 | }, 364 | create_todo: { 365 | type: 'object', 366 | properties: { 367 | arguments: { 368 | type: 'object', 369 | properties: { 370 | todo: { $ref: '#/definitions/TodoInputType' }, 371 | }, 372 | required: ['todo'], 373 | }, 374 | return: { 375 | $ref: '#/definitions/Todo', 376 | }, 377 | }, 378 | required: [], 379 | }, 380 | create_todo_union: { 381 | type: 'object', 382 | properties: { 383 | arguments: { 384 | type: 'object', 385 | properties: { 386 | id: { $ref: '#/definitions/String' }, 387 | }, 388 | required: ['id'], 389 | }, 390 | return: { 391 | $ref: '#/definitions/TodoUnion', 392 | }, 393 | }, 394 | required: [], 395 | }, 396 | }, 397 | // Inappropriate for individual mutations to be required, despite possibly having 398 | // NON_NULL return types 399 | required: [], 400 | }, 401 | }, 402 | definitions: { 403 | ID: { 404 | type: 'string', 405 | title: 'ID', 406 | description: 407 | 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', 408 | }, 409 | Boolean: { 410 | type: 'boolean', 411 | title: 'Boolean', 412 | description: 'The `Boolean` scalar type represents `true` or `false`.', 413 | }, 414 | String: { 415 | type: 'string', 416 | title: 'String', 417 | description: 418 | 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', 419 | }, 420 | Todo: { 421 | type: 'object', 422 | description: 'A ToDo Object', 423 | properties: { 424 | id: { 425 | description: 'A unique identifier', 426 | type: 'object', 427 | properties: { 428 | return: { $ref: '#/definitions/String' }, 429 | arguments: { type: 'object', properties: {}, required: [] }, 430 | }, 431 | required: [], 432 | }, 433 | name: { 434 | type: 'object', 435 | properties: { 436 | return: { $ref: '#/definitions/String' }, 437 | arguments: { type: 'object', properties: {}, required: [] }, 438 | }, 439 | required: [], 440 | }, 441 | completed: { 442 | type: 'object', 443 | properties: { 444 | return: { $ref: '#/definitions/Boolean' }, 445 | arguments: { type: 'object', properties: {}, required: [] }, 446 | }, 447 | required: [], 448 | }, 449 | color: { 450 | type: 'object', 451 | properties: { 452 | return: { $ref: '#/definitions/Color' }, 453 | arguments: { type: 'object', properties: {}, required: [] }, 454 | }, 455 | required: [], 456 | }, 457 | requiredColors: { 458 | description: 459 | 'A required list containing colors that cannot contain nulls', 460 | type: 'object', 461 | properties: { 462 | return: { 463 | type: 'array', 464 | items: { $ref: '#/definitions/Color' }, 465 | }, 466 | arguments: { type: 'object', properties: {}, required: [] }, 467 | }, 468 | required: [], 469 | }, 470 | optionalColors: { 471 | description: 472 | 'A non-required list containing colors that cannot contain nulls', 473 | type: 'object', 474 | properties: { 475 | return: { 476 | type: 'array', 477 | items: { $ref: '#/definitions/Color' }, 478 | }, 479 | arguments: { type: 'object', properties: {}, required: [] }, 480 | }, 481 | required: [], 482 | }, 483 | fieldWithOptionalArgument: { 484 | type: 'object', 485 | properties: { 486 | return: { 487 | type: 'array', 488 | items: { $ref: '#/definitions/String' }, 489 | }, 490 | arguments: { 491 | type: 'object', 492 | properties: { 493 | optionalFilter: { 494 | type: 'array', 495 | items: { $ref: '#/definitions/String' }, 496 | }, 497 | }, 498 | required: [], 499 | }, 500 | }, 501 | required: [], 502 | }, 503 | fieldWithRequiredArgument: { 504 | type: 'object', 505 | properties: { 506 | return: { 507 | type: 'array', 508 | items: { $ref: '#/definitions/String' }, 509 | }, 510 | arguments: { 511 | type: 'object', 512 | properties: { 513 | requiredFilter: { 514 | type: 'array', 515 | items: { $ref: '#/definitions/String' }, 516 | }, 517 | }, 518 | required: ['requiredFilter'], 519 | }, 520 | }, 521 | required: [], 522 | }, 523 | nullableFieldThatReturnsListOfNonNullStrings: { 524 | type: 'object', 525 | properties: { 526 | return: { 527 | type: 'array', 528 | items: { $ref: '#/definitions/String' }, 529 | }, 530 | arguments: { 531 | type: 'object', 532 | properties: { 533 | nonRequiredArgumentOfNullableStrings: { 534 | type: 'array', 535 | items: { 536 | anyOf: [ 537 | { $ref: '#/definitions/String' }, 538 | { type: 'null' }, 539 | ], 540 | }, 541 | }, 542 | nonRequiredArgumentOfNonNullableStrings: { 543 | type: 'array', 544 | items: { $ref: '#/definitions/String' }, 545 | }, 546 | requiredArgumentOfNullableStrings: { 547 | type: 'array', 548 | items: { 549 | anyOf: [ 550 | { $ref: '#/definitions/String' }, 551 | { type: 'null' }, 552 | ], 553 | }, 554 | }, 555 | requiredArgumentOfNonNullableStrings: { 556 | type: 'array', 557 | items: { $ref: '#/definitions/String' }, 558 | }, 559 | }, 560 | required: [ 561 | 'requiredArgumentOfNullableStrings', 562 | 'requiredArgumentOfNonNullableStrings', 563 | ], 564 | }, 565 | }, 566 | required: [], 567 | }, 568 | nullableFieldThatReturnsListOfNullableStrings: { 569 | type: 'object', 570 | properties: { 571 | return: { 572 | type: 'array', 573 | items: { 574 | anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], 575 | }, 576 | }, 577 | arguments: { type: 'object', properties: {}, required: [] }, 578 | }, 579 | required: [], 580 | }, 581 | }, 582 | required: ['id', 'name', 'requiredColors'], 583 | }, 584 | SimpleTodo: { 585 | type: 'object', 586 | description: 'A simpler ToDo Object', 587 | properties: { 588 | id: { 589 | type: 'object', 590 | properties: { 591 | return: { $ref: '#/definitions/ID' }, 592 | arguments: { type: 'object', properties: {}, required: [] }, 593 | }, 594 | required: [], 595 | }, 596 | name: { 597 | type: 'object', 598 | properties: { 599 | return: { $ref: '#/definitions/String' }, 600 | arguments: { type: 'object', properties: {}, required: [] }, 601 | }, 602 | required: [], 603 | }, 604 | }, 605 | required: ['id', 'name'], 606 | }, 607 | Color: { 608 | // Yes, ENUM types should be the JSON built-in "string" type 609 | type: 'string', 610 | anyOf: [ 611 | { 612 | enum: ['RED'], 613 | title: 'Red color', 614 | description: 'Red color', 615 | }, 616 | { 617 | enum: ['GREEN'], 618 | title: 'Green color', 619 | description: 'Green color', 620 | }, 621 | ], 622 | }, 623 | TodoInputType: { 624 | type: 'object', 625 | description: 626 | 'A type that describes ToDoInputType. Its description might not\nfit within the bounds of 80 width and so you want MULTILINE', 627 | properties: { 628 | name: { $ref: '#/definitions/String' }, 629 | completed: { $ref: '#/definitions/Boolean' }, 630 | color: { default: 'RED', $ref: '#/definitions/Color' }, 631 | contactInfo: { 632 | $ref: '#/definitions/ContactInfoInputType', 633 | default: { email: 'spam@example.dev' }, 634 | }, 635 | }, 636 | required: ['name'], 637 | }, 638 | ContactInfoInputType: { 639 | type: 'object', 640 | description: 'Description of ContactInfoInputType.', 641 | properties: { 642 | email: { $ref: '#/definitions/String' }, 643 | }, 644 | required: [], 645 | }, 646 | TodoUnion: { 647 | description: 'A Union of Todo and SimpleTodo', 648 | oneOf: [ 649 | { $ref: '#/definitions/Todo' }, 650 | { $ref: '#/definitions/SimpleTodo' }, 651 | ], 652 | }, 653 | Node: { 654 | type: 'object', 655 | description: 'Anything with an ID can be a node', 656 | properties: { 657 | id: { 658 | type: 'object', 659 | description: 'A unique identifier', 660 | properties: { 661 | return: { $ref: '#/definitions/String' }, 662 | arguments: { type: 'object', properties: {}, required: [] }, 663 | }, 664 | required: [], 665 | }, 666 | }, 667 | required: ['id'], 668 | }, 669 | }, 670 | } as JSONSchema6 671 | } 672 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 8", 4 | 5 | "compilerOptions": { 6 | // https://www.typescriptlang.org/tsconfig#target 7 | // ES6/ES2015 features are supported by Node v8 8 | "target": "ES2015", 9 | "lib": ["ES2015"], 10 | 11 | "module": "commonjs", 12 | "strict": true, 13 | "declaration": true, 14 | "removeComments": true, 15 | "esModuleInterop": true, 16 | "noImplicitAny": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | 20 | "outDir": "./dist", 21 | "rootDir": ".", 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | // Don't try to compile the already compiled stuff 26 | "./dist", 27 | // Don't do anything with test files as they will be compiled by 28 | // ts-jest 29 | "**/*spec.ts", 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ordered-imports": true, 4 | "align": [ 5 | true, 6 | "parameters", 7 | "arguments", 8 | "statements" 9 | ], 10 | "ban": false, 11 | "class-name": true, 12 | "comment-format": [ 13 | true, 14 | "check-space" 15 | ], 16 | "curly": true, 17 | "eofline": true, 18 | "forin": true, 19 | "indent": [ 20 | true, 21 | "spaces" 22 | ], 23 | "interface-name": [ 24 | true, 25 | "never-prefix" 26 | ], 27 | "jsdoc-format": true, 28 | "jsx-no-lambda": false, 29 | "jsx-no-multiline-js": false, 30 | "jsx-wrap-multiline": false, 31 | "jsx-alignment": false, 32 | "label-position": true, 33 | "max-line-length": [ 34 | true, 35 | 120 36 | ], 37 | "member-ordering": [ 38 | true, 39 | "public-before-private", 40 | "static-before-instance", 41 | "variables-before-functions" 42 | ], 43 | "arrow-parens": [ 44 | true, 45 | "ban-single-arg-parens" 46 | ], 47 | "no-var-keyword": true, 48 | "no-any": true, 49 | "no-arg": true, 50 | "no-bitwise": true, 51 | "no-console": [ 52 | true, 53 | "log", 54 | "error", 55 | "debug", 56 | "info", 57 | "time", 58 | "timeEnd", 59 | "trace" 60 | ], 61 | "no-consecutive-blank-lines": true, 62 | "no-construct": true, 63 | "no-debugger": true, 64 | "no-duplicate-variable": true, 65 | "no-empty": true, 66 | "no-eval": true, 67 | "no-shadowed-variable": true, 68 | "no-string-literal": true, 69 | "no-switch-case-fall-through": true, 70 | "no-trailing-whitespace": false, 71 | "no-unused-expression": true, 72 | "no-unused-variable": true, 73 | "no-use-before-declare": true, 74 | "one-line": [ 75 | true, 76 | "check-catch", 77 | "check-else", 78 | "check-open-brace", 79 | "check-whitespace" 80 | ], 81 | "quotemark": [ 82 | true, 83 | "single", 84 | "jsx-double" 85 | ], 86 | "radix": true, 87 | "semicolon": [ 88 | true, 89 | "always" 90 | ], 91 | "switch-default": true, 92 | "trailing-comma": false, 93 | "triple-equals": true, 94 | "no-null-keyword": false, 95 | "typedef": [ 96 | true, 97 | "parameter", 98 | "property-declaration" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "variable-name": [ 111 | true, 112 | "ban-keywords", 113 | "check-format", 114 | "allow-leading-underscore", 115 | "allow-pascal-case" 116 | ], 117 | "whitespace": [ 118 | true, 119 | "check-branch", 120 | "check-decl", 121 | "check-module", 122 | "check-operator", 123 | "check-separator", 124 | "check-type", 125 | "check-typecast" 126 | ], 127 | "linebreak-style": [ 128 | true, 129 | "LF" 130 | ] 131 | } 132 | } --------------------------------------------------------------------------------