├── .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 [](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 | }
--------------------------------------------------------------------------------