1 | 'use strict'
|
2 | function 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 |
|
12 | function 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 |
|
23 | function 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 |
|
99 | function 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 |
|
344 | module.exports = renderSchema
|