Skip to content

Instantly share code, notes, and snippets.

@pbassham
Created November 9, 2020 16:50
Show Gist options
  • Save pbassham/0da8bc73929e2fabc1c40351b5bd9a04 to your computer and use it in GitHub Desktop.
Save pbassham/0da8bc73929e2fabc1c40351b5bd9a04 to your computer and use it in GitHub Desktop.
A script that reads DGraph's introspection schema and auto-generates a fragment file based on your schema.
// Reads DGraph's introspection schema and generates a fragment file
/* eslint-disable prefer-template */
const { default: fetch } = require('node-fetch')
const fs = require('fs')
//GraphQL Introspection Specification https://spec.graphql.org/June2018/
fetch('https://ENDPOINT_HERE.aws.cloud.dgraph.io/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `{
__schema {
types {
name
ofType {name}
fields {
name
type {kind name ofType{name kind} interfaces{name} possibleTypes {name} enumValues {name}}}
kind
}
}
}`,
}),
})
.then((res) => res.json())
.then((res) => {
// console.log(res.data)
const header = `import gql from 'graphql-tag'\n\n`
// FRAGS = Scalar fields only
let fragmentsObj1 = `export const FRAGS = {`
// FRAGS1 = Scalar fields + nested fields 1 level deep
let fragmentsObj2 = `\n\n\n\n\nexport const FRAGS1 = {`
// FRAGS1 = Scalar fields + nested fields 2 levels deep
let fragmentsObj3 = `\n\n\n\n\nexport const FRAGS2 = {`
// FRAGS1 = Scalar fields + access fields (custom for us, but might want to customize it)
let fragmentsObj4 = `\n\n\n\n\nexport const FRAGS_ACCESS = {`
const { types } = res.data.__schema // eslint-disable-line no-underscore-dangle
//Filter out DGRAPH types to generate fragments for basic queries only. This could be changed to generate fragments of different types.
const regex = /((^(Add|Update|Delete|__)[A-Z])|^Mutation|^Query|^Subscription)/m
types.forEach((type) => {
// Skip TYPES that match the regex pattern
if (regex.test(type.name)) {
// console.log(type.name+ ' SKIPPED TYPE via regex')
return
}
// console.log(type.name)
const { fields } = type
let count = 0
let nestedFields1 = ''
let nestedFields2 = ''
const framentVarsForOtherFragment = []
const framentVarsForOtherFragment2 = []
const typename = `${type.name.charAt(0).toLowerCase()}${type.name.slice(1)}`
const fragmentName = `\n${typename}Fields: gql\`\n fragment ${typename}Fields on ${type.name} {`
const fragmentName1 = `\n${typename}Fields1: gql\`\n fragment ${typename}Fields1 on ${type.name} {`
const fragmentName2 = `\n${typename}Fields2: gql\`\n fragment ${typename}Fields2 on ${type.name} {`
const fragmentNameAccess = `\n${typename}FieldsAccess: gql\`\n fragment ${typename}FieldsAccess on ${type.name} {`
let keyedFragment1 = ''
let keyedFragment2 = ''
let keyedFragment3 = ''
let keyedFragment4 = ''
let scalarFields = ''
let access = ''
const regex2 = /(^(add|update|delete|__)[A-Z])/m
fields.forEach((field) => {
// Skip FIELDS that match regex2 pattern
if (regex2.test(field.name)) {
// console.log(`${field.name} SKIPPED FIELD via regex`)
return
}
// let notused// = false
const fieldType = field.type.name ? field.type.name :
field.type.ofType ? field.type.ofType.name :
""
//TypeName -> typeName
const fieldTypeVarName = fieldType ? fieldType.charAt(0).toLowerCase() + fieldType.slice(1) : ""
const typeComment = '#' + fieldType + `${field.type.kind === 'NON_NULL' ? '!' :
field.type.kind === 'SCALAR' ? " Scalar" :
field.type.kind === 'LIST' ? ` ${field.type.ofType.kind}` :
` ${field.type.kind}`}`
//FIXME: fieldType conditional only returns null on Event.Occurances.name that i know of
const fragmentRef = fieldType ? `\n \${FRAGS.${fieldTypeVarName}Fields}` : ""
const fragmentRef2 = fieldType ? `\n \${FRAGS1.${fieldTypeVarName}Fields1}` : ""
const nestedField1 = fieldType ? `\n ${field.name} {\n ...${fieldTypeVarName}Fields\n }` : ""
const nestedField2 = fieldType ? `\n ${field.name} {\n ...${fieldTypeVarName}Fields1\n }` : ""
//SCALAR
if (field.type.kind === 'SCALAR' ||
field.type.kind === 'ENUM' ||
((field.type.kind === 'LIST' ||
field.type.kind === 'NON_NULL') &&
(field.type.ofType.kind === 'ENUM' ||
field.type.ofType.kind === 'String' ||
field.type.ofType.kind === 'Boolean' ||
field.type.ofType.kind === 'DateTime' ||
field.type.ofType.kind === 'Float' ||
field.type.ofType.kind === 'Int')) ||
fieldType === 'String' ||
fieldType === 'Boolean' ||
fieldType === 'DateTime' ||
fieldType === 'Int' ||
fieldType === 'Float' ||
fieldType === 'ID') {
// count1++
count++
// console.log(field.name)
// Temporary Exceptions bc of whatever reason (schema or data errors, etc.)
if (type.name === 'Contact' && field.name === 'name') {
// Comment out the 'name' field in contacts until custom directive scripts are available in DGraph
scalarFields += `\n #${field.name} ${typeComment}`
} else {
scalarFields += `\n ${field.name} ${typeComment}`
}
} else
//SUBFIELDS of LIST, NON_NULL (fields that arent scalar values on their own) and OBJECTS types
if (field.type.kind === 'LIST' ||
field.type.kind === 'OBJECT' ||
field.type.kind === 'NON_NULL') {
// count2++
count++
// notused = false
framentVarsForOtherFragment.push(fragmentRef)
framentVarsForOtherFragment2.push(fragmentRef2)
nestedFields1 += `${nestedField1} ${typeComment} #added by ${field.type.kind}`
nestedFields2 += `${nestedField2} ${typeComment} #added by ${field.type.kind}`
if (fieldType === 'ACL') {
access += `\n ${field.name} {\n ...accessFields \n } ${typeComment}`
}
} else {
console.log(` Field STILL not used: ${type.name} - ${field.name} [${field.type.kind} ${field.type.name}] ${fieldType}`)
}
})
//Create Final fragment for Type
if (count >= 1) {
//remove duplicate fragments
const uniqueFragments = [...new Set(framentVarsForOtherFragment)]
const uniqueFragments2 = [...new Set(framentVarsForOtherFragment2)]
// const typeFragmentFooter = `\n }\``
// const typeFragmentFooter2 = `\n }`+uniqueFragments+`\``
const keyedFragmentFooter1 = `\n }\`,`
const keyedFragmentFooter2 = `\n }${uniqueFragments}\`,`
const keyedFragmentFooter3 = `\n }${uniqueFragments2}\`,`
const keyedFragmentFooter4 = `\n }${access ? `\n \${accessFields}` : ""}\`,`
// typeFragments += fragmentHeaderWithVarName + typeFragmentFields + typeFragmentFooter
// fieldsNestedL2 += fragmentHeaderWithVarName2 + keyedFragmentFields + typeFragmentFooter2
keyedFragment1 += fragmentName + scalarFields + keyedFragmentFooter1
keyedFragment2 += fragmentName1 + scalarFields + nestedFields1 + keyedFragmentFooter2
keyedFragment3 += fragmentName2 + scalarFields + nestedFields2 + keyedFragmentFooter3
keyedFragment4 += fragmentNameAccess + scalarFields + access + keyedFragmentFooter4
fragmentsObj1 += `${keyedFragment1}\n`
fragmentsObj2 += `${keyedFragment2}\n`
fragmentsObj3 += `${keyedFragment3}\n`
fragmentsObj4 += `${keyedFragment4}\n`
}
})
fragmentsObj1 += '}'
fragmentsObj2 += '}'
fragmentsObj3 += '}'
fragmentsObj4 += '}'
// console.log(fragments)
// A hand written fragment to include manaully
const accessFragment = `export const accessFields = gql\`\n fragment accessFields on ACL {
level
grants {
id
firstName
lastName
organization
gravatar
isUser {
username
}
isGroup {
slug
}
}
}\`\n\n`
const fileContent = header + accessFragment + fragmentsObj1 + fragmentsObj2 + fragmentsObj3 + fragmentsObj4
fs.writeFile('./src/contexts/Fragments/codeGenFragments.js', fileContent, (err) => {
if (err) {
return console.log('ERROR: ', err)
}
return console.log('wrote codeGenFragments.js. first 200 chars: \n', fileContent.substring(0, 200))
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment