import * as withAbsintheSocket from '@absinthe/socket' ; import { GraphQLClient } from 'graphql-request' ; import * as Dom from 'graphql-request/dist/types.dom' ; import { print } from 'graphql/language/printer' ; import { Socket as PhoenixSocket } from 'phoenix' ; import gqlTag from 'graphql-tag' ; import { GraphQLError } from 'graphql' ; export interface Query < Result extends any , Payload extends any = void > { ( payload ) : Result ; } type Variable = string | number | boolean | null ; interface NoPayloadSubscription < R > { ( action ) : void ; dispose ( ) : void ; disposeWhere ( cb ) ; } interface PayloadSubscription < P , R > { ( payload , action ) : void ; dispose ( ) : void ; disposeWhere ( cb ) ; } interface Subscription { variables : { [ key ] : Variable ; } ; dispose : ( ) => void ; } type Http = { endpoint : string ; headers ? : ( ) => Dom . RequestInit [ 'headers' ] ; options ? : Dom . RequestInit ; } ; type Ws = { endpoint : string ; params ? : ( ) => { [ key ] : string | number | boolean ; } ; } | ( ( ) => PhoenixSocket | null ) ; type Queries = { rawQueries ? : { [ key ] : ( payload : any ) => any ; } ; queries ? : { [ key ] : ( payload : any ) => any ; } ; rawMutations ? : { [ key ] : ( payload : any ) => any ; } ; mutations ? : { [ key ] : ( payload : any ) => any ; } ; subscriptions ? : { [ key ] : ( payload : any ) => any ; } ; } ; type RawResult < T extends any > = Promise < { data ? : T ; extensions ? : any ; headers : Dom . Headers ; status : number ; errors ? : GraphQLError [ ] ; } > ; export type Graphql < T extends Queries > = { initialize ( http , ws ? ) : void ; } & { rawQueries : { [ N in keyof T [ 'rawQueries' ] ] : T [ 'rawQueries' ] [ N ] extends ( payload : infer P ) => infer R ? P extends void ? ( ) => RawResult < R > : ( payload : P ) => RawResult < R > : never ; } ; queries : { [ N in keyof T [ 'queries' ] ] : T [ 'queries' ] [ N ] extends ( payload : infer P ) => infer R ? P extends void ? ( ) => Promise < R > : ( payload : P ) => Promise < R > : never ; } ; rawMutations : { [ N in keyof T [ 'rawMutations' ] ] : T [ 'rawMutations' ] [ N ] extends ( payload : infer P ) => infer R ? P extends void ? ( ) => RawResult < R > : ( payload : P ) => RawResult < R > : never ; } ; mutations : { [ N in keyof T [ 'mutations' ] ] : T [ 'mutations' ] [ N ] extends ( payload : infer P ) => infer R ? P extends void ? ( ) => Promise < R > : ( payload : P ) => Promise < R > : never ; } ; subscriptions : { [ N in keyof T [ 'subscriptions' ] ] : T [ 'subscriptions' ] [ N ] extends ( payload : infer P ) => infer R ? P extends void ? NoPayloadSubscription < R > : PayloadSubscription < P , R > : never ; } ; } ; function createError ( message ) { throw new Error ( `OVERMIND-GRAPHQL: ${ message }` ) ; } type Literals = string | ReadonlyArray < string > ; export const gql = ( literals , ... placeholders ) : Query < any , any > => gqlTag ( literals , ... placeholders ) as any ; const _clients = { } ; const _subscriptions = { } ; export const graphql = ( queries ) => { let _http ; let _ws ; function getClient ( ) { if ( _http ) { const headers = typeof _http . headers === 'function' ? _http . headers ( ) : _http . options && _http . options . headers ? _http . options . headers : { } ; if ( _clients [ _http . endpoint ] ) { _clients [ _http . endpoint ] . setHeaders ( headers ) ; } else { _clients [ _http . endpoint ] = new GraphQLClient ( _http . endpoint , { ... _http . options , headers , } ) ; } return _clients [ _http . endpoint ] ; } return null ; } let wsClient = null ; function getWsClient ( ) { if ( _ws && ! wsClient ) { const socket = typeof _ws === 'function' ? _ws ( ) : new PhoenixSocket ( _ws . endpoint , { params : _ws . params ? _ws . params ( ) : undefined , } ) ; if ( ! socket ) { throw createError ( 'You are trying to create a Socket for subscriptions, but there is no socket or socket information provided' ) ; } wsClient = withAbsintheSocket . create ( socket ) ; return wsClient ; } return wsClient ; } const evaluatedQueries = { rawQueries : Object . keys ( queries . rawQueries || { } ) . reduce ( ( aggr , key ) => { aggr [ key ] = ( variables ) => { const query = queries . rawQueries ! [ key ] as any ; const client = getClient ( ) ; if ( client ) { return client . rawRequest ( print ( query ) , variables ) ; } throw createError ( 'You are running a query, though there is no HTTP endpoint configured' ) ; } ; return aggr ; } , { } ) , queries : Object . keys ( queries . queries || { } ) . reduce ( ( aggr , key ) => { aggr [ key ] = ( variables ) => { const query = queries . queries ! [ key ] as any ; const client = getClient ( ) ; if ( client ) { return client . request ( print ( query ) , variables ) ; } throw createError ( 'You are running a query, though there is no HTTP endpoint configured' ) ; } ; return aggr ; } , { } ) , rawMutations : Object . keys ( queries . rawMutations || { } ) . reduce ( ( aggr , key ) => { aggr [ key ] = ( variables ) => { const query = queries . rawMutations ! [ key ] as any ; const client = getClient ( ) ; if ( client ) { return client . rawRequest ( print ( query ) , variables ) ; } throw createError ( 'You are running a query, though there is no HTTP endpoint configured' ) ; } ; return aggr ; } , { } ) , mutations : Object . keys ( queries . mutations || { } ) . reduce ( ( aggr , key ) => { aggr [ key ] = ( variables ) => { const query = queries . mutations ! [ key ] as any ; const client = getClient ( ) ; if ( client ) { return client . request ( print ( query ) , variables ) ; } throw createError ( 'You are running a query, though there is no HTTP endpoint configured' ) ; } ; return aggr ; } , { } ) , subscriptions : Object . keys ( queries . subscriptions || { } ) . reduce ( ( aggr , key ) => { const query = queries . subscriptions ! [ key ] as any ; const queryString = print ( query ) ; if ( ! _subscriptions [ queryString ] ) { _subscriptions [ queryString ] = [ ] ; } function subscription ( arg1 , arg2 ) { const client = getWsClient ( ) ; if ( client ) { const variables = arg2 ? arg1 : { } ; const action = arg2 || arg1 ; const notifier = withAbsintheSocket . send ( client , { operation : queryString , variables , } ) ; const observer = withAbsintheSocket . observe ( client , notifier , { onResult : ( { data } ) => { action ( data ) ; } , } ) ; _subscriptions [ queryString ] . push ( { variables , dispose : ( ) => withAbsintheSocket . unobserve ( client , notifier , observer ) , } ) ; } else { throw createError ( 'There is no ws client available for this query' ) ; } } subscription . dispose = ( ) => { _subscriptions [ queryString ] . forEach ( ( sub ) => { try { sub . dispose ( ) ; } catch ( e ) { } } ) ; _subscriptions [ queryString ] . length = 0 ; } ; subscription . disposeWhere = ( cb ) => { _subscriptions [ queryString ] = _subscriptions [ queryString ] . reduce < Subscription [ ] > ( ( subAggr , sub ) => { if ( cb ( sub . variables ) ) { try { sub . dispose ( ) ; } catch ( e ) { } return subAggr ; } return subAggr . concat ( sub ) ; } , [ ] ) ; } ; aggr [ key ] = subscription ; return aggr ; } , { } ) , } ; return { initialize ( http , ws ? ) { _http = http ; if ( ws ) { _ws = ws ; } } , ... evaluatedQueries , } as any ; } ;