import { GraphQLArgument , GraphQLInputObjectType , GraphQLList , GraphQLNamedType , GraphQLObjectType , GraphQLOutputType , GraphQLResolveInfo , GraphQLSchema , GraphQLType , IntrospectionObjectType , IntrospectionType , defaultFieldResolver , getNamedType , isEnumType , isInterfaceType , isObjectType , isScalarType , isUnionType } from 'graphql' ; import { difference , each , eq , find , get , isArray , isEmpty , isObject , keys , map , set , union } from 'lodash' ; import pluralize from 'pluralize' ; import { Connection , DataResolver } from './GraphQLGenieInterfaces' ; import { FindByUniqueError , getReturnType , typeIsList } from './GraphQLUtils' ; export class Relation { public type0 : string ; public field0 : string ; public field0isList : boolean ; public type1 : string ; public field1 : string ; public field1isList : boolean ; constructor ( $type , $field , $field0isList ) { this . type0 = $type ; this . field0 = $field ; this . field0isList = $field0isList ; } setRelative ( relation ) { this . type1 = relation . type0 ; this . field1 = relation . field0 ; this . field1isList = relation . field0isList ; } isValidRelative ( relation ) { if ( ! this . type1 ) { return true ; } else { return this . isSameRelation ( relation ) || this . isCurrentRelation ( relation ) ; } } isSameRelation ( relation ) { return this . type0 === relation . type0 && this . field0 === relation . field0 && this . field0isList === relation . field0isList ; } isCurrentRelation ( relation ) { return this . type1 === relation . type0 && this . field1 === relation . field0 && this . field1isList === relation . field0isList ; } getInverse ( type , field , inverseType ? ) { const inverse = this . getInverseTuple ( type , field , inverseType ) ; return inverse ? inverse [ 1 ] : null ; } getInverseTuple ( type , field , inverseType ? ) { let inverse = null ; if ( this . type0 === type && this . field0 === field && ( ! inverseType || this . type1 === inverseType ) ) { inverse = [ this . type1 , this . field1 ] ; } else if ( this . type1 === type && this . field1 === field && ( ! inverseType || this . type0 === inverseType ) ) { inverse = [ this . type0 , this . field0 ] ; } return inverse ; } } export class Relations { public relations : Map < string , Relation > ; constructor ( ) { this . relations = new Map < string , Relation > ( ) ; } public getRelation ( name ) { let relations = null ; if ( this . relations . has ( name ) ) { relations = this . relations . get ( name ) ; } return relations ; } public getInverseWithoutName ( type , field , inverseType ) { let inverse = null ; const iter = this . relations . values ( ) ; let relation = iter . next ( ) . value ; while ( ! inverse && relation ) { inverse = relation . getInverse ( type , field , inverseType ) ; relation = iter . next ( ) . value ; } return inverse ; } public getInverse ( name , type , field ) { let inverse = null ; if ( this . relations . has ( name ) ) { const relation = this . relations . get ( name ) ; inverse = relation . getInverse ( type , field ) ; } return inverse ; } public setRelation ( name , type , field , fieldIsList ) { const newRelation = new Relation ( type , field , fieldIsList ) ; if ( ! this . relations . has ( name ) ) { this . relations . set ( name , newRelation ) ; } else { const relation = this . relations . get ( name ) ; if ( relation . isValidRelative ( newRelation ) ) { if ( ! relation . isSameRelation ( newRelation ) ) { relation . setRelative ( newRelation ) ; } } else { this . throwError ( name , type , field , relation . field0 ) ; } } } public setSelfRelation ( name , type , field , fieldIsList ) { const newRelation = new Relation ( type , field , fieldIsList ) ; newRelation . setRelative ( newRelation ) ; this . relations . set ( name , newRelation ) ; } private throwError ( name , type , primaryField , relatedField ) { throw new Error ( `Bad schema, relation could apply to multiple fields
			relation name: ${ name }
			fortune name: ${ type }
			curr field: ${ primaryField }
			other field: ${ relatedField }` ) ; } } const computeNumFieldsOfType = ( type , checkFieldTypeName ) : number => { let resultNum = 0 ; each ( type . fields , field => { if ( checkFieldTypeName === getReturnType ( field . type ) ) { resultNum ++ ; } } ) ; return resultNum ; } ; const getNumFieldsOfType = ( cache , type , checkFieldTypeName ) : number => { let numFields = 0 ; const typeName = getReturnType ( type ) ; if ( cache . has ( typeName ) && cache . get ( typeName ) . has ( checkFieldTypeName ) ) { numFields = cache . get ( typeName ) . get ( checkFieldTypeName ) ; } else { numFields = computeNumFieldsOfType ( type , checkFieldTypeName ) ; if ( ! cache . has ( typeName ) ) { cache . set ( typeName , new Map < string , number > ( ) ) ; } cache . get ( typeName ) . set ( checkFieldTypeName , numFields ) ; } return numFields ; } ; export const computeRelations = ( schemaInfo , typeNameResolver = ( name ) => name ) : Relations => { const numFieldsOfTypeCache = new Map < string , Map < string , number > > ( ) ; const relations = new Relations ( ) ; each ( keys ( schemaInfo ) , ( typeName ) => { const type = < IntrospectionObjectType > schemaInfo [ typeName ] ; each ( type . fields , field => { const relation = get ( field , 'metadata.relation' ) ; const fieldTypeName = getReturnType ( field . type ) ; const reslovedTypeName = typeNameResolver ( fieldTypeName ) ; if ( relation ) { relations . setRelation ( relation . name , reslovedTypeName , field . name , typeIsList ( field . type ) ) ; } else if ( typeName === fieldTypeName ) { relations . setSelfRelation ( `${ field . name }On${ typeName }` , reslovedTypeName , field . name , typeIsList ( field . type ) ) ; } else { const fieldTypeInfo = schemaInfo [ fieldTypeName ] ; if ( type && fieldTypeInfo ) { const numFields = getNumFieldsOfType ( numFieldsOfTypeCache , type , fieldTypeName ) ; const reverseNumFields = getNumFieldsOfType ( numFieldsOfTypeCache , fieldTypeInfo , typeName ) ; if ( numFields === 1 && reverseNumFields === 1 ) { const possibleTypes = [ typeName , fieldTypeName ] ; possibleTypes . sort ( ) ; relations . setRelation ( possibleTypes . join ( '_' ) , reslovedTypeName , field . name , typeIsList ( field . type ) ) ; } } } } ) ; } ) ; return relations ; } ; export enum Mutation { Create , Update , Delete , Upsert } export const clean = ( obj ) : any => { const returnObj = { } ; for ( const propName in obj ) { if ( obj [ propName ] !== null && obj [ propName ] !== undefined ) { if ( isObject ( obj [ propName ] ) && ! isEmpty ( obj [ propName ] ) ) { returnObj [ propName ] = obj [ propName ] ; } else { returnObj [ propName ] = obj [ propName ] ; } } } return returnObj ; } ; const setupArgs = ( results , args ) => { results . forEach ( ( types ) => { types = types ? types : [ ] ; types . forEach ( type => { if ( type && type . key && type . id && type . index > - 1 ) { const key = type . key ; const id = type . id ; const arg = args [ type . index ] ; if ( isArray ( arg [ key ] ) ) { if ( isArray ( id ) ) { arg [ key ] = union ( id , arg [ key ] ) ; } else if ( ! arg [ key ] . includes ( id ) ) { arg [ key ] . push ( id ) ; } } else { arg [ key ] = id ; } } } ) ; } ) ; return args ; } ; const resolveArgs = async ( args , returnType , mutation , dataResolver , currRecord , _args , _context , _info ) : Promise < Array < any > > => { const promises = [ ] ; args . forEach ( ( currArg , index ) => { for ( const argName in currArg ) { let argReturnType ; let argReturnRootType ; if ( ( isObjectType ( returnType ) || isInterfaceType ( returnType ) ) && returnType . getFields ( ) [ argName ] ) { argReturnType = returnType . getFields ( ) [ argName ] . type ; argReturnRootType = < GraphQLOutputType > getNamedType ( argReturnType ) ; if ( argReturnRootType [ 'name' ] . endsWith ( 'Connection' ) ) { argReturnRootType = < GraphQLOutputType > _info . schema . getType ( argReturnRootType [ 'name' ] . replace ( /Connection$/g , '' ) ) ; argReturnType = new GraphQLList ( argReturnRootType ) ; } } if ( argReturnRootType && ! isScalarType ( argReturnRootType ) && ! isEnumType ( argReturnRootType ) ) { const arg = currArg [ argName ] ; if ( isObject ( arg ) && argReturnType ) { currArg [ argName ] = typeIsList ( argReturnType ) ? [ ] : undefined ; if ( isInterfaceType ( argReturnRootType ) || isUnionType ( argReturnRootType ) ) { for ( const argKey in arg ) { const argTypeName = pluralize . singular ( argKey ) . toLowerCase ( ) ; argReturnRootType = < GraphQLOutputType > find ( _info . schema . getTypeMap ( ) , type => { return type . name . toLowerCase ( ) === argTypeName ; } ) ; promises . push ( mutateResolver ( mutation , dataResolver ) ( currRecord , arg [ argKey ] , _context , _info , index , argName , argReturnRootType ) ) ; } } else { promises . push ( mutateResolver ( mutation , dataResolver ) ( currRecord , arg , _context , _info , index , argName , argReturnRootType ) ) ; } } } } } ) ; const results = await Promise . all ( promises ) ; args = setupArgs ( results , args ) ; return args ; } ; const mutateResolver = ( mutation , dataResolver ) => { return async ( currRecord , _args , _context , _info , index ? , key ? , returnType ? ) => { await dataResolver . beginTransaction ( ) ; const recursed = returnType ? true : false ; if ( ! returnType ) { returnType = ( < GraphQLObjectType > _info . returnType ) . getFields ( ) . data . type ; returnType = < GraphQLObjectType > getNamedType ( returnType ) ; } const returnTypeName = getReturnType ( returnType ) ; const clientMutationId = _args . input && _args . input . clientMutationId ? _args . input . clientMutationId : '' ; let createArgs = _args . create ? _args . create : mutation === Mutation . Create && get ( _args , 'input.data' ) ? get ( _args , 'input.data' ) : [ ] ; createArgs = createArgs && ! isArray ( createArgs ) ? [ createArgs ] : createArgs ; let updateArgs = _args . update ? _args . update : mutation === Mutation . Update && get ( _args , 'input.data' ) ? get ( _args , 'input.data' ) : [ ] ; updateArgs = updateArgs && ! isArray ( updateArgs ) ? [ updateArgs ] : updateArgs ; let upsertArgs = _args . upsert ? _args . upsert : mutation === Mutation . Upsert && get ( _args , 'input' ) ? get ( _args , 'input' ) : [ ] ; upsertArgs = upsertArgs && ! isArray ( upsertArgs ) ? [ upsertArgs ] : upsertArgs ; let deleteArgs = _args . delete ? _args . delete : mutation === Mutation . Delete && _args . input . where ? _args . input . where : [ ] ; deleteArgs = deleteArgs && ! isArray ( deleteArgs ) ? [ deleteArgs ] : deleteArgs ; let connectArgs = _args . connect ? _args . connect : [ ] ; connectArgs = connectArgs && ! isArray ( connectArgs ) ? [ connectArgs ] : connectArgs ; let disconnectArgs = _args . disconnect ? _args . disconnect : [ ] ; disconnectArgs = disconnectArgs && ! isArray ( disconnectArgs ) ? [ disconnectArgs ] : disconnectArgs ; const whereArgs = _args . where ? _args . where : _args . input && _args . input . where ? _args . input . where : null ; const conditionsArgs = _args . conditions ? _args . conditions : _args . input && _args . input . conditions ? _args . input . conditions : null ; const canAddResults = await Promise . all ( [ dataResolver . canAdd ( returnTypeName , createArgs , { context : _context , info : _info } ) , dataResolver . canAdd ( returnTypeName , updateArgs , { context : _context , info : _info } ) ] ) ; const cannotAdd = canAddResults . includes ( false ) ; if ( cannotAdd ) { throw new Error ( 'Can not create record with duplicate on unique field on type ' + returnTypeName + ' ' + JSON . stringify ( createArgs ) + ' ' + JSON . stringify ( updateArgs ) ) ; } const dataResolverPromises = [ ] ; if ( ! isEmpty ( updateArgs ) ) { if ( whereArgs ) { if ( whereArgs !== true ) { const returnTypeName = getReturnType ( returnType ) ; currRecord = await dataResolver . getValueByUnique ( returnTypeName , whereArgs , { context : _context , info : _info } ) ; if ( ! currRecord || isEmpty ( currRecord ) ) { throw new FindByUniqueError ( `${ returnTypeName } does not exist with where args ${ JSON . stringify ( whereArgs ) }` , 'update' , { arg : whereArgs , typename : returnTypeName } ) ; } } } else if ( updateArgs [ 0 ] . data && updateArgs [ 0 ] . where ) { updateArgs . forEach ( ( currArg ) => { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { mutateResolver ( mutation , dataResolver ) ( currRecord , { update : currArg . data , where : currArg . where } , _context , _info , index , key , returnType ) . then ( ( result ) => { if ( recursed ) { resolve ( ) ; } else { resolve ( result [ 0 ] ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } ) ; updateArgs = [ ] ; } else if ( key && currRecord ) { const recordToUpdate = await dataResolver . getValueByUnique ( returnTypeName , { id : currRecord [ key ] } , { context : _context , info : _info } ) ; if ( recordToUpdate ) { currRecord = recordToUpdate ; } else { updateArgs = [ ] ; } } } if ( ! isEmpty ( upsertArgs ) ) { await Promise . all ( upsertArgs . map ( async ( currArg ) => { const whereArg = currArg . where ; let upsertRecord = currRecord ; if ( whereArg ) { upsertRecord = await dataResolver . getValueByUnique ( returnTypeName , whereArg , { context : _context , info : _info } ) ; } else if ( upsertRecord && key ) { upsertRecord = upsertRecord [ key ] ? await dataResolver . getValueByUnique ( returnTypeName , { id : upsertRecord [ key ] } , { context : _context , info : _info } ) : null ; } let newArgs = { create : currArg . create } ; if ( upsertRecord && ! isEmpty ( upsertRecord ) ) { newArgs = { where : true , update : currArg . update , conditions : conditionsArgs } ; } dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { mutateResolver ( mutation , dataResolver ) ( upsertRecord , newArgs , _context , _info , index , key , returnType ) . then ( ( result ) => { if ( result [ 0 ] ) { resolve ( result [ 0 ] ) ; } else { resolve ( ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } ) ) ; } [ createArgs , updateArgs ] = await Promise . all ( [ resolveArgs ( createArgs , returnType , Mutation . Create , dataResolver , currRecord , _args , _context , _info ) , resolveArgs ( updateArgs , returnType , Mutation . Update , dataResolver , currRecord , _args , _context , _info ) ] ) ; createArgs . forEach ( ( createArg ) => { createArg = createArg . hasOwnProperty ? createArg : Object . assign ( { } , createArg ) ; createArg = clean ( createArg ) ; if ( createArg && ! isEmpty ( createArg ) ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . create ( returnTypeName , createArg , { context : _context , info : _info } ) . then ( data => { const id = isArray ( data ) ? map ( data , 'id' ) : data . id ; resolve ( { index , key , id , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } } ) ; updateArgs . forEach ( ( updateArg ) => { updateArg = updateArg . hasOwnProperty ? updateArg : Object . assign ( { } , updateArg ) ; for ( const updateArgKey in updateArg ) { const currArg = updateArg [ updateArgKey ] ; const currRecordArg = currRecord [ updateArgKey ] ; if ( eq ( currRecordArg , currArg ) ) { delete currRecord [ updateArgKey ] ; } else if ( isArray ( currArg ) && isArray ( currRecordArg ) ) { updateArg [ updateArgKey ] = difference ( currArg , currRecordArg ) ; } } const cleanArg = clean ( updateArg ) ; if ( cleanArg && ! isEmpty ( cleanArg ) ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { cleanArg . id = currRecord . id ; const meta = { context : _context , info : _info } ; meetsConditions ( conditionsArgs , returnTypeName , returnType , currRecord , dataResolver , _context , _info ) . then ( meetsConditionsResult => { if ( ! meetsConditionsResult ) { resolve ( { index , key , id : [ ] , unalteredData : currRecord } ) ; } else { dataResolver . update ( returnTypeName , cleanArg , meta ) . then ( data => { const id = isArray ( data ) ? map ( data , 'id' ) : data . id ; resolve ( { index , key , id , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } else if ( currRecord ) { currRecord = Object . assign ( currRecord , updateArg ) ; } } ) ; connectArgs . forEach ( connectArg => { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . getValueByUnique ( returnTypeName , connectArg , { context : _context , info : _info } ) . then ( data => { if ( data && data [ 'id' ] ) { resolve ( { index , key , id : data [ 'id' ] , data } ) ; } else { reject ( new FindByUniqueError ( `connect: ${ returnTypeName } does not exist with where args ${ JSON . stringify ( connectArg ) }` , 'disconnect' , { arg : connectArg , typename : returnTypeName } ) ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } ) ; const disconnectPromises = [ ] ; disconnectArgs . forEach ( disconnectArg => { if ( disconnectArg === true ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . update ( currRecord . __typename , { id : currRecord . id , [ key ] : null } , { context : _context , info : _info } ) . then ( data => { resolve ( { index , key , id : null , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } else { disconnectPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . getValueByUnique ( returnTypeName , disconnectArg , { context : _context , info : _info } ) . then ( data => { if ( data && data [ 'id' ] ) { resolve ( data [ 'id' ] ) ; } else { reject ( new FindByUniqueError ( `disconnect: ${ returnTypeName } does not exist with where args ${ JSON . stringify ( disconnectArg ) }` , 'disconnect' , { arg : disconnectArg , typename : returnTypeName } ) ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } } ) ; const disconnectIds = await Promise . all ( disconnectPromises ) ; if ( ! isEmpty ( disconnectIds ) ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . update ( currRecord . __typename , { id : currRecord . id , [ key ] : disconnectIds } , { context : _context , info : _info } , { pull : true } ) . then ( data => { resolve ( { index , key , id : data [ key ] , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } const deletePromises = [ ] ; deleteArgs . forEach ( deleteArg => { if ( deleteArg === true ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . delete ( dataResolver . getLink ( currRecord . __typename , key ) , [ currRecord [ key ] ] , { context : _context , info : _info } ) . then ( data => { resolve ( { index , key , id : null , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } else if ( whereArgs && ! currRecord ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . getValueByUnique ( returnTypeName , whereArgs , { context : _context , info : _info } ) . then ( whereData => { currRecord = whereData ; if ( ! currRecord || isEmpty ( currRecord ) ) { throw new FindByUniqueError ( `${ returnTypeName } does not exist with where args ${ JSON . stringify ( whereArgs ) }` , 'delete' , { arg : whereArgs , typename : returnTypeName } ) ; } meetsConditions ( conditionsArgs , returnTypeName , returnType , currRecord , dataResolver , _context , _info ) . then ( meetsConditionsResult => { if ( ! meetsConditionsResult ) { resolve ( { index , key , id : [ ] , unalteredData : currRecord } ) ; } else { dataResolver . delete ( currRecord . __typename , [ currRecord . id ] , { context : _context , info : _info } ) . then ( ( ) => { resolve ( { index , key , id : null , data : currRecord } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } else { deletePromises . push ( new Promise ( ( resolve , reject ) => { const deleteTypeName = dataResolver . getLink ( currRecord . __typename , key ) ; dataResolver . getValueByUnique ( deleteTypeName , deleteArg , { context : _context , info : _info } ) . then ( data => { if ( data && data [ 'id' ] ) { resolve ( data [ 'id' ] ) ; } else { reject ( new FindByUniqueError ( `${ deleteTypeName } does not exist with where args ${ JSON . stringify ( deleteArg ) }` , 'delete' , { arg : deleteArg , typename : deleteTypeName } ) ) ; } } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } } ) ; const deleteIds = await Promise . all ( deletePromises ) ; if ( ! isEmpty ( deleteIds ) ) { dataResolverPromises . push ( new Promise ( ( resolve , reject ) => { dataResolver . delete ( dataResolver . getLink ( currRecord . __typename , key ) , deleteIds , { context : _context , info : _info } ) . then ( data => { resolve ( { index , key , id : data [ key ] , data } ) ; } ) . catch ( reason => { reject ( reason ) ; } ) ; } ) ) ; } const dataResult = await Promise . all ( dataResolverPromises ) ; if ( recursed ) { return dataResult ; } else { await dataResolver . endTransaction ( ) ; let data = get ( dataResult , '[0].data' ) ; const unalteredData = get ( dataResult , '[0].unalteredData' , null ) ; if ( ! unalteredData && ! data && mutation === Mutation . Delete ) { data = currRecord ; } else if ( ! unalteredData && ! data ) { data = currRecord ; } return { data , clientMutationId , unalteredData } ; } } ; } ; export const createResolver = ( dataResolver ) => { return mutateResolver ( Mutation . Create , dataResolver ) ; } ; export const updateResolver = ( dataResolver ) => { return mutateResolver ( Mutation . Update , dataResolver ) ; } ; export const upsertResolver = ( dataResolver ) => { return mutateResolver ( Mutation . Upsert , dataResolver ) ; } ; export const deleteResolver = ( dataResolver ) => { return mutateResolver ( Mutation . Delete , dataResolver ) ; } ; export const getTypeResolver = ( dataResolver , schema , field , returnConnection = false ) => { const schemaType = schema . getType ( getReturnType ( field . type ) ) ; let resolver ; if ( ! isScalarType ( schemaType ) && ! isEnumType ( schemaType ) ) { resolver = async ( root , _args , _context , _info ) : Promise < any > => { const fortuneReturn = root && root . fortuneReturn ? root . fortuneReturn : root ; if ( ! fortuneReturn ) { return fortuneReturn ; } const cache = root && root . cache ? root . cache : new Map < string , object > ( ) ; const typeName = getReturnType ( field . type ) ; let result = [ ] ; let returnArray = false ; let fieldValue = fortuneReturn [ field . name ] ; returnArray = isArray ( fieldValue ) ; fieldValue = returnArray ? fieldValue : [ fieldValue ] ; if ( isObject ( fieldValue [ 0 ] ) ) { result = fieldValue ; } const ids = [ ] ; let options = { } ; _args = moveArgsIntoWhere ( _args ) ; let where = null ; if ( _args && _args . where ) { where = _args . where ; options = parseFilter ( where , schemaType ) ; } if ( _args . orderBy ) { set ( options , 'orderBy' , _args . orderBy ) ; } if ( _args . skip ) { set ( options , 'offset' , _args . skip ) ; } let connection ; options = clean ( options ) ; if ( ! isEmpty ( options ) ) { result = [ ] ; } if ( isEmpty ( result ) ) { fieldValue . forEach ( id => { if ( id ) { if ( cache . has ( id ) ) { result . push ( cache . get ( id ) ) ; } else { ids . push ( id ) ; } } } ) ; } let findOptions = { } ; let applyOptionsWithCombinedResult = false ; if ( ! isEmpty ( result ) && ! isEmpty ( options ) ) { applyOptionsWithCombinedResult = true ; } else { findOptions = options ; } if ( ! isEmpty ( ids ) ) { let findResult = await dataResolver . find ( typeName , ids , findOptions , { context : _context , info : _info } ) ; if ( findResult ) { findResult = isArray ( findResult ) ? findResult : [ findResult ] ; findResult = findResult . filter ( function ( n ) { return ! ! n ; } ) ; findResult . forEach ( result => { cache . set ( result . id , result ) ; } ) ; result = result . concat ( findResult ) ; } } if ( applyOptionsWithCombinedResult ) { result = dataResolver . applyOptions ( typeName , result , options ) ; } if ( ( _args . orderBy || where ) && ( isObjectType ( schemaType ) || isInterfaceType ( schemaType ) ) ) { const pullIds = await filterNested ( where , _args . orderBy , schemaType , fortuneReturn , cache , dataResolver , _context , _info ) ; result = result . filter ( entry => ! pullIds . has ( entry . id ) ) ; } result . forEach ( resultElement => { for ( const resultElementField in resultElement ) { if ( cache . has ( `${ resultElement . id }.${ resultElementField }` ) ) { resultElement [ resultElementField ] = cache . get ( `${ resultElement . id }.${ resultElementField }` ) ; } } } ) ; connection = dataResolver . getConnection ( result , _args . before , _args . after , _args . first , _args . last ) ; result = connection . edges ; result = result . map ( ( entry ) => { return { fortuneReturn : entry , cache : cache , __typename : entry . __typename } ; } ) ; result = ! returnArray && result . length === 0 ? null : returnArray ? result : result [ 0 ] ; if ( returnConnection ) { result = { edges : result , pageInfo : connection && connection . pageInfo ? connection . pageInfo : null , aggregate : connection && connection . aggregate ? connection . aggregate : null } ; } return result ; } ; } else { resolver = async ( root , _args , _context , _info ) : Promise < any > => { const fortuneReturn = root && root . fortuneReturn ? root . fortuneReturn : root ; const result = await defaultFieldResolver . apply ( this , [ fortuneReturn , _args , _context , _info ] ) ; return result ; } ; } return resolver ; } ; export const getAllResolver = ( dataResolver , schema , type , returnConnection = false ) => { return async ( _root , _args , _context , _info ) => { let options = { } ; let where = null ; _args = moveArgsIntoWhere ( _args ) ; const schemaType = schema . getType ( type . name ) ; if ( _args && _args . where ) { where = _args . where ; options = parseFilter ( _args . where , schemaType ) ; } if ( _args . orderBy ) { set ( options , 'orderBy' , _args . orderBy ) ; } if ( _args . skip ) { set ( options , 'offset' , _args . skip ) ; } let connection ; let result = [ ] ; let fortuneReturn = await dataResolver . find ( type . name , null , options , { context : _context , info : _info } ) ; if ( fortuneReturn && ! isEmpty ( fortuneReturn ) ) { fortuneReturn = isArray ( fortuneReturn ) ? fortuneReturn : [ fortuneReturn ] ; connection = dataResolver . getConnection ( fortuneReturn , _args . before , _args . after , _args . first , _args . last ) ; fortuneReturn = connection . edges ; const cache = new Map < string , object > ( ) ; fortuneReturn . forEach ( result => { if ( result && result . id ) { cache . set ( result . id , result ) ; } } ) ; if ( ( _args . orderBy || where ) && ( isObjectType ( schemaType ) || isInterfaceType ( schemaType ) ) ) { const pullIds = await filterNested ( where , _args . orderBy , schemaType , fortuneReturn , cache , dataResolver , _context , _info ) ; fortuneReturn = pullIds . size > 0 ? fortuneReturn . filter ( result => ! pullIds . has ( result ? result . id : '' ) ) : fortuneReturn ; } result = fortuneReturn . map ( ( result ) => { if ( ! result ) { return result ; } return { fortuneReturn : result , cache : cache , where , __typename : result . __typename } ; } ) ; } if ( returnConnection ) { result = { edges : result , pageInfo : connection && connection . pageInfo ? connection . pageInfo : null , aggregate : connection && connection . aggregate ? connection . aggregate : null } ; } return result ; } ; } ; export const queryArgs = { 'first' : { type : 'Int' , description : 'Slice result from the start' } , 'last' : { type : 'Int' , description : 'Slice result from the end' } , 'skip' : { type : 'Int' , description : 'Skip results' } , 'before' : { type : 'String' , description : 'Cursor returned by previous connection queries for pagination' } , 'after' : { type : 'String' , description : 'Cursor returned by previous connection queries for pagination' } , } ; export const fortuneFilters = [ 'not' , 'or' , 'and' , 'range' , 'match' , 'exists' ] ; const genieFindArgs = [ 'first' , 'where' , 'orderBy' , 'local' , 'last' , 'skip' , 'before' , 'after' ] ; export const getRootMatchFields = ( matchInput ) : { [ key ] : GraphQLArgument ; } => { const matchFields = matchInput . getFields ( ) ; const args = { } ; Object . keys ( matchFields ) . forEach ( key => { let newKey = key ; if ( genieFindArgs . includes ( key ) ) { newKey = `f_${ key }` ; } args [ newKey ] = matchFields [ key ] ; args [ newKey ] . name = newKey ; args [ newKey ] . description = `${ key } matches at least one of argument` ; } ) ; return args ; } ; export const moveArgsIntoWhere = ( args ) : object => { if ( ! args ) { return args ; } Object . keys ( args ) . forEach ( ( argKey ) => { if ( ! genieFindArgs . includes ( argKey ) ) { set ( args , 'where.match.' + argKey , args [ argKey ] ) ; delete args [ argKey ] ; } } ) ; return args ; } ; export const parseFilter = ( filter , type ) => { if ( ! isObjectType ( type ) && ! isInterfaceType ( type ) ) { return filter ; } if ( ! filter || ! isObject ( filter ) || isArray ( filter ) ) { return filter ; } each ( type . getFields ( ) , field => { if ( ! fortuneFilters . includes ( field . name ) && filter [ field . name ] ) { if ( filter [ 'and' ] ) { filter [ 'and' ] . push ( { exists : { [ field . name ] : true } } ) ; } else { set ( filter , `exists.${ field . name }` , true ) ; } } } ) ; return filter ; } ; export const filterNested = async ( filter , orderBy , type , fortuneReturn , cache , dataResolver , _context , _info ) : Promise < Set < string > > => { const pullIds = new Set < string > ( ) ; if ( ! cache ) { cache = new Map < string , object > ( ) ; } if ( ( orderBy || filter ) && ( isObjectType ( type ) || isInterfaceType ( type ) ) ) { await Promise . all ( map ( type . getFields ( ) , async ( field ) => { const currFilter = filter && filter [ field . name ] ? filter [ field . name ] : filter && filter [ `f_${ field . name }` ] ? filter [ `f_${ field . name }` ] : null ; const currOrderBy = orderBy && orderBy [ field . name ] ? orderBy [ field . name ] : orderBy && orderBy [ `f_${ field . name }` ] ? orderBy [ `f_${ field . name }` ] : null ; const childType = getNamedType ( field . type ) ; if ( ! isScalarType ( childType ) && ! isEnumType ( childType ) && ( currFilter || currOrderBy ) ) { const options = currFilter ? parseFilter ( currFilter , childType ) : { } ; await Promise . all ( fortuneReturn . map ( async ( result ) => { if ( ! result ) { return result ; } const childIds = result [ field . name ] ; if ( childIds && ! isEmpty ( childIds ) ) { if ( currOrderBy ) { options [ 'orderBy' ] = currOrderBy ; } let childReturn = await dataResolver . find ( childType . name , childIds , options , { context : _context , info : _info } ) ; if ( isArray ( childReturn ) && ! isEmpty ( childReturn ) ) { const recursePullIds = await filterNested ( currFilter , currOrderBy , childType , childReturn , cache , dataResolver , _context , _info ) ; childReturn = childReturn ? childReturn . filter ( result => { if ( ! result ) { return result ; } return ! recursePullIds . has ( result . id ) ; } ) : childReturn ; } else if ( childReturn && ( currOrderBy || currFilter ) ) { const recursePullIds = await filterNested ( currFilter , currOrderBy , childType , [ childReturn ] , cache , dataResolver , _context , _info ) ; childReturn = childReturn ? [ childReturn ] . filter ( result => { if ( ! result ) { return result ; } return ! recursePullIds . has ( result . id ) ; } ) : childReturn ; } if ( childReturn && ! isEmpty ( childReturn ) ) { if ( cache ) { if ( childReturn . id ) { cache . set ( childReturn . id , childReturn ) ; } else { cache . set ( `${ result . id }.${ field . name }` , childReturn ) ; } } } else { pullIds . add ( result . id ) ; } } } ) ) ; } } ) ) ; } return pullIds ; } ; export const getPayloadTypeName = ( typeName ) : string => { return `${ typeName }Payload` ; } ; export const getPayloadTypeDef = ( typeName ) : string => { return `
		type ${ getPayloadTypeName ( typeName ) } {
			data: ${ typeName }
			clientMutationId: String
			#In the case of a update or delete and you had conditions, if the conditions did not match the existing object will be returned here. data will be null
			unalteredData: ${ typeName }
		}` ; } ; export const capFirst = ( val ) => { return val ? val . charAt ( 0 ) . toUpperCase ( ) + val . slice ( 1 ) : '' ; } ; export const isConnectionType = ( type ) : boolean => { let isConnection = false ; if ( isObjectType ( type ) && type . name . endsWith ( 'Connection' ) ) { isConnection = true ; } return isConnection ; } ; export const getRecordFromResolverReturn = ( record ) => { record = record && record . fortuneReturn ? record . fortuneReturn : record ; record = record && record . edges ? record . edges : record ; record = record && record . node ? record . node : record ; record = record && record . fortuneReturn ? record . fortuneReturn : record ; record = record && record . data ? record . data : record ; return record ; } ; export const meetsConditions = async ( conditionsArgs , returnTypeName , returnType , currRecord , dataResolver , _context , _info ) => { let meetsConditions = true ; if ( conditionsArgs ) { const conditionsOptions = parseFilter ( conditionsArgs , < GraphQLNamedType > returnType ) ; let dataAfterConditions = dataResolver . applyOptions ( returnTypeName , currRecord , conditionsOptions ) ; if ( ! isEmpty ( dataAfterConditions ) ) { if ( conditionsArgs && ( isObjectType ( returnType ) || isInterfaceType ( returnType ) ) ) { const pullIds = await filterNested ( conditionsArgs , null , returnType , currRecord , null , dataResolver , _context , _info ) ; dataAfterConditions = dataAfterConditions . filter ( entry => ! pullIds . has ( entry . id ) ) ; } } if ( isEmpty ( dataAfterConditions ) ) { meetsConditions = false ; } } return meetsConditions ; } ;