diff --git a/.gitignore b/.gitignore index 30221a4..5209cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ /bower_components/ /.pulp-cache/ -/output/ \ No newline at end of file +/output/ +.psc-ide-port +/.psci_modules +.spago +node_modules +.purs-repl \ No newline at end of file diff --git a/bower.json b/bower.json index 5bb185b..eb5bcbb 100644 --- a/bower.json +++ b/bower.json @@ -9,26 +9,26 @@ "tests" ], "dependencies": { - "purescript-prelude": "^4.0.0", - "purescript-aff": "^5.0.0", - "purescript-avar": "^3.0.0", - "purescript-console": "^4.1.0", - "purescript-control": "^4.0.0", - "purescript-datetime": "^4.0.0", - "purescript-exceptions": "^4.0.0", - "purescript-exists": "^4.0.0", - "purescript-free": "^5.0.0", - "purescript-foreign-generic": "^7.0.0", - "purescript-foreign-object": "^1.0.0", - "purescript-generics-rep": "^6.0.0", - "purescript-transformers": "^4.1.0", - "purescript-record": "^1.0.0", - "purescript-typelevel-prelude": "^3.0.0", - "purescript-tuples": "^5.0.0" + "purescript-prelude": "^6.0.0", + "purescript-aff": "^7.0.0", + "purescript-avar": "^5.0.0", + "purescript-console": "^6.0.0", + "purescript-control": "^6.0.0", + "purescript-datetime": "^6.0.0", + "purescript-exceptions": "^6.0.0", + "purescript-exists": "^6.0.0", + "purescript-free": "^7.0.0", + "purescript-foreign-generic": "https://github.com/Adien7368/purescript-foreign-generic.git#v12.0.0", + "purescript-transformers": "^6.0.0", + "purescript-foreign-object": "^4.0.0", + "purescript-record": "^4.0.0", + "purescript-typelevel-prelude": "^7.0.0", + "purescript-tuples": "^7.0.0", + "purescript-debug": "^6.0.0" }, "devDependencies": { - "purescript-psci-support": "^4.0.0", - "purescript-spec": "^3.0.0" + "purescript-psci-support": "^6.0.0", + "purescript-spec": "^7.0.0" }, "homepage": "https://github.com/juspay/purescript-presto", "description": "eDSL for writing Apps", diff --git a/src/package-lock.json b/package-lock.json similarity index 100% rename from src/package-lock.json rename to package-lock.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..10b926e --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "purescript-presto", + "version": "1.0.0", + "description": "Write apps like equations!", + "main": "index.js", + "directories": { + "example": "examples", + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "pulp build" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@bitbucket.org/juspay/purescript-presto.git" + }, + "author": "Juspay", + "license": "ISC", + "bugs": { + "url": "https://bitbucket.org/juspay/purescript-presto/issues" + }, + "homepage": "https://bitbucket.org/juspay/purescript-presto#readme", + "dependencies": { + "pulp": "^16.0.2", + "purescript": "0.15.6", + "spago": "^0.20.9" + } +} diff --git a/packages.dhall b/packages.dhall new file mode 100644 index 0000000..c99b52c --- /dev/null +++ b/packages.dhall @@ -0,0 +1,134 @@ +{- +Welcome to your new Dhall package-set! + +Below are instructions for how to edit this file for most use +cases, so that you don't need to know Dhall to use it. + +## Use Cases + +Most will want to do one or both of these options: +1. Override/Patch a package's dependency +2. Add a package not already in the default package set + +This file will continue to work whether you use one or both options. +Instructions for each option are explained below. + +### Overriding/Patching a package + +Purpose: +- Change a package's dependency to a newer/older release than the + default package set's release +- Use your own modified version of some dependency that may + include new API, changed API, removed API by + using your custom git repo of the library rather than + the package set's repo + +Syntax: +where `entityName` is one of the following: +- dependencies +- repo +- version +------------------------------- +let upstream = -- +in upstream + with packageName.entityName = "new value" +------------------------------- + +Example: +------------------------------- +let upstream = -- +in upstream + with halogen.version = "master" + with halogen.repo = "https://example.com/path/to/git/repo.git" + + with halogen-vdom.version = "v4.0.0" + with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies +------------------------------- + +### Additions + +Purpose: +- Add packages that aren't already included in the default package set + +Syntax: +where `` is: +- a tag (i.e. "v4.0.0") +- a branch (i.e. "master") +- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977") +------------------------------- +let upstream = -- +in upstream + with new-package-name = + { dependencies = + [ "dependency1" + , "dependency2" + ] + , repo = + "https://example.com/path/to/git/repo.git" + , version = + "" + } +------------------------------- + +Example: +------------------------------- +let upstream = -- +in upstream + with benchotron = + { dependencies = + [ "arrays" + , "exists" + , "profunctor" + , "strings" + , "quickcheck" + , "lcg" + , "transformers" + , "foldable-traversable" + , "exceptions" + , "node-fs" + , "node-buffer" + , "node-readline" + , "datetime" + , "now" + ] + , repo = + "https://github.com/hdgarrood/purescript-benchotron.git" + , version = + "v7.0.0" + } +------------------------------- +-} +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.15.3/packages.dhall + sha256:ffc496e19c93f211b990f52e63e8c16f31273d4369dbae37c7cf6ea852d4442f + +in upstream + with foreign-generic = + { dependencies = + [ "arrays" + , "assert" + , "bifunctors" + , "console" + , "control" + , "effect" + , "either" + , "exceptions" + , "foldable-traversable" + , "foreign" + , "foreign-object" + , "identity" + , "lists" + , "maybe" + , "newtype" + , "partial" + , "prelude" + , "record" + , "strings" + , "transformers" + , "tuples" + , "typelevel-prelude" + , "unsafe-coerce" + ] + , repo = "https://github.com/Adien7368/purescript-foreign-generic.git" + , version = "v12.0.0" + } diff --git a/spago.dhall b/spago.dhall new file mode 100644 index 0000000..32b9f86 --- /dev/null +++ b/spago.dhall @@ -0,0 +1,27 @@ +{ name = "presto" +, dependencies = + [ "aff" + , "avar" + , "datetime" + , "effect" + , "either" + , "exceptions" + , "exists" + , "foldable-traversable" + , "foreign" + , "foreign-generic" + , "foreign-object" + , "free" + , "identity" + , "maybe" + , "newtype" + , "parallel" + , "prelude" + , "record" + , "transformers" + , "tuples" + , "unsafe-coerce" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs" ] +} diff --git a/src/Presto/Core/Flow.purs b/src/Presto/Core/Flow.purs index 142fa26..f8bf5a5 100644 --- a/src/Presto/Core/Flow.purs +++ b/src/Presto/Core/Flow.purs @@ -7,6 +7,6 @@ module Presto.Core.Flow ( module Presto.Core.Language.Runtime.API import Presto.Core.Language.Runtime.API (APIRunner, runAPIInteraction) import Presto.Core.Language.Runtime.Interpreter (Runtime(..), PermissionRunner(..), PermissionCheckRunner, PermissionTakeRunner, UIRunner, run) -import Presto.Core.Types.Language.Flow (Flow, callAPI, evalUI, forkUI, runUI, showUI, get, set, load, save, delete, fork, launch, doAff, await, await', delay, oneOf) +import Presto.Core.Types.Language.Flow (Flow, callAPI, evalUI, forkUI, runUI, showUI, get, set, load, save, delete, fork, launch, doAff, await, await', delay, oneOf, getLogField, getLogFields, setLogField) import Presto.Core.Types.Language.Interaction (class Interact, Interaction, defaultInteract, request) import Presto.Core.Types.Language.Storage (class Serializable, Key) diff --git a/src/Presto/Core/Language/Runtime/API.purs b/src/Presto/Core/Language/Runtime/API.purs index b80b8a1..d562b0a 100644 --- a/src/Presto/Core/Language/Runtime/API.purs +++ b/src/Presto/Core/Language/Runtime/API.purs @@ -11,11 +11,10 @@ import Data.Either (Either(..)) import Effect.Aff (Aff) import Effect.Exception (error) import Foreign.Class (encode, decode) - -import Presto.Core.Types.API (Request) as API +import Presto.Core.Types.API (Request, Response) as API import Presto.Core.Types.Language.Interaction (InteractionF(..), Interaction, ForeignIn(..), ForeignOut(..)) -type APIRunner = API.Request -> Aff String +type APIRunner = API.Request -> Aff (API.Response String) interpretAPI :: APIRunner -> InteractionF ~> Aff interpretAPI apiRunner (Request (ForeignIn fgnIn) nextF) = do diff --git a/src/Presto/Core/Language/Runtime/Interpreter.purs b/src/Presto/Core/Language/Runtime/Interpreter.purs index c009204..d96691c 100644 --- a/src/Presto/Core/Language/Runtime/Interpreter.purs +++ b/src/Presto/Core/Language/Runtime/Interpreter.purs @@ -17,17 +17,16 @@ import Control.Parallel (parOneOf) import Data.Either (Either(..)) import Data.Exists (runExists) import Data.Tuple (Tuple(..)) -import Effect.Aff (Aff, forkAff, delay) -import Effect.Aff.AVar as AV import Effect (Effect) +import Effect.Aff (Aff, delay, forkAff, killFiber) +import Effect.Aff.AVar as AV import Effect.Exception (Error, error) import Foreign.JSON (parseJSON) import Foreign.Object as Object -import Global.Unsafe (unsafeStringify) - +import Presto.Core.Utils.Encoding (unsafeStringify) import Presto.Core.Language.Runtime.API (APIRunner, runAPIInteraction) import Presto.Core.LocalStorage (deleteValueFromLocalStore, getValueFromLocalStore, setValueToLocalStore) -import Presto.Core.Types.Language.Flow (ErrorHandler(..), Flow, FlowMethod, FlowMethodF(..), FlowWrapper(..), Store(..), Control(..)) +import Presto.Core.Types.Language.Flow (ErrorHandler(..), Flow, FlowMethod, FlowMethodF(..), FlowWrapper(..), Store(..), Control(..), St) import Presto.Core.Types.Language.Interaction (InteractionF(..), Interaction, ForeignOut(..)) import Presto.Core.Types.Language.Storage (Key) import Presto.Core.Types.Permission (Permission, PermissionResponse, PermissionStatus) @@ -35,8 +34,7 @@ import Presto.Core.Types.Permission (Permission, PermissionResponse, PermissionS type AffError = (Error -> Effect Unit) type AffSuccess s = (s -> Effect Unit) -type St = AV.AVar (Object.Object String) -type InterpreterSt a = S.StateT St Aff a +type InterpreterSt st a = S.StateT (St st) Aff a type UIRunner = String -> Aff String @@ -47,15 +45,24 @@ data PermissionRunner = PermissionRunner PermissionCheckRunner PermissionTakeRun data Runtime = Runtime UIRunner PermissionRunner APIRunner -readState :: InterpreterSt (Object.Object String) -readState = S.get >>= (lift <<< AV.read) +readState :: forall a. InterpreterSt a a +readState = S.get >>= (lift <<< map (_.state) <<< AV.read) + +readMap :: forall a. InterpreterSt a (Object.Object String) +readMap = S.get >>= (lift <<< map (_.keyMap) <<< AV.read) -updateState :: Key -> String -> InterpreterSt Unit -updateState key value = do +updateState :: forall a. a -> InterpreterSt a Unit +updateState value = do stVar <- S.get - st <- lift $ AV.take stVar - let st' = Object.insert key value st - lift $ AV.put st' stVar + state <- lift $ AV.take stVar + lift $ AV.put (state {state = value}) stVar + +updateMap :: forall a. Key -> String -> InterpreterSt a Unit +updateMap key value = do + stVar <- S.get + state <- lift $ AV.take stVar + let st' = Object.insert key value state.keyMap + lift $ AV.put (state {keyMap = st'}) stVar interpretUI :: UIRunner -> InteractionF ~> Aff interpretUI uiRunner (Request fgnIn nextF) = do @@ -67,20 +74,19 @@ interpretUI uiRunner (Request fgnIn nextF) = do runUIInteraction :: UIRunner -> Interaction ~> Aff runUIInteraction uiRunner = foldFree (interpretUI uiRunner) --- TODO: canceller support -forkFlow :: forall a. Runtime -> Flow a -> InterpreterSt (Control a) +forkFlow :: forall a e. Runtime -> Flow e a -> InterpreterSt e (Control a) forkFlow rt flow = do st <- S.get resultVar <- lift AV.empty let m = S.evalStateT (run rt flow) st - _ <- lift $ forkAff $ m >>= flip AV.put resultVar - pure $ Control resultVar + fiber <- lift $ forkAff $ m >>= (\a -> AV.put a resultVar *> pure a) + pure $ Control fiber resultVar -runErrorHandler :: forall s. ErrorHandler s -> InterpreterSt s +runErrorHandler :: forall st s. ErrorHandler s -> InterpreterSt st s runErrorHandler (ThrowError msg) = throwError $ error msg runErrorHandler (ReturnResult res) = pure res -interpret :: forall s. Runtime -> FlowMethod s ~> InterpreterSt +interpret :: forall s e. Runtime -> FlowMethod e s ~> InterpreterSt e interpret (Runtime _ _ apiRunner) (CallAPI apiInteractionF nextF) = do lift $ runAPIInteraction apiRunner apiInteractionF >>= (pure <<< nextF) @@ -96,30 +102,57 @@ interpret (Runtime uiRunner _ _) (ForkUI uiInteraction next) = do interpret _ (Get LocalStore key next) = lift $ getValueFromLocalStore key >>= (pure <<< next) interpret _ (Get InMemoryStore key next) = do - readState >>= (Object.lookup key >>> next >>> pure) + readMap >>= (Object.lookup key >>> next >>> pure) + +interpret _ (GetState next) = do + readState <#> next interpret _ (Set LocalStore key value next) = do lift $ setValueToLocalStore key value pure next +interpret _ (SetState value next) = do + updateState value *> pure (next unit) + interpret _ (Set InMemoryStore key value next) = do - updateState key value *> pure next + updateMap key value *> pure next + +interpret _ (GetForeign next) = + S.get >>= (lift <<< map (next <<< (_.logsState)) <<< AV.read) + +interpret _ (SetForeign key fValue next) = do + stVar <- S.get + state <- lift $ AV.take stVar + let stf' = Object.insert key fValue state.logsState + lift $ AV.put (state {logsState = stf'}) stVar + pure next interpret _ (Delete LocalStore key next) = do lift $ deleteValueFromLocalStore key pure next interpret _ (Delete InMemoryStore key next) = do - _ <- Object.delete key <$> readState + _ <- Object.delete key <$> readMap pure next +interpret _ (ModifyState fn next) = do + stVar <- S.get + state <- lift $ AV.take stVar + let st' = fn state.state + lift $ AV.put (state {state = st'}) stVar + pure $ next st' + interpret r (Fork flow nextF) = forkFlow r flow >>= (pure <<< nextF) interpret _ (DoAff aff nextF) = lift aff >>= (pure <<< nextF) -interpret _ (Await (Control resultVar) nextF) = do +interpret _ (Await (Control _ resultVar) nextF) = do lift (AV.read resultVar) >>= (pure <<< nextF) +interpret _ (Kill (Control fiber _) next) = do + lift (killFiber (error "Received termination") fiber) + pure (next unit) + interpret _ (Delay duration next) = lift (delay duration) *> pure next interpret rt (OneOf flows nextF) = do @@ -139,5 +172,5 @@ interpret (Runtime _ (PermissionRunner check _) _) (CheckPermissions permissions interpret (Runtime _ (PermissionRunner _ take) _) (TakePermissions permissions nextF) = do lift $ take permissions >>= (pure <<< nextF) -run :: Runtime -> Flow ~> InterpreterSt +run :: forall st. Runtime -> Flow st ~> InterpreterSt st run runtime = foldFree (\(FlowWrapper x) -> runExists (interpret runtime) x) diff --git a/src/Presto/Core/LocalStorage/LocalStorage.js b/src/Presto/Core/LocalStorage/LocalStorage.js index 861fa8a..e98b37a 100644 --- a/src/Presto/Core/LocalStorage/LocalStorage.js +++ b/src/Presto/Core/LocalStorage/LocalStorage.js @@ -1,11 +1,29 @@ -exports.getValueFromLocalStoreImpl = function(key) { +export const getValueFromLocalStoreImpl = function(key) { + if (window.JOS && window.JOS.fetchAndDecrypt) { + try { + return window.JOS.fetchAndDecrypt(key); + } catch (err) { } + } return JBridge.getFromSharedPrefs(key); }; -exports.setValueToLocalStoreImpl = function(key, value) { +export const setValueToLocalStoreImpl = function(key, value) { + if (window.JOS && window.JOS.encryptAndStore) { + try { + return window.JOS.encryptAndStore(key)(value); + } catch (err) { } + } return JBridge.setInSharedPrefs(key, value); }; -exports.deleteValueFromLocalStoreImpl = function(key){ - JBridge.removeDataFromSharedPrefs(key); +export const deleteValueFromLocalStoreImpl = function(key) { + if (window.JOS && window.JOS.deleteEncKeys) { + try { + window.JOS.deleteEncKeys(key); + } catch (err) { + JBridge.removeDataFromSharedPrefs(key); + } + } else { + JBridge.removeDataFromSharedPrefs(key); + } }; \ No newline at end of file diff --git a/src/Presto/Core/Operators.purs b/src/Presto/Core/Operators.purs index 8f81ba9..c4cb74e 100644 --- a/src/Presto/Core/Operators.purs +++ b/src/Presto/Core/Operators.purs @@ -37,7 +37,7 @@ until pred flow = do infixl 5 orElse as <|> -onFirstRun :: forall s. Serializable s => Key -> Flow s -> Flow s +onFirstRun :: forall s st. Serializable s => Key -> Flow st s -> Flow st s onFirstRun key flow = do mbRes <- load key case mbRes of @@ -47,8 +47,8 @@ onFirstRun key flow = do save key value pure value -inParallel :: Array (Flow Unit) -> Flow (Array (Control Unit)) +inParallel :: forall st. Array (Flow st Unit) -> Flow st (Array (Control Unit)) inParallel = traverse fork -inParallel' :: Array (Flow Unit) -> Flow Unit +inParallel' :: forall st. Array (Flow st Unit) -> Flow st Unit inParallel' = traverse_ launch diff --git a/src/Presto/Core/Types/API.purs b/src/Presto/Core/Types/API.purs index d0cafa8..3ce2b0a 100644 --- a/src/Presto/Core/Types/API.purs +++ b/src/Presto/Core/Types/API.purs @@ -1,7 +1,8 @@ module Presto.Core.Types.API ( class RestEndpoint - , ErrorPayload(..) - , ErrorResponse + , class EncodeRecord + , class StandardEncode + , class EncodeWithOptions , Method(..) , Header(..) , HeaderField @@ -10,41 +11,88 @@ module Presto.Core.Types.API , RegTokens(..) , Request(..) , Response(..) + , ErrorResponse + , ErrorPayload , URL , defaultMakeRequest + , defaultMakeRequestWithoutLogs + , defaultMakeRequestString , defaultMakeRequest_ , defaultDecodeResponse , makeRequest , decodeResponse + , encodeRecordWithOptions + , encodeWithOptions , responsePayload + , encodeRequest + , standardEncode + , standardEncodeJSON ) where import Prelude import Data.Generic.Rep (class Generic) -import Foreign (F) -import Foreign.Class (class Decode, class Encode) -import Foreign.Generic.Class (class GenericDecode, class GenericEncode) - -import Presto.Core.Utils.Encoding (defaultDecode, defaultEncode, defaultDecodeJSON, defaultEncodeJSON) +import Foreign (F, Foreign, isNull, unsafeToForeign) +import Foreign.Class (class Decode, class Encode, encode) +import Data.Identity (Identity) +import Data.Maybe (Maybe, maybe) +import Data.Newtype (unwrap) +import Foreign.Generic.Class (class GenericDecode, defaultOptions, Options) +import Prim.Row (class Cons) +import Data.Symbol (class IsSymbol, reflectSymbol) +import Prim.RowList (class RowToList, Nil, Cons) +import Foreign.NullOrUndefined (undefined) +import Foreign.Object (Object) +import Foreign.Object as Object +import Presto.Core.Utils.Encoding (defaultDecode, defaultEncode, defaultDecodeJSON, unsafeStringify) +import Unsafe.Coerce (unsafeCoerce) +import Record as Record +import Type.Proxy (Proxy(..)) class RestEndpoint a b | a -> b, b -> a where makeRequest :: a -> Headers -> Request decodeResponse :: String -> F b - -defaultMakeRequest :: forall a x. Generic a x => GenericEncode x - => Method -> URL -> Headers -> a -> Request -defaultMakeRequest method url headers req = Request { method: method - , url: url - , headers: headers - , payload: defaultEncodeJSON req - } + encodeRequest :: a -> Foreign + +standardEncodeJSON :: forall a. StandardEncode a => a -> String +standardEncodeJSON = unsafeStringify <<< standardEncode + +convertNullToUndefined :: Foreign -> Foreign +convertNullToUndefined a = if isNull a then undefined else a + +defaultMakeRequest :: forall a x. RestEndpoint a x => Method -> URL -> Headers -> a -> Request +defaultMakeRequest method url headers req = + Request { method: method + , url: url + , headers: headers + , payload: unsafeStringify $ encodeRequest req + , logResponse: true + } + +defaultMakeRequestWithoutLogs :: forall a x. RestEndpoint a x => Method -> URL -> Headers -> a -> Request +defaultMakeRequestWithoutLogs method url headers req = + Request { method: method + , url: url + , headers: headers + , payload: unsafeStringify $ encodeRequest req + , logResponse: false + } + +defaultMakeRequestString :: Method -> String -> Headers -> String -> Request +defaultMakeRequestString method url headers req = + Request { method: method + , url: url + , headers: headers + , payload: req + , logResponse: true + } defaultMakeRequest_ :: Method -> URL -> Headers -> Request defaultMakeRequest_ method url headers = Request { method: method , url: url , headers: headers , payload: "" + , logResponse: true } defaultDecodeResponse :: forall a x. Generic a x => GenericDecode x @@ -64,7 +112,7 @@ newtype Headers = Headers (Array Header) type URL = String -data Method = POST | GET | PUT | DELETE +data Method = POST | GET | PUT | DELETE | HEAD data GetReqBody = GetReqBody newtype Request = Request @@ -72,24 +120,25 @@ newtype Request = Request , url :: URL , payload :: String , headers :: Headers + , logResponse :: Boolean } -newtype Response a = Response +type ErrorResponse = Response ErrorPayload + +type ErrorPayload = { error :: Boolean + , errorMessage :: String + , userMessage :: String + } + +type Response a = { code :: Int , status :: String , response :: a + , responseHeaders :: Object (Array String) } responsePayload :: forall a. Response a -> a -responsePayload (Response r) = r.response - -newtype ErrorPayload = ErrorPayload - { error :: Boolean - , errorMessage :: String - , userMessage :: String - } - -type ErrorResponse = Response ErrorPayload +responsePayload r = r.response derive instance genericMethod :: Generic Method _ instance encodeMethod :: Encode Method where @@ -101,6 +150,7 @@ instance showMethod :: Show Method where show GET = "GET" show PUT = "PUT" show DELETE = "DELETE" + show HEAD = "HEAD" derive instance genericHeader :: Generic Header _ instance encodeHeaderG :: Encode Header where @@ -119,23 +169,83 @@ instance encodeRequestG :: Encode Request where encode = defaultEncode instance decodeRequestG :: Decode Request where decode = defaultDecode - -derive instance genericErrorPayload :: Generic ErrorPayload _ -instance encodeErrorPayload :: Encode ErrorPayload where - encode = defaultEncode -instance decodeErrorPayload :: Decode ErrorPayload where - decode = defaultDecode -instance showErrorPayload :: Show ErrorPayload where - show (ErrorPayload payload) = payload.userMessage - -derive instance genericResponse :: Generic (Response a) _ -instance decodeResponseG :: Decode a => Decode (Response a) where - decode = defaultDecode -instance encodeResponseG :: Encode a => Encode (Response a) where - encode = defaultEncode -instance showResponse :: Show a => Show (Response a) where - show (Response r) = show r.code <> "_" <> r.status <> "_" <> (show r.response) derive instance genericGetReqBody :: Generic GetReqBody _ instance decodeGetReqBody :: Decode GetReqBody where decode = defaultDecode instance encodeGetReqBody :: Encode GetReqBody where encode = defaultEncode + + +-- | The `StandardEncode` class is a proxy to `Encode` +-- | of the form `a -> Foreign` using `generics-rep` deriving. +-- | +-- | Requirement to proxy is to enable Maybe to encode into undefined, +-- | instead of null +class StandardEncode a where + standardEncode :: a -> Foreign + +instance voidstandardEncode :: StandardEncode Void where + standardEncode = encode + +instance unitstandardEncode :: StandardEncode Unit where + standardEncode = encode + +instance foreignstandardEncode :: StandardEncode Foreign where + standardEncode = encode + +instance stringstandardEncode :: StandardEncode String where + standardEncode = encode + +instance charstandardEncode :: StandardEncode Char where + standardEncode = encode + +instance booleanstandardEncode :: StandardEncode Boolean where + standardEncode = encode + +instance numberstandardEncode :: StandardEncode Number where + standardEncode = encode + +instance intstandardEncode :: StandardEncode Int where + standardEncode = encode + +instance identitystandardEncode :: StandardEncode a => StandardEncode (Identity a) where + standardEncode = standardEncode <<< unwrap + +instance arraystandardEncode :: StandardEncode a => StandardEncode (Array a) where + standardEncode = unsafeToForeign <<< map standardEncode + +instance maybestandardEncode :: StandardEncode a => StandardEncode (Maybe a) where + standardEncode = maybe undefined standardEncode + +instance objectstandardEncode :: StandardEncode v => StandardEncode (Object v) where + standardEncode = unsafeToForeign <<< Object.mapWithKey (\_ -> standardEncode) + +instance recordstandardEncode :: (RowToList r rl, EncodeRecord r rl) => StandardEncode (Record r) where + standardEncode = encodeWithOptions defaultOptions + +class EncodeWithOptions a where + encodeWithOptions :: Options -> a -> Foreign + +instance encodeWithOptionsRecord :: (RowToList r rl, EncodeRecord r rl) => EncodeWithOptions (Record r) where + encodeWithOptions opts = unsafeToForeign <<< encodeRecordWithOptions (Proxy :: Proxy rl) opts + +else instance encodeWithOptionsOther :: StandardEncode a => EncodeWithOptions a where + encodeWithOptions _ = standardEncode + +class EncodeRecord r rl | rl -> r where + encodeRecordWithOptions :: Proxy rl -> Options -> Record r -> Object Foreign + +instance encodeRecordNil :: EncodeRecord () Nil where + encodeRecordWithOptions _ _ _ = Object.empty + +instance encodeRecordCons + :: ( Cons l a r_ r + , EncodeRecord r_ rl_ + , IsSymbol l + , EncodeWithOptions a + ) + => EncodeRecord r (Cons l a rl_) + where + encodeRecordWithOptions _ opts rec = + let obj = encodeRecordWithOptions (Proxy :: Proxy rl_) opts (unsafeCoerce rec) + l = reflectSymbol (Proxy :: Proxy l) + in Object.insert (opts.fieldTransform l) (encodeWithOptions opts (Record.get (Proxy :: Proxy l) rec)) obj \ No newline at end of file diff --git a/src/Presto/Core/Types/Language/APIInteract.js b/src/Presto/Core/Types/Language/APIInteract.js new file mode 100644 index 0000000..a27ca36 --- /dev/null +++ b/src/Presto/Core/Types/Language/APIInteract.js @@ -0,0 +1,31 @@ + +const loopedFunction = function(){ + return loopedFunction +} +const getTracker = function(){ + var trackerJson = window.JOS && window.JOS.tracker || {}; + if (typeof trackerJson._trackException != "function"){ + trackerJson._trackException = loopedFunction; + } + if (typeof trackerJson._trackAction != "function"){ + trackerJson._trackAction = loopedFunction; + } + return trackerJson; +} +const tracker = getTracker(); + +export const _trackException = function(category){ + return function(subcategory){ + return function(label){ + return function(key){ + return function(value) { + tracker._trackException(category)(subcategory)(label)(key)(value)(); + } + } + } + } +} + +export const _trackApiCall = function(resp){ + tracker._trackAction("system")("debug")("api_response")(resp)(); +} \ No newline at end of file diff --git a/src/Presto/Core/Types/Language/APIInteract.purs b/src/Presto/Core/Types/Language/APIInteract.purs index cc129c6..392201a 100644 --- a/src/Presto/Core/Types/Language/APIInteract.purs +++ b/src/Presto/Core/Types/Language/APIInteract.purs @@ -6,30 +6,53 @@ import Prelude import Control.Monad.Except (runExcept) import Data.Either (Either(..)) -import Foreign.Class (class Decode, class Encode, decode, encode) - +import Foreign.Class (class Decode, decode, encode) +import Foreign.Generic (decodeJSON) +import Foreign.Object (empty) +import Presto.Core.Types.API (class RestEndpoint, class StandardEncode, ErrorResponse, Headers, Request(..), Response, makeRequest) import Presto.Core.Types.Language.Interaction (Interaction, request) -import Presto.Core.Types.API (class RestEndpoint, ErrorPayload(..), ErrorResponse, Response(..), Headers, decodeResponse, makeRequest) -import Presto.Core.Utils.Encoding (defaultDecodeJSON) + +foreign import _trackException :: String -> String -> String -> String -> String -> Unit +foreign import _trackApiCall :: forall a. a -> Unit -- Special interact function for API. apiInteract :: forall a b. - Encode a => Decode b => RestEndpoint a b - => a -> Headers -> Interaction (Either ErrorResponse b) + StandardEncode a => Decode b => RestEndpoint a b + => a -> Headers -> Interaction (Either ErrorResponse (Response b)) apiInteract a headers = do - fgnOut <- request (encode (makeRequest a headers)) - pure $ case runExcept (decode fgnOut >>= decodeResponse) of - -- Try to decode the server's resopnse into the expected type - Right resp -> Right resp - Left x -> Left $ case runExcept (decode fgnOut >>= defaultDecodeJSON) of - -- See if the server sent an error response, else create our own - Right e@(Response _) -> e - Left y -> Response - { code : 0 - , status : "" - , response : ErrorPayload - { error: true - , errorMessage: show x <> "\n" <> show y - , userMessage: "Unknown error" - } + let (Request req) = makeRequest a headers + fgnOut <- request (encode (Request req)) + let _ = if req.logResponse then _trackApiCall fgnOut else unit + pure $ case runExcept $ decode fgnOut of + Right (resp :: Response String) -> + case runExcept $ decodeJSON resp.response of + Right (response :: b) -> Right $ { code : resp.code + , responseHeaders : resp.responseHeaders + , response : response + , status : resp.status + } + Left e -> + case runExcept $ decode (encode resp.response) of + Right (response :: b) -> Right $ { code : resp.code + , responseHeaders : resp.responseHeaders + , response : response + , status : resp.status + } + Left er -> Left $ { code : resp.code + , responseHeaders : resp.responseHeaders + , response : { error: true + , errorMessage: resp.response + , userMessage: show er <> "\n" <> resp.response } + , status : resp.status + } + Left x -> Left $ do + let _ = _trackException "api_call" "sdk" "decode_error" "user_errors" "Unknown error" + { code : 0 + , status : "FAILURE" + , response : { error: true + , errorMessage: show x + , userMessage: "CALL API FAILED" <> show x + } + , responseHeaders : empty + } diff --git a/src/Presto/Core/Types/Language/Flow.purs b/src/Presto/Core/Types/Language/Flow.purs index df86bd8..810c491 100644 --- a/src/Presto/Core/Types/Language/Flow.purs +++ b/src/Presto/Core/Types/Language/Flow.purs @@ -7,12 +7,14 @@ import Data.Either (Either, either) import Data.Exists (Exists, mkExists) import Data.Maybe (Maybe) import Data.Time.Duration (class Duration, Milliseconds, fromDuration) -import Effect.Aff (Aff) +import Effect (Effect) +import Effect.Aff (Aff, Fiber, error, killFiber, launchAff_) import Effect.Aff.AVar as AV import Effect.Exception (Error) -import Foreign.Class (class Decode, class Encode) - -import Presto.Core.Types.API (class RestEndpoint, ErrorResponse, Headers, RegTokens) +import Foreign (Foreign) +import Foreign.Class (class Decode) +import Foreign.Object as Object +import Presto.Core.Types.API (class RestEndpoint, class StandardEncode, ErrorResponse, Headers, RegTokens, Response) import Presto.Core.Types.Language.APIInteract (apiInteract) import Presto.Core.Types.Language.Interaction (class Interact, Interaction, interact, interactConv) import Presto.Core.Types.Language.Storage (Key, class Serializable, serialize, deserialize) @@ -21,158 +23,213 @@ import Presto.Core.Types.Permission (Permission, PermissionStatus, PermissionRes data Authorization = RegistrationTokens RegTokens type UIResult s = Either Error s -type APIResult s = Either ErrorResponse s +type APIResult s = Either ErrorResponse (Response s) data Store = LocalStore | InMemoryStore -newtype Control s = Control (AV.AVar s) +data Control s = Control (Fiber s) (AV.AVar s) + +type St st = AV.AVar (St' st) + +type St' st = + { state :: st + , keyMap :: (Object.Object String) + , logsState :: (Object.Object Foreign) + } + +defaultState :: forall st. st -> St' st +defaultState st + = { state : st + , keyMap : Object.empty + , logsState : Object.empty + } data ErrorHandler s = ThrowError String | ReturnResult s -- | Algebra of the Flow free language. -data FlowMethodF a s +data FlowMethodF a st s = RunUI (Interaction (UIResult s)) (UIResult s -> a) | ForkUI (Interaction (UIResult s)) a | CallAPI (Interaction (APIResult s)) (APIResult s -> a) | Get Store Key (Maybe String -> a) | Set Store Key String a + | GetForeign (Object.Object Foreign -> a) + | SetForeign Key Foreign a + | GetState (st → a) + | ModifyState (st → st) (st → a) + | SetState st (Unit → a) | Delete Store Key a - | Fork (Flow s) (Control s -> a) + | Fork (Flow st s) (Control s -> a) | DoAff (Aff s) (s -> a) | Await (Control s) (s -> a) + | Kill (Control s) (Unit -> a) | Delay Milliseconds a - | OneOf (Array (Flow s)) (s -> a) - | HandleError (Flow (ErrorHandler s)) (s -> a) + | OneOf (Array (Flow st s)) (s -> a) + | HandleError (Flow st (ErrorHandler s)) (s -> a) | CheckPermissions (Array Permission) (PermissionStatus -> a) | TakePermissions (Array Permission) (Array PermissionResponse -> a) -type FlowMethod s a = FlowMethodF a s -newtype FlowWrapper a = FlowWrapper (Exists (FlowMethodF a)) +type FlowMethod st s a = FlowMethodF a st s +newtype FlowWrapper st a = FlowWrapper (Exists (FlowMethodF a st)) -- | Free monadic language for making flows. -type Flow a = Free FlowWrapper a +type Flow st a = Free (FlowWrapper st) a -- | FlowWrapper for existential type. -wrap :: forall a s. FlowMethodF a s -> Flow a +wrap :: forall a s st. FlowMethodF a st s -> Flow st a wrap = liftF <<< FlowWrapper <<< mkExists -- | Gets some string from state by key -getS :: Key -> Flow (Maybe String) +getS :: forall st. Key -> Flow st (Maybe String) getS key = wrap $ Get InMemoryStore key identity -- | Puts a string value into state using key. -setS :: Key -> String -> Flow Unit +setS :: forall st. Key -> String -> Flow st Unit setS key val = wrap $ Set InMemoryStore key val unit +setLogField :: forall st. Key -> Foreign -> Flow st Unit +setLogField key fgn = + wrap $ SetForeign key fgn unit + +getLogField :: forall st. Key -> Flow st (Maybe Foreign) +getLogField key = + wrap $ GetForeign (Object.lookup key) + +getLogFields :: forall st. Flow st (Object.Object Foreign) +getLogFields = + wrap $ GetForeign identity + +-- | Get the flow state +getState :: forall st. Flow st st +getState = wrap $ GetState identity + +-- | Set the flow state +setState :: forall st. st → Flow st Unit +setState st = wrap $ SetState st identity + +-- | modify the flow state +modifyState :: forall st. (st → st) → Flow st st +modifyState fn = wrap $ ModifyState fn identity + -- | Deletes a string value from sharedprefs using key. -delete :: Key -> Flow Unit +delete :: forall st. Key -> Flow st Unit delete key = wrap $ Delete LocalStore key unit -- | Gets some string from localStorage by key -loadS :: Key -> Flow (Maybe String) +loadS :: forall st. Key -> Flow st (Maybe String) loadS key = wrap $ Get LocalStore key identity -- | Puts a string value into the localStorage using key. -saveS :: Key -> String -> Flow Unit +saveS :: forall st. Key -> String -> Flow st Unit saveS key val = wrap $ Set LocalStore key val unit -- | Converts error to string and throws at runtime or returns result. -withError :: forall err s. (err -> String) -> Flow (Either err s) -> Flow s +withError :: forall err s st. (err -> String) -> Flow st (Either err s) -> Flow st s withError toMsg flow = wrap $ HandleError flow' identity where flow' = flow >>= either (pure <<< ThrowError <<< toMsg) (pure <<< ReturnResult) -- | Supresses error. -suppress :: forall err s. Flow (Either err s) -> Flow Unit +suppress :: forall err s st. Flow st (Either err s) -> Flow st Unit suppress = void -- | Throws error. -throwErr :: forall a. String -> Flow a +throwErr :: forall a st. String -> Flow st a throwErr msg = wrap $ HandleError flow' identity where flow' = pure $ ThrowError msg -- | Runs UI and returns result of user's interaction with a screen. -runUI' :: forall a b. Interact Error a b => a -> Flow (UIResult b) +runUI' :: forall a b st. Interact Error a b => a -> Flow st (UIResult b) runUI' a = wrap $ RunUI (interact a) identity -- | Runs UI and returns result of user's interaction with a screen. -- | Handles error in runtime. -runUI :: forall a b. Interact Error a b => a -> Flow b +runUI :: forall a b st. Interact Error a b => a -> Flow st b runUI = withError show <<< runUI' -- | Runs UI async, doesn't return anything useful. -- | Handles error in runtime. -forkUI :: forall a b. Interact Error a b => a -> Flow Unit +forkUI :: forall a b st. Interact Error a b => a -> Flow st Unit forkUI a = wrap $ ForkUI (interact a) unit -- | Runs UI and doesn't return anything useful (equivalent to `void <<< runUI`). -- | Handles error in runtime. -showUI :: forall a b. Interact Error a b => a -> Flow Unit +showUI :: forall a b st. Interact Error a b => a -> Flow st Unit showUI = void <<< runUI -- | Runs UI with a custom converter -- | Handles error in runtime. -evalUI :: forall a b s. Interact Error a b => a -> (b -> Either Error s) -> Flow s +evalUI :: forall a b s st. Interact Error a b => a -> (b -> Either Error s) -> Flow st s evalUI a from = withError show $ wrap $ RunUI (interactConv a from) identity -- | Call API being authorized. -callAPI :: forall a b. Encode a => Decode b => RestEndpoint a b - => Headers -> a -> Flow (APIResult b) +callAPI :: forall a b st. StandardEncode a => Decode b => RestEndpoint a b + => Headers -> a -> Flow st (APIResult b) callAPI headers a = wrap $ CallAPI (apiInteract a headers) identity -- | Gets some data from state and deserializes to `s` if possible. -get :: forall s. Serializable s => Key -> Flow (Maybe s) +get :: forall s st. Serializable s => Key -> Flow st (Maybe s) get key = do res <- getS key pure $ res >>= deserialize -- | Serializes a value and puts it into the state. -set :: forall s. Serializable s => Key -> s -> Flow Unit +set :: forall s st. Serializable s => Key -> s -> Flow st Unit set key val = setS key (serialize val) -- | Forks a flow and returns a control structure for getting results back (like Future). -fork :: forall s. Flow s -> Flow (Control s) +fork :: forall s st. Flow st s -> Flow st (Control s) fork flow = wrap $ Fork flow identity -- | Forks a flow and returns a void control structure. -launch :: Flow Unit -> Flow (Control Unit) +launch :: forall st. Flow st Unit -> Flow st (Control Unit) launch flow = wrap $ Fork flow identity -- | Runs any Aff as part of the flow -doAff :: forall s. Aff s -> Flow s +doAff :: forall s st. Aff s -> Flow st s doAff aff = wrap $ DoAff aff identity -- | Awaits result from a forked flow. -await :: forall s. Control s -> Flow s +await :: forall s st. Control s -> Flow st s await control = wrap $ Await control identity -- | Awaits a forked flow to be completed. -await' :: forall s. Control s -> Flow Unit +await' :: forall s st. Control s -> Flow st Unit await' control = void $ wrap $ Await control identity +-- | Kills a forked flow +kill :: forall s st. Control s -> Flow st Unit +kill control = wrap $ Kill control identity + +getKiller :: forall s st. Control s -> Flow st (Effect Unit) +getKiller (Control f _) = pure $ launchAff_ (killFiber (error "Received termination") f) + +getFiber :: forall s st. Control s -> Flow st (Fiber s) +getFiber (Control f _) = pure f + -- | Delays computation for the given amount of time. -delay :: forall d. Duration d => d -> Flow Unit +delay :: forall d st. Duration d => d -> Flow st Unit delay duration = wrap $ Delay (fromDuration duration) unit -- | Executes a set of actions and returns when the first one is done -oneOf :: forall s. Array (Flow s) -> Flow s +oneOf :: forall s st. Array (Flow st s) -> Flow st s oneOf flows = wrap $ OneOf flows identity -- | Gets some data from local storage and deserializes to `s` if possible. -load :: forall s. Serializable s => Key -> Flow (Maybe s) +load :: forall s st. Serializable s => Key -> Flow st (Maybe s) load key = do res <- loadS key pure $ res >>= deserialize -- | Serializes a value and puts it into the local storage. -save :: forall s. Serializable s => Key -> s -> Flow Unit +save :: forall s st. Serializable s => Key -> s -> Flow st Unit save key val = saveS key (serialize val) -- | Checks if permissions granted. -checkPermissions :: Array Permission -> Flow PermissionStatus +checkPermissions :: forall st. Array Permission -> Flow st PermissionStatus checkPermissions permissions = wrap $ CheckPermissions permissions identity -- | Tries to aquire permissions. -takePermissions :: Array Permission -> Flow (Array PermissionResponse) +takePermissions :: forall st. Array Permission -> Flow st (Array PermissionResponse) takePermissions permissions = wrap $ TakePermissions permissions identity diff --git a/src/Presto/Core/Types/Permission.purs b/src/Presto/Core/Types/Permission.purs index 0cad84f..4ff7010 100644 --- a/src/Presto/Core/Types/Permission.purs +++ b/src/Presto/Core/Types/Permission.purs @@ -8,7 +8,7 @@ import Prelude import Data.Tuple (Tuple) import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show (genericShow) +import Data.Show.Generic (genericShow) import Foreign.Class (class Decode, class Encode) import Foreign.Generic (defaultOptions, genericDecode, genericEncode) diff --git a/src/Presto/Core/Utils/Encoding.js b/src/Presto/Core/Utils/Encoding.js new file mode 100644 index 0000000..55ecc8a --- /dev/null +++ b/src/Presto/Core/Utils/Encoding.js @@ -0,0 +1,3 @@ +export const unsafeStringify = x => { + return JSON.stringify(x); +}; diff --git a/src/Presto/Core/Utils/Encoding.purs b/src/Presto/Core/Utils/Encoding.purs index 129e6f4..e196c45 100644 --- a/src/Presto/Core/Utils/Encoding.purs +++ b/src/Presto/Core/Utils/Encoding.purs @@ -5,17 +5,18 @@ module Presto.Core.Utils.Encoding , defaultEnumEncode , defaultDecodeJSON , defaultEncodeJSON + , unsafeStringify ) where import Prelude import Data.Generic.Rep (class Generic) import Foreign (Foreign, F) -import Foreign.Generic (defaultOptions, genericDecode, genericDecodeJSON, genericEncode, genericEncodeJSON) +import Foreign.Generic (Options, defaultOptions, genericDecode, genericDecodeJSON, genericEncode, genericEncodeJSON) import Foreign.Generic.Class (class GenericDecode, class GenericEncode) import Foreign.Generic.EnumEncoding (class GenericDecodeEnum, class GenericEncodeEnum, genericDecodeEnum, genericEncodeEnum) -import Foreign.Generic.Types (Options) +foreign import unsafeStringify :: forall a. a -> String options :: Options options = defaultOptions { unwrapSingleConstructors = true } diff --git a/test/Language/ApiInteractionTest.purs b/test/Language/ApiInteractionTest.purs index df67584..6867c15 100644 --- a/test/Language/ApiInteractionTest.purs +++ b/test/Language/ApiInteractionTest.purs @@ -5,7 +5,7 @@ import Prelude import Control.Monad.State.Trans as S import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Eq as GEq -import Data.Generic.Rep.Show as GShow +import Data.Generic.Rep as GShow import Data.Map (empty) import Effect.Aff (Aff) import Foreign.Class (class Decode, class Encode, encode) diff --git a/test/Language/UIInteractionTest.purs b/test/Language/UIInteractionTest.purs index 36d08c1..ec3ea4c 100644 --- a/test/Language/UIInteractionTest.purs +++ b/test/Language/UIInteractionTest.purs @@ -5,7 +5,7 @@ import Control.Monad.State.Trans as S import Data.Map (empty) import Data.Either (Either(..)) import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show as GShow +import Data.Generic.Rep as GShow import Data.Generic.Rep.Eq as GEq import Effect.Aff (Aff) import Effect.Exception (error, Error)