packages feed

morpheus-graphql-client 0.20.1 → 0.21.0

raw patch · 22 files changed

+572/−144 lines, 22 filesdep +modern-uridep +morpheus-graphql-subscriptionsdep +reqdep ~morpheus-graphql-code-gendep ~morpheus-graphql-corePVP ok

version bump matches the API change (PVP)

Dependencies added: modern-uri, morpheus-graphql-subscriptions, req, unliftio-core, websockets, wuss

Dependency ranges changed: morpheus-graphql-code-gen, morpheus-graphql-core

API changes (from Hackage documentation)

- Data.Morpheus.Client: __fetch :: (Fetch a, Monad m, Show a, ToJSON (Args a), FromJSON a) => String -> FieldName -> (ByteString -> m ByteString) -> Args a -> m (Either (FetchError a) a)
+ Data.Morpheus.Client: data GQLClient
+ Data.Morpheus.Client: data ResponseStream a
+ Data.Morpheus.Client: forEach :: (MonadIO m, MonadUnliftIO m, MonadFail m) => (GQLClientResult a -> m ()) -> ResponseStream a -> m ()
+ Data.Morpheus.Client: request :: (ClientTypeConstraint a, MonadFail m) => GQLClient -> Args a -> m (ResponseStream a)
+ Data.Morpheus.Client: single :: MonadIO m => ResponseStream a -> m (GQLClientResult a)
+ Data.Morpheus.Client: type GQLClientResult (a :: Type) = (Either (FetchError a) a)
+ Data.Morpheus.Client: withHeaders :: GQLClient -> [Header] -> GQLClient
- Data.Morpheus.Client: class Fetch a where {
+ Data.Morpheus.Client: class (RequestType a, ToJSON (Args a), FromJSON a) => Fetch a where {
- Data.Morpheus.Client: fetch :: (Fetch a, Monad m, FromJSON a) => (ByteString -> m ByteString) -> Args a -> m (Either (FetchError a) a)
+ Data.Morpheus.Client: fetch :: (Fetch a, Monad m) => (ByteString -> m ByteString) -> Args a -> m (Either (FetchError a) a)

Files

changelog.md view
@@ -77,8 +77,7 @@  ### breaking changes -- from now you should provide for every custom graphql scalar definition corresponding haskell type definition and `GQLScalar` implementation fot it. for details see [`examples-client`](https://github.com/morpheusgraphql/morpheus-graphql/tree/master/examples-client)-+- from now you should provide for every custom graphql scalar definition corresponding haskell type definition and `GQLScalar` implementation fot it. - input fields and query arguments are imported without namespace  ### new features
morpheus-graphql-client.cabal view
@@ -5,9 +5,9 @@ -- see: https://github.com/sol/hpack  name:           morpheus-graphql-client-version:        0.20.1+version:        0.21.0 synopsis:       Morpheus GraphQL Client-description:    Build GraphQL APIs with your favourite functional language!+description:    Build GraphQL APIs with your favorite functional language! category:       web, graphql, client homepage:       https://morpheusgraphql.com bug-reports:    https://github.com/nalchevanidze/morpheus-graphql/issues@@ -59,9 +59,14 @@       Data.Morpheus.Client.Declare       Data.Morpheus.Client.Declare.Aeson       Data.Morpheus.Client.Declare.Client-      Data.Morpheus.Client.Declare.Fetch+      Data.Morpheus.Client.Declare.RequestType       Data.Morpheus.Client.Declare.Type       Data.Morpheus.Client.Fetch+      Data.Morpheus.Client.Fetch.GQLClient+      Data.Morpheus.Client.Fetch.Http+      Data.Morpheus.Client.Fetch.RequestType+      Data.Morpheus.Client.Fetch.ResponseStream+      Data.Morpheus.Client.Fetch.WebSockets       Data.Morpheus.Client.Internal.TH       Data.Morpheus.Client.Internal.Types       Data.Morpheus.Client.Internal.Utils@@ -85,14 +90,20 @@     , bytestring >=0.10.4 && <0.12.0     , containers >=0.4.2.1 && <0.7.0     , file-embed >=0.0.10 && <1.0.0-    , morpheus-graphql-code-gen >=0.20.0 && <0.21.0-    , morpheus-graphql-core >=0.20.0 && <0.21.0+    , modern-uri >=0.1.0.0 && <1.0.0+    , morpheus-graphql-code-gen >=0.21.0 && <0.22.0+    , morpheus-graphql-core >=0.21.0 && <0.22.0+    , morpheus-graphql-subscriptions     , mtl >=2.0.0 && <3.0.0     , relude >=0.3.0 && <2.0.0+    , req >=3.0.0 && <4.0.0     , template-haskell >=2.0.0 && <3.0.0     , text >=1.2.3 && <1.3.0     , transformers >=0.3.0 && <0.6.0+    , unliftio-core >=0.0.1 && <0.4.0     , unordered-containers >=0.2.8 && <0.3.0+    , websockets >=0.12.6.0 && <1.0.0+    , wuss >=1.0.0 && <3.0.0   default-language: Haskell2010  test-suite morpheus-graphql-client-test@@ -119,15 +130,21 @@     , containers >=0.4.2.1 && <0.7.0     , directory >=1.0.0 && <2.0.0     , file-embed >=0.0.10 && <1.0.0+    , modern-uri >=0.1.0.0 && <1.0.0     , morpheus-graphql-client-    , morpheus-graphql-code-gen >=0.20.0 && <0.21.0-    , morpheus-graphql-core >=0.20.0 && <0.21.0+    , morpheus-graphql-code-gen >=0.21.0 && <0.22.0+    , morpheus-graphql-core >=0.21.0 && <0.22.0+    , morpheus-graphql-subscriptions     , mtl >=2.0.0 && <3.0.0     , relude >=0.3.0 && <2.0.0+    , req >=3.0.0 && <4.0.0     , tasty >=0.1.0 && <1.5.0     , tasty-hunit >=0.1.0 && <1.0.0     , template-haskell >=2.0.0 && <3.0.0     , text >=1.2.3 && <1.3.0     , transformers >=0.3.0 && <0.6.0+    , unliftio-core >=0.0.1 && <0.4.0     , unordered-containers >=0.2.8 && <0.3.0+    , websockets >=0.12.6.0 && <1.0.0+    , wuss >=1.0.0 && <3.0.0   default-language: Haskell2010
src/Data/Morpheus/Client.hs view
@@ -1,4 +1,8 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NoImplicitPrelude #-}  module Data.Morpheus.Client@@ -14,6 +18,14 @@     declareLocalTypes,     declareLocalTypesInline,     clientTypeDeclarations,+    -- Fetch API+    GQLClient,+    GQLClientResult,+    ResponseStream,+    withHeaders,+    request,+    forEach,+    single,     -- DEPRECATED EXPORTS     gql,     defineByDocument,@@ -41,9 +53,18 @@ import Data.Morpheus.Client.Fetch   ( Fetch (..),   )+import Data.Morpheus.Client.Fetch.ResponseStream+  ( GQLClient,+    ResponseStream,+    forEach,+    request,+    single,+    withHeaders,+  ) import Data.Morpheus.Client.Internal.Types   ( ExecutableSource,     FetchError (..),+    GQLClientResult,     SchemaSource (..),   ) import Data.Morpheus.Types.GQLScalar
src/Data/Morpheus/Client/Declare.hs view
@@ -12,10 +12,8 @@   ) where -import Data.Morpheus.Client.Declare.Client-  ( declareTypes,-  )-import Data.Morpheus.Client.Declare.Fetch+import Data.Morpheus.Client.Declare.Client (declareTypes)+import Data.Morpheus.Client.Declare.RequestType (declareRequestType) import Data.Morpheus.Client.Internal.Types   ( ExecutableSource,     SchemaSource,@@ -52,7 +50,7 @@     )     ( \(fetch, types) ->         (<>)-          <$> declareFetch query fetch+          <$> declareRequestType query fetch           <*> declareTypes types     ) 
src/Data/Morpheus/Client/Declare/Aeson.hs view
− src/Data/Morpheus/Client/Declare/Fetch.hs
@@ -1,52 +0,0 @@-{-# LANGUAGE ConstrainedClassMethods #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskellQuotes #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE NoImplicitPrelude #-}--module Data.Morpheus.Client.Declare.Fetch-  ( declareFetch,-  )-where--import Data.Morpheus.Client.Fetch (Fetch (..))-import Data.Morpheus.Client.Internal.Types-  ( FetchDefinition (..),-    TypeNameTH (..),-  )-import Data.Morpheus.CodeGen.Internal.TH-  ( applyCons,-    toCon,-    typeInstanceDec,-  )-import qualified Data.Text as T-import Language.Haskell.TH-  ( Dec,-    Q,-    Type,-    clause,-    cxt,-    funD,-    instanceD,-    normalB,-  )-import Relude hiding (ByteString, Type)--declareFetch :: Text -> FetchDefinition -> Q [Dec]-declareFetch query FetchDefinition {clientArgumentsTypeName, rootTypeName} =-  pure <$> instanceD (cxt []) iHead methods-  where-    queryString = T.unpack query-    typeName = typename rootTypeName-    iHead = applyCons ''Fetch [typeName]-    methods =-      [ funD 'fetch [clause [] (normalB [|__fetch queryString typeName|]) []],-        pure $ typeInstanceDec ''Args (toCon typeName) (argumentType clientArgumentsTypeName)-      ]--argumentType :: Maybe TypeNameTH -> Type-argumentType Nothing = toCon ("()" :: String)-argumentType (Just clientTypeName) = toCon (typename clientTypeName)
+ src/Data/Morpheus/Client/Declare/RequestType.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskellQuotes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Declare.RequestType+  ( declareRequestType,+  )+where++import Data.Morpheus.Client.Fetch.RequestType+  ( RequestType (..),+  )+import Data.Morpheus.Client.Internal.Types+  ( FetchDefinition (..),+    TypeNameTH (..),+  )+import Data.Morpheus.CodeGen.Internal.TH+  ( applyCons,+    funDSimple,+    toCon,+    typeInstanceDec,+    _',+  )+import qualified Data.Text as T+import Language.Haskell.TH+  ( Dec,+    Q,+    Type,+    cxt,+    instanceD,+  )+import Relude hiding (ByteString, Type)++declareRequestType :: Text -> FetchDefinition -> Q [Dec]+declareRequestType query FetchDefinition {clientArgumentsTypeName, rootTypeName, fetchOperationType} =+  pure <$> instanceD (cxt []) iHead methods+  where+    queryString = T.unpack query+    typeName = typename rootTypeName+    iHead = applyCons ''RequestType [typeName]+    methods =+      [ funDSimple '__name [_'] [|typeName|],+        funDSimple '__query [_'] [|queryString|],+        funDSimple '__type [_'] [|fetchOperationType|],+        pure $ typeInstanceDec ''RequestArgs (toCon typeName) (argumentType clientArgumentsTypeName)+      ]++argumentType :: Maybe TypeNameTH -> Type+argumentType Nothing = toCon ("()" :: String)+argumentType (Just clientTypeName) = toCon (typename clientTypeName)
src/Data/Morpheus/Client/Declare/Type.hs view
@@ -10,6 +10,7 @@   ) where +import Data.Morpheus.Client.Internal.TH (isTypeDeclared) import Data.Morpheus.Client.Internal.Types   ( ClientConstructorDefinition (..),     ClientTypeDefinition (..),@@ -34,15 +35,14 @@   ) import Language.Haskell.TH import Relude hiding (Type)-import Data.Morpheus.Client.Internal.TH (isTypeDeclared)  typeDeclarations :: TypeKind -> ClientTypeDefinition -> Q [Dec] typeDeclarations KindScalar _ = pure [] typeDeclarations _ c = do-    exists <- isTypeDeclared c-    if exists-        then pure []-        else pure [declareType c]+  exists <- isTypeDeclared c+  if exists+    then pure []+    else pure [declareType c]  declareType :: ClientTypeDefinition -> Dec declareType
src/Data/Morpheus/Client/Fetch.hs view
@@ -1,10 +1,17 @@ {-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-}  module Data.Morpheus.Client.Fetch   ( Fetch (..),+    decodeResponse,   ) where @@ -14,45 +21,23 @@     eitherDecode,     encode,   )-import qualified Data.Aeson as A-import qualified Data.Aeson.Types as A import Data.ByteString.Lazy (ByteString)+import Data.Morpheus.Client.Fetch.RequestType (Request (Request), RequestType (RequestArgs), processResponse, toRequest) import Data.Morpheus.Client.Internal.Types   ( FetchError (..),   )-import Data.Morpheus.Client.Schema.JSON.Types-  ( JSONResponse (..),-  )-import Data.Morpheus.Types.IO-  ( GQLRequest (..),-  )-import Data.Morpheus.Types.Internal.AST-  ( FieldName,-  )-import Data.Text-  ( pack,-  ) import Relude hiding (ByteString) -fixVars :: A.Value -> Maybe A.Value-fixVars x-  | x == A.emptyArray = Nothing-  | otherwise = Just x+decodeResponse :: FromJSON a => ByteString -> Either (FetchError a) a+decodeResponse = (first FetchErrorParseFailure . eitherDecode) >=> processResponse -class Fetch a where+class (RequestType a, ToJSON (Args a), FromJSON a) => Fetch a where   type Args a :: Type-  __fetch ::-    (Monad m, Show a, ToJSON (Args a), FromJSON a) =>-    String ->-    FieldName ->-    (ByteString -> m ByteString) ->-    Args a ->-    m (Either (FetchError a) a)-  __fetch strQuery opName trans vars = ((first FetchErrorParseFailure . eitherDecode) >=> processResponse) <$> trans (encode gqlReq)+  fetch :: Monad m => (ByteString -> m ByteString) -> Args a -> m (Either (FetchError a) a)++instance (RequestType a, ToJSON (Args a), FromJSON a) => Fetch a where+  type Args a = RequestArgs a+  fetch f args = decodeResponse <$> f (encode $ toRequest request)     where-      gqlReq = GQLRequest {operationName = Just opName, query = pack strQuery, variables = fixVars (toJSON vars)}-      --------------------------------------------------------------      processResponse JSONResponse {responseData = Just x, responseErrors = []} = Right x-      processResponse JSONResponse {responseData = Nothing, responseErrors = []} = Left FetchErrorNoResult-      processResponse JSONResponse {responseData = result, responseErrors = (x : xs)} = Left $ FetchErrorProducedErrors (x :| xs) result-  fetch :: (Monad m, FromJSON a) => (ByteString -> m ByteString) -> Args a -> m (Either (FetchError a) a)+      request :: Request a+      request = Request args
+ src/Data/Morpheus/Client/Fetch/GQLClient.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Fetch.GQLClient+  ( GQLClient (..),+    withHeaders,+    Headers,+    Header,+  )+where++import Relude hiding (ByteString)++type Headers = Map Text Text++type Header = (Text, Text)++data GQLClient = GQLClient+  { clientHeaders :: Headers,+    clientURI :: String+  }++instance IsString GQLClient where+  fromString clientURI =+    GQLClient+      { clientURI,+        clientHeaders = fromList [("Content-Type", "application/json")]+      }++withHeaders :: GQLClient -> [Header] -> GQLClient+withHeaders GQLClient {..} headers = GQLClient {clientHeaders = clientHeaders <> fromList headers, ..}
+ src/Data/Morpheus/Client/Fetch/Http.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Fetch.Http+  ( httpRequest,+  )+where++import Data.Aeson (FromJSON, ToJSON, encode)+import qualified Data.ByteString.Char8 as L+import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.Map as M+import Data.Morpheus.Client.Fetch.GQLClient (Header, Headers)+import Data.Morpheus.Client.Fetch.RequestType+  ( Request,+    RequestType (RequestArgs),+    decodeResponse,+    toRequest,+  )+import Data.Morpheus.Client.Internal.Types (GQLClientResult)+import qualified Data.Text as T+import Network.HTTP.Req+  ( POST (..),+    ReqBodyLbs (ReqBodyLbs),+    defaultHttpConfig,+    header,+    lbsResponse,+    req,+    responseBody,+    runReq,+    useURI,+  )+import qualified Network.HTTP.Req as R (Option)+import Relude hiding (ByteString)+import Text.URI (URI)++withHeader :: Header -> R.Option scheme+withHeader (k, v) = header (L.pack $ T.unpack k) (L.pack $ T.unpack v)++setHeaders :: Headers -> R.Option scheme+setHeaders = foldMap withHeader . M.toList++post :: URI -> ByteString -> Headers -> IO ByteString+post uri body headers = case useURI uri of+  Nothing -> fail ("Invalid Endpoint: " <> show uri <> "!")+  (Just (Left (u, o))) -> responseBody <$> runReq defaultHttpConfig (req POST u (ReqBodyLbs body) lbsResponse (o <> setHeaders headers))+  (Just (Right (u, o))) -> responseBody <$> runReq defaultHttpConfig (req POST u (ReqBodyLbs body) lbsResponse (o <> setHeaders headers))++httpRequest :: (FromJSON a, RequestType a, ToJSON (RequestArgs a)) => URI -> Request a -> Headers -> IO (GQLClientResult a)+httpRequest uri r h = decodeResponse <$> post uri (encode $ toRequest r) h
+ src/Data/Morpheus/Client/Fetch/RequestType.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE ConstrainedClassMethods #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Fetch.RequestType+  ( toRequest,+    decodeResponse,+    Request (..),+    RequestType (..),+    processResponse,+    ClientTypeConstraint,+    isSubscription,+  )+where++import Data.Aeson+  ( FromJSON,+    ToJSON (..),+    eitherDecode,+  )+import qualified Data.Aeson as A+import qualified Data.Aeson.Types as A+import Data.ByteString.Lazy (ByteString)+import Data.Morpheus.Client.Internal.Types+  ( FetchError (..),+  )+import Data.Morpheus.Client.Schema.JSON.Types+  ( JSONResponse (..),+  )+import Data.Morpheus.Types.IO+  ( GQLRequest (..),+  )+import Data.Morpheus.Types.Internal.AST+  ( FieldName,+    OperationType (Subscription),+  )+import Data.Text+  ( pack,+  )+import Relude hiding (ByteString)++fixVars :: A.Value -> Maybe A.Value+fixVars x+  | x == A.emptyArray = Nothing+  | otherwise = Just x++toRequest :: (RequestType a, ToJSON (RequestArgs a)) => Request a -> GQLRequest+toRequest r@Request {requestArgs} =+  ( GQLRequest+      { operationName = Just (__name r),+        query = pack (__query r),+        variables = fixVars (toJSON requestArgs)+      }+  )++decodeResponse :: FromJSON a => ByteString -> Either (FetchError a) a+decodeResponse = (first FetchErrorParseFailure . eitherDecode) >=> processResponse++processResponse :: JSONResponse a -> Either (FetchError a) a+processResponse JSONResponse {responseData = Just x, responseErrors = []} = Right x+processResponse JSONResponse {responseData = Nothing, responseErrors = []} = Left FetchErrorNoResult+processResponse JSONResponse {responseData = result, responseErrors = (x : xs)} = Left $ FetchErrorProducedErrors (x :| xs) result++type ClientTypeConstraint (a :: Type) = (RequestType a, ToJSON (RequestArgs a), FromJSON a)++class RequestType a where+  type RequestArgs a :: Type+  __name :: f a -> FieldName+  __query :: f a -> String+  __type :: f a -> OperationType++newtype Request (a :: Type) = Request {requestArgs :: RequestArgs a}++isSubscription :: RequestType a => Request a -> Bool+isSubscription x = __type x == Subscription
+ src/Data/Morpheus/Client/Fetch/ResponseStream.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Fetch.ResponseStream+  ( request,+    forEach,+    single,+    ResponseStream,+    GQLClient,+    withHeaders,+  )+where++import Control.Monad.IO.Unlift (MonadUnliftIO)+import Data.Morpheus.Client.Fetch (Args)+import Data.Morpheus.Client.Fetch.GQLClient+import Data.Morpheus.Client.Fetch.Http (httpRequest)+import Data.Morpheus.Client.Fetch.RequestType+  ( ClientTypeConstraint,+    Request (..),+    isSubscription,+  )+import Data.Morpheus.Client.Fetch.WebSockets+  ( endSession,+    receiveResponse,+    responseStream,+    sendInitialRequest,+    sendRequest,+    useWS,+  )+import Data.Morpheus.Client.Internal.Types+import qualified Data.Text as T+import Relude hiding (ByteString)+import Text.URI (URI, mkURI)++parseURI :: MonadFail m => String -> m URI+parseURI url = maybe (fail ("Invalid Endpoint: " <> show url <> "!")) pure (mkURI (T.pack url))++requestSingle :: ResponseStream a -> IO (Either (FetchError a) a)+requestSingle ResponseStream {..}+  | isSubscription _req = useWS _uri _headers wsApp+  | otherwise = httpRequest _uri _req _headers+  where+    wsApp conn = do+      let sid = "0243134"+      sendInitialRequest conn+      sendRequest conn sid _req+      x <- receiveResponse conn+      endSession conn sid+      pure x++requestMany :: (MonadIO m, MonadUnliftIO m, MonadFail m) => (GQLClientResult a -> m ()) -> ResponseStream a -> m ()+requestMany f ResponseStream {..}+  | isSubscription _req = useWS _uri _headers appWS+  | otherwise = liftIO (httpRequest _uri _req _headers) >>= f+  where+    appWS conn = do+      let sid = "0243134"+      sendInitialRequest conn+      sendRequest conn sid _req+      traverse_ (>>= f) (responseStream conn)+      endSession conn sid++-- PUBLIC API+data ResponseStream a = ClientTypeConstraint a =>+  ResponseStream+  { _req :: Request a,+    _uri :: URI,+    _headers :: Headers+    -- _wsConnection :: Connection+  }++request :: (ClientTypeConstraint a, MonadFail m) => GQLClient -> Args a -> m (ResponseStream a)+request GQLClient {clientURI, clientHeaders} requestArgs = do+  _uri <- parseURI clientURI+  let _req = Request {requestArgs}+  pure ResponseStream {_req, _uri, _headers = clientHeaders}++-- | returns first response from the server+single :: MonadIO m => ResponseStream a -> m (GQLClientResult a)+single = liftIO . requestSingle++-- | returns loop listening subscription events forever. if you want to run it in background use `forkIO`+forEach :: (MonadIO m, MonadUnliftIO m, MonadFail m) => (GQLClientResult a -> m ()) -> ResponseStream a -> m ()+forEach = requestMany
+ src/Data/Morpheus/Client/Fetch/WebSockets.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Morpheus.Client.Fetch.WebSockets+  ( useWS,+    sendInitialRequest,+    responseStream,+    sendRequest,+    receiveResponse,+    endSession,+  )+where++import Control.Monad.IO.Unlift (MonadUnliftIO (..))+import qualified Data.Aeson as A+import qualified Data.ByteString.Char8 as BS+import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.Map as M+import Data.Morpheus.Client.Fetch.GQLClient (Headers)+import Data.Morpheus.Client.Fetch.RequestType (Request, RequestType (..), processResponse, toRequest)+import Data.Morpheus.Client.Internal.Types (FetchError (..), GQLClientResult)+import Data.Morpheus.Client.Schema.JSON.Types (JSONResponse (..))+import Data.Morpheus.Subscriptions.Internal (ApolloSubscription (..))+import qualified Data.Text as T+import Network.WebSockets.Client (runClientWith)+import Network.WebSockets.Connection (Connection, defaultConnectionOptions, receiveData, sendTextData)+import Relude hiding (ByteString)+import Text.URI+  ( Authority (..),+    RText,+    RTextLabel (..),+    URI (..),+    unRText,+  )+import Wuss (runSecureClientWith)++handleHost :: Text -> String+handleHost "localhost" = "127.0.0.1"+handleHost x = T.unpack x++toPort :: Maybe Word -> Int+toPort Nothing = 80+toPort (Just x) = fromIntegral x++getPath :: Maybe (Bool, NonEmpty (RText 'PathPiece)) -> String+getPath (Just (_, h :| t)) = T.unpack $ T.intercalate "/" $ fmap unRText (h : t)+getPath _ = ""++data WebSocketSettings = WebSocketSettings+  { isSecure :: Bool,+    port :: Int,+    host :: String,+    path :: String,+    headers :: Headers+  }+  deriving (Show)++parseProtocol :: MonadFail m => Text -> m Bool+parseProtocol "ws" = pure False+parseProtocol "wss" = pure True+parseProtocol p = fail $ "unsupported protocol" <> show p++getWebsocketURI :: MonadFail m => URI -> Headers -> m WebSocketSettings+getWebsocketURI URI {uriScheme = Just scheme, uriAuthority = Right Authority {authHost, authPort}, uriPath} headers = do+  isSecure <- parseProtocol $ unRText scheme+  pure+    WebSocketSettings+      { isSecure,+        host = handleHost $ unRText authHost,+        port = toPort authPort,+        path = getPath uriPath,+        headers+      }+getWebsocketURI uri _ = fail ("Invalid Endpoint: " <> show uri <> "!")++toHeader :: IsString a => (Text, Text) -> (a, BS.ByteString)+toHeader (x, y) = (fromString $ T.unpack x, BS.pack $ T.unpack y)++_useWS :: WebSocketSettings -> (Connection -> IO a) -> IO a+_useWS WebSocketSettings {isSecure, ..} app+  | isSecure = runSecureClientWith host 443 path options wsHeaders app+  | otherwise = runClientWith host port path options wsHeaders app+  where+    wsHeaders = map toHeader $ M.toList headers+    options = defaultConnectionOptions++useWS :: (MonadFail m, MonadIO m, MonadUnliftIO m) => URI -> Headers -> (Connection -> m a) -> m a+useWS uri headers app = do+  wsURI <- getWebsocketURI uri headers+  withRunInIO $ \runInIO -> _useWS wsURI (runInIO . app)++processMessage :: ApolloSubscription (JSONResponse a) -> GQLClientResult a+processMessage ApolloSubscription {apolloPayload = Just payload} = processResponse payload+processMessage ApolloSubscription {} = Left (FetchErrorParseFailure "empty message")++decodeMessage :: A.FromJSON a => ByteString -> GQLClientResult a+decodeMessage = (first FetchErrorParseFailure . A.eitherDecode) >=> processMessage++initialMessage :: ApolloSubscription ()+initialMessage = ApolloSubscription {apolloType = "connection_init", apolloPayload = Nothing, apolloId = Nothing}++encodeRequestMessage :: (RequestType a, A.ToJSON (RequestArgs a)) => Text -> Request a -> ByteString+encodeRequestMessage uid r =+  A.encode+    ApolloSubscription+      { apolloPayload = Just (toRequest r),+        apolloType = "start",+        apolloId = Just uid+      }++endMessage :: Text -> ApolloSubscription ()+endMessage uid = ApolloSubscription {apolloType = "stop", apolloPayload = Nothing, apolloId = Just uid}++endSession :: MonadIO m => Connection -> Text -> m ()+endSession conn uid = liftIO $ sendTextData conn $ A.encode $ endMessage uid++receiveResponse :: MonadIO m => A.FromJSON a => Connection -> m (GQLClientResult a)+receiveResponse conn = liftIO $ do+  message <- receiveData conn+  pure $ decodeMessage message++-- returns infinite number of responses+responseStream :: (A.FromJSON a, MonadIO m) => Connection -> [m (GQLClientResult a)]+responseStream conn = getResponse : responseStream conn+  where+    getResponse = receiveResponse conn++sendRequest :: (RequestType a, A.ToJSON (RequestArgs a), MonadIO m) => Connection -> Text -> Request a -> m ()+sendRequest conn uid r = liftIO $ sendTextData conn (encodeRequestMessage uid r)++sendInitialRequest :: MonadIO m => Connection -> m ()+sendInitialRequest conn = liftIO $ sendTextData conn (A.encode initialMessage)
src/Data/Morpheus/Client/Internal/TH.hs view
@@ -124,18 +124,18 @@  isTypeDeclared :: ClientTypeDefinition -> Q Bool isTypeDeclared clientDef = do-    let name = mkTypeName clientDef-    m <- lookupTypeName (show name)-    case m of-        Nothing -> pure False-        _ -> pure True+  let name = mkTypeName clientDef+  m <- lookupTypeName (show name)+  case m of+    Nothing -> pure False+    _ -> pure True  hasInstance :: Name -> ClientTypeDefinition -> Q Bool hasInstance typeClass clientDef = do-    isInstance typeClass [ConT (mkTypeName clientDef)]+  isInstance typeClass [ConT (mkTypeName clientDef)]  mkTypeName :: ClientTypeDefinition -> Name-mkTypeName ClientTypeDefinition{clientTypeName = TypeNameTH namespace typeName} =-    toType typeName+mkTypeName ClientTypeDefinition {clientTypeName = TypeNameTH namespace typeName} =+  toType typeName   where     toType = toName . camelCaseTypeName namespace
src/Data/Morpheus/Client/Internal/Types.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE NoImplicitPrelude #-}  module Data.Morpheus.Client.Internal.Types@@ -9,6 +11,7 @@     FetchError (..),     SchemaSource (..),     ExecutableSource,+    GQLClientResult,   ) where @@ -18,6 +21,7 @@     FieldDefinition,     FieldName,     GQLErrors,+    OperationType,     TypeKind,     TypeName,     VALID,@@ -45,7 +49,8 @@  data FetchDefinition = FetchDefinition   { rootTypeName :: TypeNameTH,-    clientArgumentsTypeName :: Maybe TypeNameTH+    clientArgumentsTypeName :: Maybe TypeNameTH,+    fetchOperationType :: OperationType   }   deriving (Show) @@ -61,3 +66,5 @@   deriving (Show, Eq)  type ExecutableSource = Text++type GQLClientResult (a :: Type) = (Either (FetchError a) a)
src/Data/Morpheus/Client/QuasiQuoter.hs view
@@ -21,7 +21,7 @@     things       <> " are not supported by the GraphQL QuasiQuoter" --- | QuasiQuoter to insert multiple lines of text in Haskell +-- | QuasiQuoter to insert multiple lines of text in Haskell raw :: QuasiQuoter raw =   QuasiQuoter
src/Data/Morpheus/Client/Schema/JSON/Parse.hs view
@@ -36,9 +36,6 @@   ( empty,     fromElems,   )-import qualified Data.Morpheus.Types.Internal.AST as AST-  ( Schema,-  ) import Data.Morpheus.Types.Internal.AST   ( ANY,     ArgumentDefinition (..),@@ -65,6 +62,9 @@     mkUnionContent,     msg,     toAny,+  )+import qualified Data.Morpheus.Types.Internal.AST as AST+  ( Schema,   ) import Relude hiding   ( ByteString,
src/Data/Morpheus/Client/Schema/JSON/Types.hs view
@@ -41,7 +41,7 @@     mutationType :: Maybe TypeRef,     subscriptionType :: Maybe TypeRef     -- TODO: directives-    --directives: [__Directive]+    -- directives: [__Directive]   }   deriving (Generic, Show, FromJSON) 
src/Data/Morpheus/Client/Transform/Global.hs view
@@ -45,17 +45,17 @@ toArgumentsType cName variables   | null variables = Nothing   | otherwise =-    Just-      ClientTypeDefinition-        { clientTypeName = TypeNameTH [] cName,-          clientKind = KindInputObject,-          clientCons =-            [ ClientConstructorDefinition-                { cName,-                  cFields = toFieldDefinition <$> toList variables-                }-            ]-        }+      Just+        ClientTypeDefinition+          { clientTypeName = TypeNameTH [] cName,+            clientKind = KindInputObject,+            clientCons =+              [ ClientConstructorDefinition+                  { cName,+                    cFields = toFieldDefinition <$> toList variables+                  }+              ]+          }  toFieldDefinition :: Variable RAW -> FieldDefinition ANY VALID toFieldDefinition Variable {variableName, variableType} =
src/Data/Morpheus/Client/Transform/Local.hs view
@@ -76,10 +76,11 @@ toLocalDefinitions request schema = do   validOperation <- validateRequest clientConfig schema request   flip runReaderT (schema, operationArguments $ operation request) $-    runConverter $ genOperation validOperation+    runConverter $+      genOperation validOperation  genOperation :: Operation VALID -> Converter (FetchDefinition, [ClientTypeDefinition])-genOperation op@Operation {operationName, operationSelection} = do+genOperation op@Operation {operationName, operationSelection, operationType} = do   (schema, varDefs) <- asks id   datatype <- getOperationDataType op schema   let argumentsType = toArgumentsType (getOperationName operationName <> "Args") varDefs@@ -92,7 +93,8 @@   pure     ( FetchDefinition         { clientArgumentsTypeName = fmap clientTypeName argumentsType,-          rootTypeName = clientTypeName rootType+          rootTypeName = clientTypeName rootType,+          fetchOperationType = operationType         },       rootType : (localTypes <> maybeToList argumentsType)     )@@ -160,8 +162,8 @@           { clientTypeName = TypeNameTH path (typeFrom [] dType),             clientCons,             clientKind = KindUnion-          } :-        concat subTypes+          }+          : concat subTypes       )  getVariantType :: [FieldName] -> UnionTag -> Converter (ClientConstructorDefinition, [ClientTypeDefinition])@@ -185,14 +187,14 @@       toFieldDef :: TypeContent TRUE ANY VALID -> Converter (FieldDefinition OUT VALID)       toFieldDef _         | selectionName == "__typename" =-          pure-            FieldDefinition-              { fieldName = "__typename",-                fieldDescription = Nothing,-                fieldType = mkTypeRef "String",-                fieldDirectives = empty,-                fieldContent = Nothing-              }+            pure+              FieldDefinition+                { fieldName = "__typename",+                  fieldDescription = Nothing,+                  fieldType = mkTypeRef "String",+                  fieldDirectives = empty,+                  fieldContent = Nothing+                }       toFieldDef DataObject {objectFields} = selectBy selError selectionName objectFields       toFieldDef DataInterface {interfaceFields} = selectBy selError selectionName interfaceFields       toFieldDef dt = throwError (compileError $ "Type should be output Object \"" <> msg (show dt))
test/Case/ResponseTypes/Test.hs view
@@ -141,7 +141,8 @@     ( FetchErrorProducedErrors         ( ("Failure" `at` Position {line = 3, column = 7})             `withPath` ["queryTypeName"]-            `custom` "QUERY_BAD" :| []+            `custom` "QUERY_BAD"+            :| []         )         (Just ErrorsWithType {queryTypeName = Just "TestQuery"})     )@@ -163,7 +164,7 @@             `withPath` [ "queryTypeName",                          PropIndex 0                        ]-              :| []+            :| []         )         ( Just             TestErrorsQuery