/**
 * Defines the type of the object
 */ export enum GraphQLType { SCALAR , INLINE_FRAGMENT , FRAGMENT } /**
 * The symbol to use to store the object GQL type
 */ export const typeSymbol = Symbol ( 'GraphQL Type' ) ; /**
 * The symbol to use to store the object parameters.
 */ export const paramsSymbol = Symbol ( 'GraphQL Params' ) ; /**
 * The parameters type.
 */ export interface Params { [ key ] : string | boolean | number | null | Params | Array < Params > ; } /**
 * A GQL inline fragment.
 */ export interface GraphQLInlineFragment { [ typeSymbol ] : GraphQLType . INLINE_FRAGMENT ; typeName : string ; internal : Record < string , unknown > ; } /**
 * Checks whether an object is a fragment object.
 */ function isInlineFragmentObject ( value ) { return ( typeof value === 'object' && value !== null && ( value as any ) [ typeSymbol ] === GraphQLType . INLINE_FRAGMENT ) ; } /**
 * A GQL fragment.
 */ export interface GraphQLFragment { [ typeSymbol ] : GraphQLType . FRAGMENT ; name : string ; typeName : string ; internal : Record < string , unknown > ; } /**
 * Checks whether an object is a fragment object.
 */ function isFragmentObject ( value ) { return ( typeof value === 'object' && value !== null && ( value as any ) [ typeSymbol ] === GraphQLType . FRAGMENT ) ; } /**
 * A GQL scalar object.
 * Stores the params of the scalar if any were given.
 */ export interface GraphQLScalar { [ typeSymbol ] : GraphQLType . SCALAR ; [ paramsSymbol ] ? : Params ; } /**
 * Checks whether an object is a scalar object.
 */ function isScalarObject ( value ) { return ( typeof value === 'object' && value !== null && ( value as any ) [ typeSymbol ] === GraphQLType . SCALAR ) ; } /**
 * Provides the context for the current render.
 */ interface RenderContext { fragments : Map < symbol , GraphQLFragment > ; } /**
 * Renders the name if it was given.
 */ function renderName ( name ) { return name === undefined ? '' : name ; } param params param brackets param array returns function renderParams ( params ? , brackets = true , array = false ) { if ( ! params ) { return '' ; } const builder = [ ] ; for ( const [ key , value ] of Object . entries ( params ) ) { let params ; if ( value === null ) { params = `null` ; } else if ( Array . isArray ( value ) ) { params = `[${ renderParams ( value as Array < Params > , false , true ) }]` ; } else if ( typeof value === 'object' ) { params = `{${ renderParams ( value as Params , false ) }}` ; } else { params = `${ value }` ; } builder . push ( array ? `${ params }` : `${ key }:${ params }` ) ; } let built = builder . join ( ',' ) ; if ( brackets ) { built = `(${ built })` ; } return built ; } /**
 * Renders a GQL scalar.
 */ function renderScalar ( name , params ? ) { return renderName ( name ) + renderParams ( params ) ; } /**
 * Renders a GQL inline fragment.
 */ function renderInlineFragment ( fragment , context ) { return `...on ${ fragment . typeName }${ renderObject ( undefined , fragment . internal , context ) }` ; } /**
 * Renders a GQL fragment.
 */ function renderFragment ( fragment , context ) { return `fragment ${ fragment . name } on ${ fragment . typeName }${ renderObject ( undefined , fragment . internal , context ) }` ; } /**
 * Renders a GQL array.
 */ function renderArray ( name , arr , context ) { const first = arr [ 0 ] ; if ( first === undefined || first === null ) { throw new Error ( 'Cannot render array with no first value' ) ; } ; ( first as any ) [ paramsSymbol ] = ( arr as any ) [ paramsSymbol ] ; return renderType ( name , first , context ) ; } /**
 * Renders the given value into its given GQL.
 */ function renderType ( name , value , context ) { switch ( typeof value ) { case 'bigint' : case 'boolean' : case 'number' : case 'string' : throw new Error ( `Rendering type ${ typeof value } directly is disallowed` ) ; case 'object' : if ( value === null ) { throw new Error ( 'Cannot render null' ) ; } if ( isScalarObject ( value ) ) { return `${ renderScalar ( name , value [ paramsSymbol ] ) } ` ; } else if ( Array . isArray ( value ) ) { return renderArray ( name , value , context ) ; } else { return renderObject ( name , value , context ) ; } case 'undefined' : return '' ; default : throw new Error ( `Cannot render type ${ typeof value }` ) ; } } /**
 * Renders a object to GQL.
 */ function renderObject ( name , obj , context ) { const fields = [ ] ; for ( const [ key , value ] of Object . entries ( obj ) ) { fields . push ( renderType ( key , value , context ) ) ; } for ( const sym of Object . getOwnPropertySymbols ( obj ) ) { const value = ( obj as any ) [ sym ] ; if ( isInlineFragmentObject ( value ) ) { fields . push ( renderInlineFragment ( value , context ) ) ; } else if ( isFragmentObject ( value ) ) { context . fragments . set ( sym , value ) ; fields . push ( `...${ value . name }` ) ; } } if ( fields . length === 0 ) { throw new Error ( 'Object cannot have no fields' ) ; } return `${ renderName ( name ) }${ renderParams ( ( obj as any ) [ paramsSymbol ] ) }{${ fields . join ( '' ) . trim ( ) }}` ; } /**
 * Performs a complete render of a given object.
 */ export function render ( value ) { const context = { fragments : new Map ( ) , } ; let rend = renderObject ( undefined , value , context ) ; const rendered = new Map < symbol , string > ( ) ; let executingContext = context ; let currentContext = { fragments : new Map ( ) , } ; while ( executingContext . fragments . size > 0 ) { for ( const [ sym , fragment ] of Array . from ( executingContext . fragments . entries ( ) ) ) { if ( ! rendered . has ( sym ) ) { rendered . set ( sym , renderFragment ( fragment , currentContext ) ) ; } } executingContext = currentContext ; currentContext = { fragments : new Map ( ) , } ; } return rend + Array . from ( rendered . values ( ) ) . join ( '' ) ; } export function fragmentToString ( value ) { const context = { fragments : new Map ( ) , } ; renderObject ( undefined , value , context ) ; const currentContext = { fragments : new Map ( ) , } ; let output = '' ; for ( const [ , fragment ] of Array . from ( context . fragments . entries ( ) ) ) { output = output + renderFragment ( fragment , currentContext ) ; } return output ; }