UNPKG

8.49 kBJavaScriptView Raw
1'use strict'
2function sortBy(arr, property) {
3 arr.sort((a, b) => {
4 const aValue = a[property]
5 const bValue = b[property]
6 if (aValue > bValue) return 1
7 if (bValue > aValue) return -1
8 return 0
9 })
10}
11
12function renderType(type, options) {
13 if (type.kind === 'NON_NULL') {
14 return renderType(type.ofType, options) + '!'
15 }
16 if (type.kind === 'LIST') {
17 return `[${renderType(type.ofType, options)}]`
18 }
19 const url = options.getTypeURL(type)
20 return url ? `<a href="${url}">${type.name}</a>` : type.name
21}
22
23function renderObject(type, options) {
24 options = options || {}
25 const skipTitle = options.skipTitle === true
26 const printer = options.printer || console.log
27 const headingLevel = options.headingLevel || 1
28 const getTypeURL = options.getTypeURL
29 const isInputObject = type.kind === 'INPUT_OBJECT'
30
31 if (!skipTitle) {
32 printer(`\n${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
33 }
34 if (type.description) {
35 printer(`${type.description}\n`)
36 }
37 printer('<table>')
38 printer('<thead>')
39 printer('<tr>')
40 if (isInputObject) {
41 printer('<th colspan="2" align="left">Field</th>')
42 } else {
43 printer('<th align="left">Field</th>')
44 printer('<th align="right">Argument</th>')
45 }
46 printer('<th align="left">Type</th>')
47 printer('<th align="left">Description</th>')
48 printer('</tr>')
49 printer('</thead>')
50 printer('<tbody>')
51
52 const fields = isInputObject ? type.inputFields : type.fields
53 fields.forEach(field => {
54 printer('<tr>')
55 printer(
56 `<td colspan="2" valign="top"><strong>${field.name}</strong>${
57 field.isDeprecated ? ' ⚠️' : ''
58 }</td>`
59 )
60 printer(`<td valign="top">${renderType(field.type, { getTypeURL })}</td>`)
61 if (field.description || field.isDeprecated) {
62 printer('<td>')
63 if (field.description) {
64 printer(`\n${field.description}\n`)
65 }
66 if (field.isDeprecated) {
67 printer('<p>⚠️ <strong>DEPRECATED</strong></p>')
68 if (field.deprecationReason) {
69 printer('<blockquote>')
70 printer(`\n${field.deprecationReason}\n`)
71 printer('</blockquote>')
72 }
73 }
74 printer('</td>')
75 } else {
76 printer('<td></td>')
77 }
78 printer('</tr>')
79 if (!isInputObject && field.args.length) {
80 field.args.forEach((arg, i) => {
81 printer('<tr>')
82 printer(`<td colspan="2" align="right" valign="top">${arg.name}</td>`)
83 printer(`<td valign="top">${renderType(arg.type, { getTypeURL })}</td>`)
84 if (arg.description) {
85 printer('<td>')
86 printer(`\n${arg.description}\n`)
87 printer('</td>')
88 } else {
89 printer(`<td></td>`)
90 }
91 printer('</tr>')
92 })
93 }
94 })
95 printer('</tbody>')
96 printer('</table>')
97}
98
99function renderSchema(schema, options) {
100 options = options || {}
101 const title = options.title || 'Schema Types'
102 const skipTitle = options.skipTitle || false
103 const prologue = options.prologue || ''
104 const epilogue = options.epilogue || ''
105 const printer = options.printer || console.log
106 const headingLevel = options.headingLevel || 1
107 const unknownTypeURL = options.unknownTypeURL
108
109 if (schema.__schema) {
110 schema = schema.__schema
111 }
112
113 const types = schema.types.filter(type => !type.name.startsWith('__'))
114 const typeMap = schema.types.reduce((typeMap, type) => {
115 return Object.assign(typeMap, { [type.name]: type })
116 }, {})
117 const getTypeURL = type => {
118 const url = `#${type.name.toLowerCase()}`
119 if (typeMap[type.name]) {
120 return url
121 } else if (typeof unknownTypeURL === 'function') {
122 return unknownTypeURL(type)
123 } else if (unknownTypeURL) {
124 return unknownTypeURL + url
125 }
126 }
127
128 const queryType = schema.queryType
129 const query =
130 queryType && types.find(type => type.name === schema.queryType.name)
131 const mutationType = schema.mutationType
132 const mutation =
133 mutationType && types.find(type => type.name === schema.mutationType.name)
134 const objects = types.filter(
135 type => type.kind === 'OBJECT' && type !== query && type !== mutation
136 )
137 const inputs = types.filter(type => type.kind === 'INPUT_OBJECT')
138 const enums = types.filter(type => type.kind === 'ENUM')
139 const scalars = types.filter(type => type.kind === 'SCALAR')
140 const interfaces = types.filter(type => type.kind === 'INTERFACE')
141
142 sortBy(objects, 'name')
143 sortBy(inputs, 'name')
144 sortBy(enums, 'name')
145 sortBy(scalars, 'name')
146 sortBy(interfaces, 'name')
147
148 if (!skipTitle) {
149 printer(`${'#'.repeat(headingLevel)} ${title}\n`)
150 }
151
152 if (prologue) {
153 printer(`${prologue}\n`)
154 }
155
156 printer('<details>')
157 printer(' <summary><strong>Table of Contents</strong></summary>\n')
158 if (query) {
159 printer(' * [Query](#query)')
160 }
161 if (mutation) {
162 printer(' * [Mutation](#mutation)')
163 }
164 if (objects.length) {
165 printer(' * [Objects](#objects)')
166 objects.forEach(type => {
167 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
168 })
169 }
170 if (inputs.length) {
171 printer(' * [Inputs](#inputs)')
172 inputs.forEach(type => {
173 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
174 })
175 }
176 if (enums.length) {
177 printer(' * [Enums](#enums)')
178 enums.forEach(type => {
179 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
180 })
181 }
182 if (scalars.length) {
183 printer(' * [Scalars](#scalars)')
184 scalars.forEach(type => {
185 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
186 })
187 }
188 if (interfaces.length) {
189 printer(' * [Interfaces](#interfaces)')
190 interfaces.forEach(type => {
191 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
192 })
193 }
194 printer('\n</details>')
195
196 if (query) {
197 printer(
198 `\n${'#'.repeat(headingLevel + 1)} Query${
199 query.name === 'Query' ? '' : ' (' + query.name + ')'
200 }`
201 )
202 renderObject(query, { skipTitle: true, headingLevel, printer, getTypeURL })
203 }
204
205 if (mutation) {
206 printer(
207 `\n${'#'.repeat(headingLevel + 1)} Mutation${
208 mutation.name === 'Mutation' ? '' : ' (' + mutation.name + ')'
209 }`
210 )
211 renderObject(mutation, {
212 skipTitle: true,
213 headingLevel,
214 printer,
215 getTypeURL
216 })
217 }
218
219 if (objects.length) {
220 printer(`\n${'#'.repeat(headingLevel + 1)} Objects`)
221 objects.forEach(type =>
222 renderObject(type, { headingLevel, printer, getTypeURL })
223 )
224 }
225
226 if (inputs.length) {
227 printer(`\n${'#'.repeat(headingLevel + 1)} Inputs`)
228 inputs.forEach(type =>
229 renderObject(type, { headingLevel, printer, getTypeURL })
230 )
231 }
232
233 if (enums.length) {
234 printer(`\n${'#'.repeat(headingLevel + 1)} Enums`)
235 enums.forEach(type => {
236 printer(`\n${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
237 if (type.description) {
238 printer(`${type.description}\n`)
239 }
240 printer('<table>')
241 printer('<thead>')
242 printer('<th align="left">Value</th>')
243 printer('<th align="left">Description</th>')
244 printer('</thead>')
245 printer('<tbody>')
246 type.enumValues.forEach(value => {
247 printer('<tr>')
248 printer(
249 `<td valign="top"><strong>${value.name}</strong>${
250 value.isDeprecated ? ' ⚠️' : ''
251 }</td>`
252 )
253 if (value.description || value.isDeprecated) {
254 printer('<td>')
255 if (value.description) {
256 printer(`\n${value.description}\n`)
257 }
258 if (value.isDeprecated) {
259 printer('<p>⚠️ <strong>DEPRECATED</strong></p>')
260 if (value.deprecationReason) {
261 printer('<blockquote>')
262 printer(`\n${value.deprecationReason}\n`)
263 printer('</blockquote>')
264 }
265 }
266 printer('</td>')
267 } else {
268 printer('<td></td>')
269 }
270 printer('</tr>')
271 })
272 printer('</tbody>')
273 printer('</table>')
274 })
275 }
276
277 if (scalars.length) {
278 printer(`\n${'#'.repeat(headingLevel + 1)} Scalars\n`)
279 scalars.forEach(type => {
280 printer(`${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
281 if (type.description) {
282 printer(`${type.description}\n`)
283 }
284 })
285 }
286
287 if (interfaces.length) {
288 printer(`\n${'#'.repeat(headingLevel + 1)} Interfaces\n`)
289 interfaces.forEach(type =>
290 renderObject(type, { headingLevel, printer, getTypeURL })
291 )
292 }
293
294 if (epilogue) {
295 printer(`\n${epilogue}`)
296 }
297}
298
299module.exports = renderSchema