blockfrost-client 0.12.0.0 → 0.13.0.0
raw patch · 4 files changed
+115/−33 lines, 4 filesdep +retrydep −directorydep ~blockfrost-client-corePVP ok
version bump matches the API change (PVP)
Dependencies added: retry
Dependencies removed: directory
Dependency ranges changed: blockfrost-client-core
API changes (from Hackage documentation)
- Blockfrost.Client.Types: BlockfrostFatal :: Text -> BlockfrostError
- Blockfrost.Client.Types: type ClientConfig = (ClientEnv, Project)
+ Blockfrost.Client.Types: data ClientConfig
+ Blockfrost.Client.Types: defaultRetryJudge :: RetryStatus -> BlockfrostError -> RetryAction
+ Blockfrost.Client.Types: defaultRetryPolicy :: RetryPolicy
+ Blockfrost.Client.Types: endlessRetryPolicy :: RetryPolicy
- Blockfrost.Client: ipfsAdd :: (MonadError BlockfrostError m, MonadBlockfrost m) => FilePath -> m IPFSAdd
+ Blockfrost.Client: ipfsAdd :: MonadBlockfrost m => FilePath -> m IPFSAdd
- Blockfrost.Client.IPFS: ipfsAdd :: (MonadError BlockfrostError m, MonadBlockfrost m) => FilePath -> m IPFSAdd
+ Blockfrost.Client.IPFS: ipfsAdd :: MonadBlockfrost m => FilePath -> m IPFSAdd
- Blockfrost.Client.Types: BlockfrostError :: Text -> BlockfrostError
+ Blockfrost.Client.Types: BlockfrostError :: Status -> Text -> BlockfrostError
Files
- CHANGELOG.md +28/−0
- blockfrost-client.cabal +3/−3
- src/Blockfrost/Client/IPFS.hs +3/−12
- src/Blockfrost/Client/Types.hs +81/−18
CHANGELOG.md view
@@ -1,3 +1,31 @@+# Version [0.13.0.0](https://github.com/blockfrost/blockfrost-haskell/compare/client-0.12.0.0...client-0.13.0.0) (2026-04-15)++* Changes+ * Retry logic added++ Client will retry most transient errors like connection timeouts,+ up to 5 times with exponential backoff. This can be customized+ via `clientConfigRetryPolicy` field of `ClientConfig`. See [`Control.Retry`](https://hackage.haskell.org/package/retry/docs/Control-Retry.html#g:5)+ for available options.++ * `type ClientConfig = (ClientEnv, Project)` is now++ ```+ data ClientConfig =+ ClientConfig+ { clientConfigClientEnv :: ClientEnv+ , clientConfigProject :: Project+ , clientConfigRetryPolicy :: RetryPolicy+ , clientConfigRetryJudge :: RetryStatus -> BlockfrostError -> RetryAction+ }+ ```+ last two fields are populated with `defaultRetryPolicy` and `defaultRetryJudge`.++ * `ipfsAdd`+ * no longer throws `BlockfrostError "File not found"`+ * no longer checks if the file exists+ * no longer prints `"Uploading: <filename>"` message to stdout+ # Version [0.12.0.0](https://github.com/blockfrost/blockfrost-haskell/compare/client-0.11.0.0...client-0.12.0.0) (2026-02-02) * Changes
blockfrost-client.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: blockfrost-client-version: 0.12.0.0+version: 0.13.0.0 synopsis: blockfrost.io basic client description: Simple Blockfrost clients for use with transformers or mtl homepage: https://github.com/blockfrost/blockfrost-haskell@@ -70,13 +70,13 @@ , Blockfrost.Client.NutLink build-depends: base >= 4.7 && < 5 , blockfrost-api >= 0.15- , blockfrost-client-core ^>= 0.7+ , blockfrost-client-core ^>= 0.8 , bytestring- , directory , data-default , filepath , text , mtl+ , retry >= 0.9 && < 0.10 , servant >= 0.18 && < 0.21 , servant-client , servant-client-core
src/Blockfrost/Client/IPFS.hs view
@@ -15,28 +15,19 @@ import Blockfrost.API import Blockfrost.Client.Types import Blockfrost.Types-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (liftIO) import Data.ByteString.Lazy (ByteString) import Data.Text (Text) import qualified Data.Text-import qualified System.Directory import qualified System.FilePath ipfsAdd_ :: MonadBlockfrost m => Project -> (ByteString, Form) -> m IPFSAdd ipfsAdd_ = _add . ipfsClient -- | Add a file or directory to IPFS-ipfsAdd :: (MonadError BlockfrostError m, MonadBlockfrost m) => FilePath -> m IPFSAdd+ipfsAdd :: MonadBlockfrost m => FilePath -> m IPFSAdd ipfsAdd fp = do- hasFile <- liftIO $ System.Directory.doesFileExist fp- if hasFile- then do- liftIO $ putStrLn $ "Uploading: " ++ fp- let fn = Data.Text.pack $ System.FilePath.takeBaseName fp- go (\proj -> ipfsAdd_ proj ("suchBoundary", (Form fn fp)))- else- throwError (BlockfrostError "No such file")+ let fn = Data.Text.pack $ System.FilePath.takeBaseName fp+ go (\proj -> ipfsAdd_ proj ("suchBoundary", (Form fn fp))) ipfsGateway_ :: MonadBlockfrost m => Project -> Text -> m IPFSData ipfsGateway_ = _gateway . ipfsClient
src/Blockfrost/Client/Types.hs view
@@ -2,11 +2,17 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE Rank2Types #-} module Blockfrost.Client.Types ( BlockfrostClient , BlockfrostError (..) , ClientConfig+ , defaultRetryPolicy+ , endlessRetryPolicy+ , defaultRetryJudge , runBlockfrost , apiClient , api0Client@@ -30,6 +36,7 @@ import Control.Monad.Except import Control.Monad.Reader+import Control.Retry import Data.Default import Servant.API.Generic@@ -39,8 +46,26 @@ import Blockfrost.API import Blockfrost.Client.Core -type ClientConfig = (ClientEnv, Project)+data ClientConfig =+ ClientConfig+ { clientConfigClientEnv :: ClientEnv+ , clientConfigProject :: Project+ , clientConfigRetryPolicy :: RetryPolicy+ , clientConfigRetryJudge :: RetryStatus -> BlockfrostError -> RetryAction+ } +defaultRetryPolicy :: RetryPolicy+defaultRetryPolicy = exponentialBackoff (1000 * 1000) <> limitRetries 5++endlessRetryPolicy :: RetryPolicy+endlessRetryPolicy = exponentialBackoff (1000 * 1000)++defaultRetryJudge+ :: RetryStatus+ -> BlockfrostError+ -> RetryAction+defaultRetryJudge _retryStatus err = retriableError err+ newtype BlockfrostClientT m a = BlockfrostClientT { unBlockfrostClientT :: ExceptT BlockfrostError@@ -62,13 +87,33 @@ instance MonadIO m => MonadBlockfrost (BlockfrostClientT m) where liftBlockfrostClient act = BlockfrostClientT $ do- (env, _proj) <- ask- liftIO (runClientM act env)- >>= either- (throwError . fromServantClientError)- pure+ clientConfig <- ask+ liftIO+ $ withRetry+ clientConfig+ $ runClientM act (clientConfigClientEnv clientConfig)+ >>= either+ (throwError . fromServantClientError)+ pure getConf = BlockfrostClientT ask +withRetry+ :: ClientConfig+ -> IO (Either ClientError a)+ -> IO (Either ClientError a)+withRetry ClientConfig{..} act =+ retryingDynamic+ clientConfigRetryPolicy+ (\retryStatus -> \case+ Right{} -> pure DontRetry+ Left err ->+ pure+ $ clientConfigRetryJudge+ retryStatus+ (fromServantClientError err)+ )+ (const act)+ instance MonadBlockfrost ClientM where liftBlockfrostClient = id getConf = newClientConfig@@ -76,11 +121,16 @@ instance MonadBlockfrost IO where liftBlockfrostClient act = getConf- >>= \(env, _prj) ->- runClientM act env- >>= either- (error . show)- pure+ >>= \clientConfig ->+ withRetry+ clientConfig+ ( runClientM+ act+ (clientConfigClientEnv clientConfig)+ )+ >>= either+ (error . show)+ pure getConf = newClientConfig apiClient@@ -110,24 +160,37 @@ -> BlockfrostClientT m a -> m (Either BlockfrostError a) runBlockfrostClientT proj act = do- env <- liftIO $ newEnvByProject proj- flip runReaderT (env, proj)+ cc <- liftIO $ mkClientConfig proj+ flip runReaderT cc $ runExceptT $ unBlockfrostClientT act -- | Build default `ClientConfig` using BLOCKFROST_TOKEN_PATH environment variable newClientConfig :: MonadIO m => m ClientConfig-newClientConfig = liftIO $ do- prj <- projectFromEnv- env <- newEnvByProject prj- pure (env, prj)+newClientConfig =+ liftIO+ $ projectFromEnv >>= mkClientConfig +mkClientConfig+ :: MonadIO m+ => Project+ -> m ClientConfig+mkClientConfig prj = do+ env <- liftIO $ newEnvByProject prj+ pure+ $ ClientConfig+ { clientConfigClientEnv = env+ , clientConfigProject = prj+ , clientConfigRetryPolicy = defaultRetryPolicy+ , clientConfigRetryJudge = defaultRetryJudge+ }+ -- | Helper go :: MonadBlockfrost m => (Project -> m a) -> m a-go act = getConf >>= act . snd+go act = getConf >>= act . clientConfigProject -- Until mtl > 2.2.2 -- https://github.com/haskell/mtl/pull/66