UNPKG

10 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 skipTableOfContents = options.skipTableOfContents || false
104 const prologue = options.prologue || ''
105 const epilogue = options.epilogue || ''
106 const printer = options.printer || console.log
107 const headingLevel = options.headingLevel || 1
108 const unknownTypeURL = options.unknownTypeURL
109
110 if (schema.__schema) {
111 schema = schema.__schema
112 }
113
114 const types = schema.types.filter((type) => !type.name.startsWith('__'))
115 const typeMap = schema.types.reduce((typeMap, type) => {
116 return Object.assign(typeMap, { [type.name]: type })
117 }, {})
118 const getTypeURL = (type) => {
119 const url = `#${type.name.toLowerCase()}`
120 if (typeMap[type.name]) {
121 return url
122 } else if (typeof unknownTypeURL === 'function') {
123 return unknownTypeURL(type)
124 } else if (unknownTypeURL) {
125 return unknownTypeURL + url
126 }
127 }
128
129 const queryType = schema.queryType
130 const query =
131 queryType && types.find((type) => type.name === schema.queryType.name)
132 const mutationType = schema.mutationType
133 const mutation =
134 mutationType && types.find((type) => type.name === schema.mutationType.name)
135 const objects = types.filter(
136 (type) => type.kind === 'OBJECT' && type !== query && type !== mutation
137 )
138 const inputs = types.filter((type) => type.kind === 'INPUT_OBJECT')
139 const enums = types.filter((type) => type.kind === 'ENUM')
140 const scalars = types.filter((type) => type.kind === 'SCALAR')
141 const interfaces = types.filter((type) => type.kind === 'INTERFACE')
142 const unions = types.filter((type) => type.kind === 'UNION')
143
144 sortBy(objects, 'name')
145 sortBy(inputs, 'name')
146 sortBy(enums, 'name')
147 sortBy(scalars, 'name')
148 sortBy(interfaces, 'name')
149 sortBy(unions, 'name')
150
151 if (!skipTitle) {
152 printer(`${'#'.repeat(headingLevel)} ${title}\n`)
153 }
154
155 if (prologue) {
156 printer(`${prologue}\n`)
157 }
158
159 if (!skipTableOfContents) {
160 printer('<details>')
161 printer(' <summary><strong>Table of Contents</strong></summary>\n')
162 if (query) {
163 printer(' * [Query](#query)')
164 }
165 if (mutation) {
166 printer(' * [Mutation](#mutation)')
167 }
168 if (objects.length) {
169 printer(' * [Objects](#objects)')
170 objects.forEach((type) => {
171 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
172 })
173 }
174 if (inputs.length) {
175 printer(' * [Inputs](#inputs)')
176 inputs.forEach((type) => {
177 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
178 })
179 }
180 if (enums.length) {
181 printer(' * [Enums](#enums)')
182 enums.forEach((type) => {
183 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
184 })
185 }
186 if (scalars.length) {
187 printer(' * [Scalars](#scalars)')
188 scalars.forEach((type) => {
189 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
190 })
191 }
192 if (interfaces.length) {
193 printer(' * [Interfaces](#interfaces)')
194 interfaces.forEach((type) => {
195 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
196 })
197 }
198 if (unions.length) {
199 printer(' * [Unions](#unions)')
200 unions.forEach((type) => {
201 printer(` * [${type.name}](#${type.name.toLowerCase()})`)
202 })
203 }
204 printer('\n</details>')
205 }
206
207 if (query) {
208 printer(
209 `\n${'#'.repeat(headingLevel + 1)} Query${
210 query.name === 'Query' ? '' : ' (' + query.name + ')'
211 }`
212 )
213 renderObject(query, { skipTitle: true, headingLevel, printer, getTypeURL })
214 }
215
216 if (mutation) {
217 printer(
218 `\n${'#'.repeat(headingLevel + 1)} Mutation${
219 mutation.name === 'Mutation' ? '' : ' (' + mutation.name + ')'
220 }`
221 )
222 renderObject(mutation, {
223 skipTitle: true,
224 headingLevel,
225 printer,
226 getTypeURL,
227 })
228 }
229
230 if (objects.length) {
231 printer(`\n${'#'.repeat(headingLevel + 1)} Objects`)
232 objects.forEach((type) =>
233 renderObject(type, { headingLevel, printer, getTypeURL })
234 )
235 }
236
237 if (inputs.length) {
238 printer(`\n${'#'.repeat(headingLevel + 1)} Inputs`)
239 inputs.forEach((type) =>
240 renderObject(type, { headingLevel, printer, getTypeURL })
241 )
242 }
243
244 if (enums.length) {
245 printer(`\n${'#'.repeat(headingLevel + 1)} Enums`)
246 enums.forEach((type) => {
247 printer(`\n${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
248 if (type.description) {
249 printer(`${type.description}\n`)
250 }
251 printer('<table>')
252 printer('<thead>')
253 printer('<th align="left">Value</th>')
254 printer('<th align="left">Description</th>')
255 printer('</thead>')
256 printer('<tbody>')
257 type.enumValues.forEach((value) => {
258 printer('<tr>')
259 printer(
260 `<td valign="top"><strong>${value.name}</strong>${
261 value.isDeprecated ? ' ⚠️' : ''
262 }</td>`
263 )
264 if (value.description || value.isDeprecated) {
265 printer('<td>')
266 if (value.description) {
267 printer(`\n${value.description}\n`)
268 }
269 if (value.isDeprecated) {
270 printer('<p>⚠️ <strong>DEPRECATED</strong></p>')
271 if (value.deprecationReason) {
272 printer('<blockquote>')
273 printer(`\n${value.deprecationReason}\n`)
274 printer('</blockquote>')
275 }
276 }
277 printer('</td>')
278 } else {
279 printer('<td></td>')
280 }
281 printer('</tr>')
282 })
283 printer('</tbody>')
284 printer('</table>')
285 })
286 }
287
288 if (scalars.length) {
289 printer(`\n${'#'.repeat(headingLevel + 1)} Scalars\n`)
290 scalars.forEach((type) => {
291 printer(`${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
292 if (type.description) {
293 printer(`${type.description}\n`)
294 }
295 })
296 }
297
298 if (interfaces.length) {
299 printer(`\n${'#'.repeat(headingLevel + 1)} Interfaces\n`)
300 interfaces.forEach((type) =>
301 renderObject(type, { headingLevel, printer, getTypeURL })
302 )
303 }
304
305 if (unions.length) {
306 printer(`\n${'#'.repeat(headingLevel + 1)} Unions`)
307 unions.forEach((type) => {
308 printer(`\n${'#'.repeat(headingLevel + 2)} ${type.name}\n`)
309 if (type.description) {
310 printer(`${type.description}\n`)
311 }
312 printer('<table>')
313 printer('<thead>')
314 printer('<th align="left">Type</th>')
315 printer('<th align="left">Description</th>')
316 printer('</thead>')
317 printer('<tbody>')
318 type.possibleTypes.forEach((objType) => {
319 const obj = objects.find((o) => objType.name === o.name)
320 const desc = objType.description || (obj && obj.description)
321 printer('<tr>')
322 printer(
323 `<td valign="top"><strong>${renderType(objType, {
324 getTypeURL,
325 })}</strong></td>`
326 )
327 if (desc) {
328 printer(`<td valign="top">${desc}</td>`)
329 } else {
330 printer('<td></td>')
331 }
332 printer('</tr>')
333 })
334 printer('</tbody>')
335 printer('</table>')
336 })
337 }
338
339 if (epilogue) {
340 printer(`\n${epilogue}`)
341 }
342}
343
344module.exports = renderSchema