git.iko.soy/rowtype-yoga/purescript-yoga-om
on main help sign in
Add Yoga.Om.Retry with recovering and repeating
247d662Mark
spago.lock+210−1
@@ -8,6 +8,12 @@
8 {
8 {
9 "aff": ">=7.0.0 <8.0.0"
9 "aff": ">=7.0.0 <8.0.0"
10 },
10 },
11+ {
12+ "aff-retry": ">=2.0.0 <3.0.0"
13+ },
14+ {
15+ "arrays": ">=7.0.0 <8.0.0"
16+ },
11 {
17 {
12 "avar": ">=5.0.0 <6.0.0"
18 "avar": ">=5.0.0 <6.0.0"
13 },
19 },
@@ -38,12 +44,18 @@
38 {
44 {
39 "newtype": ">=5.0.0 <6.0.0"
45 "newtype": ">=5.0.0 <6.0.0"
40 },
46 },
47+ {
48+ "numbers": ">=9.0.0 <10.0.0"
49+ },
41 {
50 {
42 "parallel": ">=6.0.0 <8.0.0"
51 "parallel": ">=6.0.0 <8.0.0"
43 },
52 },
44 {
53 {
45 "prelude": ">=6.0.0 <7.0.0"
54 "prelude": ">=6.0.0 <7.0.0"
46 },
55 },
56+ {
57+ "random": ">=6.0.0 <7.0.0"
58+ },
47 {
59 {
48 "record": ">=4.0.0 <5.0.0"
60 "record": ">=4.0.0 <5.0.0"
49 },
61 },
@@ -74,7 +86,14 @@
74 ]
86 ]
75 },
87 },
76 "test": {
88 "test": {
77− "dependencies": []
89+ "dependencies": [
90+ {
91+ "refs": ">=6.0.0 <7.0.0"
92+ },
93+ {
94+ "spec": ">=7.0.0 <8.0.0"
95+ }
96+ ]
78 }
97 }
79 }
98 }
80 },
99 },
@@ -599,6 +618,36 @@
599 "unsafe-coerce"
618 "unsafe-coerce"
600 ]
619 ]
601 },
620 },
621+ "aff-retry": {
622+ "type": "registry",
623+ "version": "2.0.0",
624+ "integrity": "sha256-rKZNcP4IKSyBFQBVzrCvE0ULoJt0N/uDLLJ0dPotr4k=",
625+ "dependencies": [
626+ "aff",
627+ "arrays",
628+ "datetime",
629+ "effect",
630+ "either",
631+ "exceptions",
632+ "integers",
633+ "maybe",
634+ "newtype",
635+ "numbers",
636+ "prelude",
637+ "random",
638+ "transformers"
639+ ]
640+ },
641+ "ansi": {
642+ "type": "registry",
643+ "version": "7.0.0",
644+ "integrity": "sha256-tOUaXJFzPSfBVAqLI8hc+Tz2M1rg16cXqyHaK/3SmFg=",
645+ "dependencies": [
646+ "foldable-traversable",
647+ "lists",
648+ "prelude"
649+ ]
650+ },
602 "arrays": {
651 "arrays": {
603 "type": "registry",
652 "type": "registry",
604 "version": "7.3.0",
653 "version": "7.3.0",
@@ -646,6 +695,20 @@
646 "tuples"
695 "tuples"
647 ]
696 ]
648 },
697 },
698+ "catenable-lists": {
699+ "type": "registry",
700+ "version": "7.0.0",
701+ "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=",
702+ "dependencies": [
703+ "control",
704+ "foldable-traversable",
705+ "lists",
706+ "maybe",
707+ "prelude",
708+ "tuples",
709+ "unfoldable"
710+ ]
711+ },
649 "console": {
712 "console": {
650 "type": "registry",
713 "type": "registry",
651 "version": "6.1.0",
714 "version": "6.1.0",
@@ -794,6 +857,37 @@
794 "tuples"
857 "tuples"
795 ]
858 ]
796 },
859 },
860+ "fork": {
861+ "type": "registry",
862+ "version": "6.0.0",
863+ "integrity": "sha256-0keBVeZZg1eCZEy/En1CohVW829gQUqKtqBNIRa93/A=",
864+ "dependencies": [
865+ "aff",
866+ "prelude",
867+ "transformers"
868+ ]
869+ },
870+ "free": {
871+ "type": "registry",
872+ "version": "7.1.0",
873+ "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=",
874+ "dependencies": [
875+ "catenable-lists",
876+ "control",
877+ "distributive",
878+ "either",
879+ "exists",
880+ "foldable-traversable",
881+ "invariant",
882+ "lazy",
883+ "maybe",
884+ "prelude",
885+ "tailrec",
886+ "transformers",
887+ "tuples",
888+ "unsafe-coerce"
889+ ]
890+ },
797 "functions": {
891 "functions": {
798 "type": "registry",
892 "type": "registry",
799 "version": "6.0.0",
893 "version": "6.0.0",
@@ -924,6 +1018,23 @@
924 "prelude"
1018 "prelude"
925 ]
1019 ]
926 },
1020 },
1021+ "mmorph": {
1022+ "type": "registry",
1023+ "version": "7.0.0",
1024+ "integrity": "sha256-Cfltaq+tfYjuSoSBN/b8cSQZbvZTqIRs6ebQewbIkGs=",
1025+ "dependencies": [
1026+ "bifunctors",
1027+ "either",
1028+ "free",
1029+ "functors",
1030+ "identity",
1031+ "maybe",
1032+ "newtype",
1033+ "prelude",
1034+ "transformers",
1035+ "tuples"
1036+ ]
1037+ },
927 "newtype": {
1038 "newtype": {
928 "type": "registry",
1039 "type": "registry",
929 "version": "5.0.0",
1040 "version": "5.0.0",
@@ -946,6 +1057,16 @@
946 "unfoldable"
1057 "unfoldable"
947 ]
1058 ]
948 },
1059 },
1060+ "now": {
1061+ "type": "registry",
1062+ "version": "6.0.0",
1063+ "integrity": "sha256-mTyG4s8tps3XIf0jjV/lME2/QlwMzrQly2wqfSSUM3o=",
1064+ "dependencies": [
1065+ "datetime",
1066+ "effect",
1067+ "prelude"
1068+ ]
1069+ },
949 "numbers": {
1070 "numbers": {
950 "type": "registry",
1071 "type": "registry",
951 "version": "9.0.1",
1072 "version": "9.0.1",
@@ -1010,6 +1131,27 @@
1010 "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1131 "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1011 "dependencies": []
1132 "dependencies": []
1012 },
1133 },
1134+ "pipes": {
1135+ "type": "registry",
1136+ "version": "8.0.0",
1137+ "integrity": "sha256-dtUNpG8BGVr7pu6mQZyAnUAftUHgNS7f0QH3Kw5UTpQ=",
1138+ "dependencies": [
1139+ "aff",
1140+ "control",
1141+ "effect",
1142+ "either",
1143+ "foldable-traversable",
1144+ "identity",
1145+ "lists",
1146+ "maybe",
1147+ "mmorph",
1148+ "newtype",
1149+ "prelude",
1150+ "tailrec",
1151+ "transformers",
1152+ "tuples"
1153+ ]
1154+ },
1013 "prelude": {
1155 "prelude": {
1014 "type": "registry",
1156 "type": "registry",
1015 "version": "6.0.1",
1157 "version": "6.0.1",
@@ -1031,6 +1173,16 @@
1031 "tuples"
1173 "tuples"
1032 ]
1174 ]
1033 },
1175 },
1176+ "random": {
1177+ "type": "registry",
1178+ "version": "6.0.0",
1179+ "integrity": "sha256-7sZRo6P9UIiXDZ9s2QL9T8h3wlIwLdQrR4+oZKOijQw=",
1180+ "dependencies": [
1181+ "effect",
1182+ "integers",
1183+ "prelude"
1184+ ]
1185+ },
1034 "record": {
1186 "record": {
1035 "type": "registry",
1187 "type": "registry",
1036 "version": "4.0.0",
1188 "version": "4.0.0",
@@ -1071,6 +1223,40 @@
1071 "unsafe-coerce"
1223 "unsafe-coerce"
1072 ]
1224 ]
1073 },
1225 },
1226+ "spec": {
1227+ "type": "registry",
1228+ "version": "7.6.0",
1229+ "integrity": "sha256-CLwN4VGULEM64zwgffT1kQ0BuxAiwjarCW7gSexcQvs=",
1230+ "dependencies": [
1231+ "aff",
1232+ "ansi",
1233+ "arrays",
1234+ "avar",
1235+ "bifunctors",
1236+ "control",
1237+ "datetime",
1238+ "effect",
1239+ "either",
1240+ "exceptions",
1241+ "foldable-traversable",
1242+ "fork",
1243+ "identity",
1244+ "integers",
1245+ "lists",
1246+ "maybe",
1247+ "newtype",
1248+ "now",
1249+ "ordered-collections",
1250+ "parallel",
1251+ "pipes",
1252+ "prelude",
1253+ "refs",
1254+ "strings",
1255+ "tailrec",
1256+ "transformers",
1257+ "tuples"
1258+ ]
1259+ },
1074 "st": {
1260 "st": {
1075 "type": "registry",
1261 "type": "registry",
1076 "version": "6.2.0",
1262 "version": "6.2.0",
@@ -1083,6 +1269,29 @@
1083 "unsafe-coerce"
1269 "unsafe-coerce"
1084 ]
1270 ]
1085 },
1271 },
1272+ "strings": {
1273+ "type": "registry",
1274+ "version": "6.0.1",
1275+ "integrity": "sha256-FNto2hoNW5Da6W6crIk77YXMagBvOGHZE1kO7eZ1vLM=",
1276+ "dependencies": [
1277+ "arrays",
1278+ "control",
1279+ "either",
1280+ "enums",
1281+ "foldable-traversable",
1282+ "gen",
1283+ "integers",
1284+ "maybe",
1285+ "newtype",
1286+ "nonempty",
1287+ "partial",
1288+ "prelude",
1289+ "tailrec",
1290+ "tuples",
1291+ "unfoldable",
1292+ "unsafe-coerce"
1293+ ]
1294+ },
1086 "tailrec": {
1295 "tailrec": {
1087 "type": "registry",
1296 "type": "registry",
1088 "version": "6.1.0",
1297 "version": "6.1.0",
spago.yaml+9
@@ -36,3 +36,12 @@ package:
36 - uncurried-transformers: ">=1.0.0 <2.0.0"
36 - uncurried-transformers: ">=1.0.0 <2.0.0"
37 - unsafe-coerce: ">=6.0.0 <7.0.0"
37 - unsafe-coerce: ">=6.0.0 <7.0.0"
38 - variant: ">=8.0.0 <9.0.0"
38 - variant: ">=8.0.0 <9.0.0"
39+ - aff-retry: ">=2.0.0 <3.0.0"
40+ - arrays: ">=7.0.0 <8.0.0"
41+ - numbers: ">=9.0.0 <10.0.0"
42+ - random: ">=6.0.0 <7.0.0"
43+ test:
44+ main: Test.Main
45+ dependencies:
46+ - spec: ">=7.0.0 <8.0.0"
47+ - refs: ">=6.0.0 <7.0.0"
src/Yoga/Om.purs+45−1
@@ -3,6 +3,9 @@
3-- @inline export note arity=2
3-- @inline export note arity=2
4-- @inline export fromAff arity=1
4-- @inline export fromAff arity=1
5-- @inline export runOm arity=2
5-- @inline export runOm arity=2
6+-- @inline export toOm arity=1
7+-- @inline export onError arity=2
8+-- @inline export mapError arity=2
6module Yoga.Om
9module Yoga.Om
7 ( Om(..)
10 ( Om(..)
8 , ParOm(..)
11 , ParOm(..)
@@ -13,6 +16,8 @@ module Yoga.Om
13 , expandErr
16 , expandErr
14 , fromAff
17 , fromAff
15 , handleErrors
18 , handleErrors
19+ , mapError
20+ , onError
16 , handleErrors'
21 , handleErrors'
17 , launchOm
22 , launchOm
18 , launchOm_
23 , launchOm_
@@ -29,6 +34,8 @@ module Yoga.Om
29 , throw
34 , throw
30 , throwLeftAs
35 , throwLeftAs
31 , throwLeftAsM
36 , throwLeftAsM
37+ , class ToOm
38+ , toOm
32 , unliftAff
39 , unliftAff
33 , unliftAffFn
40 , unliftAffFn
34 , widenCtx
41 , widenCtx
@@ -52,6 +59,7 @@ import Data.Newtype (class Newtype)
52import Data.Time.Duration (class Duration, fromDuration)
59import Data.Time.Duration (class Duration, fromDuration)
53import Data.Tuple.Nested ((/\))
60import Data.Tuple.Nested ((/\))
54import Data.Variant (class VariantMatchCases, Variant, case_, match, on, onMatch)
61import Data.Variant (class VariantMatchCases, Variant, case_, match, on, onMatch)
62+import Data.Variant as Variant
55import Effect (Effect)
63import Effect (Effect)
56import Effect.AVar (AVar)
64import Effect.AVar (AVar)
57import Effect.Aff (Aff, Fiber, ParAff, forkAff, launchAff, supervise)
65import Effect.Aff (Aff, Fiber, ParAff, forkAff, launchAff, supervise)
@@ -60,7 +68,9 @@ import Effect.Aff.AVar as AVar
60import Effect.Aff.Class (class MonadAff, liftAff)
68import Effect.Aff.Class (class MonadAff, liftAff)
61import Effect.Class (class MonadEffect, liftEffect)
69import Effect.Class (class MonadEffect, liftEffect)
62import Effect.Exception (Error)
70import Effect.Exception (Error)
71+import Data.Symbol (class IsSymbol)
63−import Prim.Row (class Nub, class Union)
72+import Prim.Row (class Cons, class Nub, class Union)
73+import Type.Proxy (Proxy(..))
64import Prim.RowList (class RowToList, RowList)
74import Prim.RowList (class RowToList, RowList)
65import Record (disjointUnion)
75import Record (disjointUnion)
66import Record.Studio (class Keys, shrink)
76import Record.Studio (class Keys, shrink)
@@ -218,6 +228,30 @@ handleErrors cases (Om om) = do
218 err ← liftAff ((\(_ /\ v /\ _) → v) <$> (runRWSET ctx unit om))
228 err ← liftAff ((\(_ /\ v /\ _) → v) <$> (runRWSET ctx unit om))
219 err # either (onMatch cases throwError) pure
229 err # either (onMatch cases throwError) pure
220
230
231+mapError
232+ :: forall @from @to tyIn tyOut ctx errIn errMid errOut a
233+ . IsSymbol from
234+ => IsSymbol to
235+ => Cons from tyIn (exception :: Error | errMid) (exception :: Error | errIn)
236+ => Cons to tyOut (exception :: Error | errMid) (exception :: Error | errOut)
237+ => (tyIn -> tyOut)
238+ -> Om ctx errIn a
239+ -> Om ctx errOut a
240+mapError f = handleErrors' \variant ->
241+ variant # on (Proxy :: Proxy from)
242+ (throwError <<< Variant.inj (Proxy :: Proxy to) <<< f)
243+ (throwError <<< unsafeCoerce)
244+
245+onError
246+ :: forall @label ty ctx errIn errOut a
247+ . IsSymbol label
248+ => Cons label ty (exception :: Error | errOut) (exception :: Error | errIn)
249+ => (ty -> Om ctx errOut a)
250+ -> Om ctx errIn a
251+ -> Om ctx errOut a
252+onError handler = handleErrors' \variant ->
253+ variant # on (Proxy :: Proxy label) handler throwError
254+
221-- | When you have a function that can throw a subset of the errors of the ones
255-- | When you have a function that can throw a subset of the errors of the ones
222-- | you could throw in a do block, `expand` allows to make them compatible
256-- | you could throw in a do block, `expand` allows to make them compatible
223
257
@@ -347,6 +381,16 @@ runOm ctx errorHandlers (Om app) = do
347 (_ /\ Right r /\ _) → pure r
381 (_ /\ Right r /\ _) → pure r
348 (_ /\ Left err /\ _) → err # match errorHandlers
382 (_ /\ Left err /\ _) → err # match errorHandlers
349
383
384+class ToOm :: (Type -> Type) -> Constraint
385+class ToOm f where
386+ toOm :: forall ctx err a. f a -> Om ctx err a
387+
388+instance ToOm Effect where
389+ toOm = liftEffect
390+
391+instance ToOm Aff where
392+ toOm = liftAff
393+
350-- | Manually add some fields to the context of a computation.
394-- | Manually add some fields to the context of a computation.
351-- | This function is useful when already inside a `do` block of an `Om`
395-- | This function is useful when already inside a `do` block of an `Om`
352-- | and you want to call a function that requires some `ctx` that you obtained
396-- | and you want to call a function that requires some `ctx` that you obtained
src/Yoga/Om/Ref.pursnew file+39
@@ -0,0 +1,39 @@
1+-- @inline export new arity=1
2+-- @inline export read arity=1
3+-- @inline export modify' arity=2
4+-- @inline export modify arity=2
5+-- @inline export modify_ arity=2
6+-- @inline export write arity=2
7+module Yoga.Om.Ref
8+ ( module Effect.Ref
9+ , new
10+ , read
11+ , modify'
12+ , modify
13+ , modify_
14+ , write
15+ ) where
16+
17+import Prelude
18+
19+import Effect.Class (class MonadEffect, liftEffect)
20+import Effect.Ref (Ref)
21+import Effect.Ref as Ref
22+
23+new :: forall m s. MonadEffect m => s -> m (Ref s)
24+new = liftEffect <<< Ref.new
25+
26+read :: forall m s. MonadEffect m => Ref s -> m s
27+read = liftEffect <<< Ref.read
28+
29+modify' :: forall m s b. MonadEffect m => (s -> { state :: s, value :: b }) -> Ref s -> m b
30+modify' f = liftEffect <<< Ref.modify' f
31+
32+modify :: forall m s. MonadEffect m => (s -> s) -> Ref s -> m s
33+modify f = liftEffect <<< Ref.modify f
34+
35+modify_ :: forall m s. MonadEffect m => (s -> s) -> Ref s -> m Unit
36+modify_ f = liftEffect <<< Ref.modify_ f
37+
38+write :: forall m s. MonadEffect m => s -> Ref s -> m Unit
39+write s = liftEffect <<< Ref.write s
src/Yoga/Om/Retry.pursnew file+61
@@ -0,0 +1,61 @@
1+module Yoga.Om.Retry
2+ ( recovering
3+ , repeating
4+ , module Effect.Aff.Retry
5+ ) where
6+
7+import Prelude
8+
9+import Control.Monad.Error.Class (catchError, throwError)
10+import Data.Maybe (maybe)
11+import Data.Variant (class VariantMatchCases, onMatch)
12+import Effect.Aff.Retry (RetryPolicyM, RetryStatus, applyAndDelay, defaultRetryStatus)
13+import Effect.Exception (Error)
14+import Prim.Row (class Union)
15+import Prim.RowList (class RowToList, RowList)
16+import Yoga.Om (Om)
17+
18+recovering
19+ :: forall ctx err rest a
20+ (handlersRL :: RowList Type)
21+ (handlers :: Row Type)
22+ (handled :: Row Type)
23+ . RowToList handlers handlersRL
24+ => VariantMatchCases handlersRL handled (Om ctx err Boolean)
25+ => Union handled rest (exception :: Error | err)
26+ => RetryPolicyM (Om ctx err)
27+ -> (RetryStatus -> Record handlers)
28+ -> (RetryStatus -> Om ctx err a)
29+ -> Om ctx err a
30+recovering policy mkChecks action = go defaultRetryStatus
31+ where
32+ go status =
33+ catchError (action status) (handleErr status)
34+
35+ handleErr status variant = do
36+ let shouldRetry = variant # onMatch (mkChecks status) (\_ -> pure false)
37+ ifM shouldRetry
38+ (applyAndDelay policy status >>= maybe (throwError variant) go)
39+ (throwError variant)
40+
41+-- | Repeat a successful action according to a policy while a condition holds.
42+-- | Returns the last result.
43+-- |
44+-- | ```purescript
45+-- | repeating (constantDelay (Milliseconds 1000.0) <> limitRetries 10)
46+-- | (\_ result -> pure (result < threshold))
47+-- | (\_ -> pollSensor)
48+-- | ```
49+repeating
50+ :: forall ctx err a
51+ . RetryPolicyM (Om ctx err)
52+ -> (RetryStatus -> a -> Om ctx err Boolean)
53+ -> (RetryStatus -> Om ctx err a)
54+ -> Om ctx err a
55+repeating policy shouldRepeat action = go defaultRetryStatus
56+ where
57+ go status = do
58+ result <- action status
59+ ifM (shouldRepeat status result)
60+ (applyAndDelay policy status >>= maybe (pure result) go)
61+ (pure result)
test/Test/Main.pursnew file+149
@@ -0,0 +1,149 @@
1+module Test.Main where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Effect.Aff (launchAff_)
7+import Effect.Aff.Class (liftAff)
8+import Effect.Class (liftEffect)
9+import Effect.Ref as Ref
10+import Effect.Aff.Retry (RetryStatus(..), limitRetries, constantDelay)
11+import Data.Time.Duration (Milliseconds(..))
12+import Test.Spec (describe, it)
13+import Test.Spec.Assertions (shouldEqual)
14+import Test.Spec.Reporter (consoleReporter)
15+import Test.Spec.Runner (runSpec)
16+import Yoga.Om as Om
17+import Yoga.Om.Retry (recovering, repeating)
18+
19+main :: Effect Unit
20+main = launchAff_ $ runSpec [ consoleReporter ] do
21+ describe "Yoga.Om.Retry - recovering" do
22+
23+ it "retries on matching errors up to the limit" do
24+ attemptsRef <- Ref.new 0 # liftEffect
25+ recovering (constantDelay (Milliseconds 0.0) <> limitRetries 3)
26+ (\_ -> { networkError: \(_ :: String) -> pure true })
27+ (\_ -> do
28+ attempts <- Ref.modify (_ + 1) attemptsRef # liftEffect
29+ Om.throw { networkError: "fail " <> show attempts }
30+ )
31+ # Om.runOm {}
32+ { exception: \_ -> pure unit
33+ , networkError: \_ -> pure unit
34+ }
35+ # liftAff
36+ attempts <- Ref.read attemptsRef # liftEffect
37+ attempts `shouldEqual` 4
38+
39+ it "does not retry on non-matching errors" do
40+ attemptsRef <- Ref.new 0 # liftEffect
41+ recovering (constantDelay (Milliseconds 0.0) <> limitRetries 3)
42+ (\_ -> { networkError: \(_ :: String) -> pure true })
43+ (\_ -> do
44+ Ref.modify_ (_ + 1) attemptsRef # liftEffect
45+ Om.throw { authError: "unauthorized" }
46+ )
47+ # Om.runOm {}
48+ { exception: \_ -> pure unit
49+ , networkError: \_ -> pure unit
50+ , authError: \_ -> pure unit
51+ }
52+ # liftAff
53+ attempts <- Ref.read attemptsRef # liftEffect
54+ attempts `shouldEqual` 1
55+
56+ it "succeeds after transient failures" do
57+ attemptsRef <- Ref.new 0 # liftEffect
58+ result <-
59+ recovering (constantDelay (Milliseconds 0.0) <> limitRetries 5)
60+ (\_ -> { networkError: \(_ :: String) -> pure true })
61+ (\_ -> do
62+ attempts <- Ref.modify (_ + 1) attemptsRef # liftEffect
63+ when (attempts < 3) do
64+ Om.throw { networkError: "transient" }
65+ pure "success"
66+ )
67+ # Om.runOm {}
68+ { exception: \_ -> pure "failed"
69+ , networkError: \_ -> pure "failed"
70+ }
71+ # liftAff
72+ result `shouldEqual` "success"
73+ attempts <- Ref.read attemptsRef # liftEffect
74+ attempts `shouldEqual` 3
75+
76+ it "uses RetryStatus to decide whether to retry" do
77+ statusesRef <- Ref.new [] # liftEffect
78+ recovering (constantDelay (Milliseconds 0.0) <> limitRetries 3)
79+ (\(RetryStatus s) ->
80+ { networkError: \(_ :: Unit) -> do
81+ Ref.modify_ (_ <> [s.iterNumber]) statusesRef # liftEffect
82+ pure true
83+ })
84+ (\_ -> Om.throw { networkError: unit })
85+ # Om.runOm {}
86+ { exception: \_ -> pure unit
87+ , networkError: \_ -> pure unit
88+ }
89+ # liftAff
90+ statuses <- Ref.read statusesRef # liftEffect
91+ statuses `shouldEqual` [0, 1, 2, 3]
92+
93+ it "stops retrying when check returns false" do
94+ attemptsRef <- Ref.new 0 # liftEffect
95+ recovering (constantDelay (Milliseconds 0.0) <> limitRetries 10)
96+ (\(RetryStatus s) ->
97+ { networkError: \(_ :: Unit) -> pure (s.iterNumber < 2) })
98+ (\_ -> do
99+ Ref.modify_ (_ + 1) attemptsRef # liftEffect
100+ Om.throw { networkError: unit }
101+ )
102+ # Om.runOm {}
103+ { exception: \_ -> pure unit
104+ , networkError: \_ -> pure unit
105+ }
106+ # liftAff
107+ attempts <- Ref.read attemptsRef # liftEffect
108+ attempts `shouldEqual` 3
109+
110+ describe "Yoga.Om.Retry - repeating" do
111+
112+ it "repeatings while condition holds" do
113+ counterRef <- Ref.new 0 # liftEffect
114+ result <-
115+ repeating (constantDelay (Milliseconds 0.0) <> limitRetries 10)
116+ (\_ n -> pure (n < 5))
117+ (\_ -> Ref.modify (_ + 1) counterRef # liftEffect)
118+ # Om.runOm {} { exception: \_ -> pure 0 }
119+ # liftAff
120+ result `shouldEqual` 5
121+
122+ it "stops when condition returns false" do
123+ counterRef <- Ref.new 0 # liftEffect
124+ result <-
125+ repeating (constantDelay (Milliseconds 0.0) <> limitRetries 100)
126+ (\_ n -> pure (n < 3))
127+ (\_ -> Ref.modify (_ + 1) counterRef # liftEffect)
128+ # Om.runOm {} { exception: \_ -> pure 0 }
129+ # liftAff
130+ result `shouldEqual` 3
131+
132+ it "stops when policy exhausted" do
133+ counterRef <- Ref.new 0 # liftEffect
134+ result <-
135+ repeating (constantDelay (Milliseconds 0.0) <> limitRetries 3)
136+ (\_ _ -> pure true)
137+ (\_ -> Ref.modify (_ + 1) counterRef # liftEffect)
138+ # Om.runOm {} { exception: \_ -> pure 0 }
139+ # liftAff
140+ result `shouldEqual` 4
141+
142+ it "returns last result" do
143+ result <-
144+ repeating (constantDelay (Milliseconds 0.0) <> limitRetries 2)
145+ (\_ _ -> pure true)
146+ (\(RetryStatus s) -> pure ("attempt-" <> show s.iterNumber))
147+ # Om.runOm {} { exception: \_ -> pure "" }
148+ # liftAff
149+ result `shouldEqual` "attempt-2"