packages feed

flink-statefulfun 0.3.0.0 → 0.4.0.0

raw patch · 7 files changed

+348/−313 lines, 7 filesdep +microlensdep +timedep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: microlens, time, uuid

API changes (from Hackage documentation)

- Network.Flink.Internal.Stateful: [getMessage] :: ProtoSerde a -> a
- Network.Flink.Internal.Stateful: jsonInput :: (Serde s, Json a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Network.Flink.Internal.Stateful: protoInput :: (Serde s, Message a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Network.Flink.Internal.Stateful: sendByteMsg :: (StatefulFunc s m, Serde a) => (Text, Text, Text) -> a -> m ()
- Network.Flink.Internal.Stateful: sendByteMsgDelay :: (StatefulFunc s m, Serde a) => (Text, Text, Text) -> Int -> a -> m ()
- Network.Flink.Internal.Stateful: serdeInput :: (Serde s, Serde a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Network.Flink.Stateful: [getMessage] :: ProtoSerde a -> a
- Network.Flink.Stateful: jsonInput :: (Serde s, Json a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Network.Flink.Stateful: protoInput :: (Serde s, Message a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Network.Flink.Stateful: sendByteMsg :: (StatefulFunc s m, Serde a) => (Text, Text, Text) -> a -> m ()
- Network.Flink.Stateful: sendByteMsgDelay :: (StatefulFunc s m, Serde a) => (Text, Text, Text) -> Int -> a -> m ()
- Network.Flink.Stateful: serdeInput :: (Serde s, Serde a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'DelayedInvocation "argument" Proto.Google.Protobuf.Any.Any
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'DelayedInvocation "maybe'argument" (GHC.Maybe.Maybe Proto.Google.Protobuf.Any.Any)
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'EgressMessage "argument" Proto.Google.Protobuf.Any.Any
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'EgressMessage "maybe'argument" (GHC.Maybe.Maybe Proto.Google.Protobuf.Any.Any)
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'Invocation "argument" Proto.Google.Protobuf.Any.Any
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'Invocation "maybe'argument" (GHC.Maybe.Maybe Proto.Google.Protobuf.Any.Any)
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueMutation "stateValue" Data.ByteString.Internal.ByteString
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'Invocation "argument" Proto.Google.Protobuf.Any.Any
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'Invocation "maybe'argument" (GHC.Maybe.Maybe Proto.Google.Protobuf.Any.Any)
- Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'PersistedValue "stateValue" Data.ByteString.Internal.ByteString
+ Network.Flink.Internal.Stateful: AFTER_CALL :: ExpirationMode
+ Network.Flink.Internal.Stateful: AFTER_WRITE :: ExpirationMode
+ Network.Flink.Internal.Stateful: Address :: FuncType -> Text -> Address
+ Network.Flink.Internal.Stateful: EmptyArgumentPassed :: FlinkError
+ Network.Flink.Internal.Stateful: Expiration :: ExpirationMode -> NominalDiffTime -> Expiration
+ Network.Flink.Internal.Stateful: FuncType :: Text -> Text -> FuncType
+ Network.Flink.Internal.Stateful: InvalidTypePassedError :: Text -> Text -> FlinkError
+ Network.Flink.Internal.Stateful: NONE :: ExpirationMode
+ Network.Flink.Internal.Stateful: [emode] :: Expiration -> ExpirationMode
+ Network.Flink.Internal.Stateful: [expireAfterMillis] :: Expiration -> NominalDiffTime
+ Network.Flink.Internal.Stateful: [getProto] :: ProtoSerde a -> a
+ Network.Flink.Internal.Stateful: data Address
+ Network.Flink.Internal.Stateful: data Expiration
+ Network.Flink.Internal.Stateful: data ExpirationMode
+ Network.Flink.Internal.Stateful: data FuncType
+ Network.Flink.Internal.Stateful: instance GHC.Classes.Eq Network.Flink.Internal.Stateful.Expiration
+ Network.Flink.Internal.Stateful: instance GHC.Classes.Eq Network.Flink.Internal.Stateful.ExpirationMode
+ Network.Flink.Internal.Stateful: instance GHC.Classes.Eq Network.Flink.Internal.Stateful.FuncType
+ Network.Flink.Internal.Stateful: instance GHC.Classes.Ord Network.Flink.Internal.Stateful.FuncType
+ Network.Flink.Internal.Stateful: instance GHC.Show.Show Network.Flink.Internal.Stateful.Expiration
+ Network.Flink.Internal.Stateful: instance GHC.Show.Show Network.Flink.Internal.Stateful.ExpirationMode
+ Network.Flink.Internal.Stateful: instance GHC.Show.Show Network.Flink.Internal.Stateful.FuncRes
+ Network.Flink.Internal.Stateful: pattern Address' :: Text -> Text -> Text -> Address
+ Network.Flink.Internal.Stateful: sendProtoMsg :: (StatefulFunc s m, Message a) => Address -> a -> m ()
+ Network.Flink.Internal.Stateful: sendProtoMsgDelay :: (StatefulFunc s m, Message a) => Address -> NominalDiffTime -> a -> m ()
+ Network.Flink.Internal.Stateful: tpName :: Serde a => Proxy a -> Text
+ Network.Flink.Stateful: AFTER_CALL :: ExpirationMode
+ Network.Flink.Stateful: AFTER_WRITE :: ExpirationMode
+ Network.Flink.Stateful: Address :: FuncType -> Text -> Address
+ Network.Flink.Stateful: Expiration :: ExpirationMode -> NominalDiffTime -> Expiration
+ Network.Flink.Stateful: FuncType :: Text -> Text -> FuncType
+ Network.Flink.Stateful: NONE :: ExpirationMode
+ Network.Flink.Stateful: [emode] :: Expiration -> ExpirationMode
+ Network.Flink.Stateful: [expireAfterMillis] :: Expiration -> NominalDiffTime
+ Network.Flink.Stateful: [getProto] :: ProtoSerde a -> a
+ Network.Flink.Stateful: data Address
+ Network.Flink.Stateful: data Expiration
+ Network.Flink.Stateful: data ExpirationMode
+ Network.Flink.Stateful: data FuncType
+ Network.Flink.Stateful: pattern Address' :: Text -> Text -> Text -> Address
+ Network.Flink.Stateful: sendProtoMsg :: (StatefulFunc s m, Message a) => Address -> a -> m ()
+ Network.Flink.Stateful: sendProtoMsgDelay :: (StatefulFunc s m, Message a) => Address -> NominalDiffTime -> a -> m ()
+ Network.Flink.Stateful: tpName :: Serde a => Proxy a -> Text
+ Proto.RequestReply: FromFunction'ExpirationSpec'AFTER_INVOKE :: FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: FromFunction'ExpirationSpec'AFTER_WRITE :: FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: FromFunction'ExpirationSpec'ExpireMode'Unrecognized :: !FromFunction'ExpirationSpec'ExpireMode'UnrecognizedValue -> FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: FromFunction'ExpirationSpec'NONE :: FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: FromFunction'IncompleteInvocationContext' :: !FromFunction'IncompleteInvocationContext -> FromFunction'Response
+ Proto.RequestReply: _FromFunction'IncompleteInvocationContext' :: Prism' FromFunction'Response FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: data FromFunction'ExpirationSpec
+ Proto.RequestReply: data FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: data FromFunction'ExpirationSpec'ExpireMode'UnrecognizedValue
+ Proto.RequestReply: data FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: data FromFunction'PersistedValueSpec
+ Proto.RequestReply: data TypedValue
+ Proto.RequestReply: instance Control.DeepSeq.NFData Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance Control.DeepSeq.NFData Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance Control.DeepSeq.NFData Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance Control.DeepSeq.NFData Proto.RequestReply.FromFunction'PersistedValueSpec
+ Proto.RequestReply: instance Control.DeepSeq.NFData Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction "incompleteInvocationContext" Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction "maybe'incompleteInvocationContext" (GHC.Maybe.Maybe Proto.RequestReply.FromFunction'IncompleteInvocationContext)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'DelayedInvocation "argument" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'DelayedInvocation "maybe'argument" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'EgressMessage "argument" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'EgressMessage "maybe'argument" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'ExpirationSpec "expireAfterMillis" GHC.Int.Int64
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'ExpirationSpec "mode" Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'IncompleteInvocationContext "missingValues" [Proto.RequestReply.FromFunction'PersistedValueSpec]
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'IncompleteInvocationContext "vec'missingValues" (Data.Vector.Vector Proto.RequestReply.FromFunction'PersistedValueSpec)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'Invocation "argument" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'Invocation "maybe'argument" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueMutation "maybe'stateValue" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueMutation "stateValue" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueSpec "expirationSpec" Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueSpec "maybe'expirationSpec" (GHC.Maybe.Maybe Proto.RequestReply.FromFunction'ExpirationSpec)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueSpec "stateName" Data.Text.Internal.Text
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.FromFunction'PersistedValueSpec "typeTypename" Data.Text.Internal.Text
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'Invocation "argument" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'Invocation "maybe'argument" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'PersistedValue "maybe'stateValue" (GHC.Maybe.Maybe Proto.RequestReply.TypedValue)
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.ToFunction'PersistedValue "stateValue" Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.TypedValue "hasValue" GHC.Types.Bool
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.TypedValue "typename" Data.Text.Internal.Text
+ Proto.RequestReply: instance Data.ProtoLens.Field.HasField Proto.RequestReply.TypedValue "value" Data.ByteString.Internal.ByteString
+ Proto.RequestReply: instance Data.ProtoLens.Message.FieldDefault Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance Data.ProtoLens.Message.Message Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance Data.ProtoLens.Message.Message Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance Data.ProtoLens.Message.Message Proto.RequestReply.FromFunction'PersistedValueSpec
+ Proto.RequestReply: instance Data.ProtoLens.Message.Message Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance Data.ProtoLens.Message.MessageEnum Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode'UnrecognizedValue
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.FromFunction'PersistedValueSpec
+ Proto.RequestReply: instance GHC.Classes.Eq Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode'UnrecognizedValue
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.FromFunction'PersistedValueSpec
+ Proto.RequestReply: instance GHC.Classes.Ord Proto.RequestReply.TypedValue
+ Proto.RequestReply: instance GHC.Enum.Bounded Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Enum.Enum Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.FromFunction'ExpirationSpec
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.FromFunction'ExpirationSpec'ExpireMode'UnrecognizedValue
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.FromFunction'IncompleteInvocationContext
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.FromFunction'PersistedValueSpec
+ Proto.RequestReply: instance GHC.Show.Show Proto.RequestReply.TypedValue
+ Proto.RequestReply_Fields: expirationSpec :: forall f s a. (Functor f, HasField s "expirationSpec" a) => LensLike' f s a
+ Proto.RequestReply_Fields: expireAfterMillis :: forall f s a. (Functor f, HasField s "expireAfterMillis" a) => LensLike' f s a
+ Proto.RequestReply_Fields: hasValue :: forall f s a. (Functor f, HasField s "hasValue" a) => LensLike' f s a
+ Proto.RequestReply_Fields: incompleteInvocationContext :: forall f s a. (Functor f, HasField s "incompleteInvocationContext" a) => LensLike' f s a
+ Proto.RequestReply_Fields: maybe'expirationSpec :: forall f s a. (Functor f, HasField s "maybe'expirationSpec" a) => LensLike' f s a
+ Proto.RequestReply_Fields: maybe'incompleteInvocationContext :: forall f s a. (Functor f, HasField s "maybe'incompleteInvocationContext" a) => LensLike' f s a
+ Proto.RequestReply_Fields: maybe'stateValue :: forall f s a. (Functor f, HasField s "maybe'stateValue" a) => LensLike' f s a
+ Proto.RequestReply_Fields: missingValues :: forall f s a. (Functor f, HasField s "missingValues" a) => LensLike' f s a
+ Proto.RequestReply_Fields: mode :: forall f s a. (Functor f, HasField s "mode" a) => LensLike' f s a
+ Proto.RequestReply_Fields: typeTypename :: forall f s a. (Functor f, HasField s "typeTypename" a) => LensLike' f s a
+ Proto.RequestReply_Fields: typename :: forall f s a. (Functor f, HasField s "typename" a) => LensLike' f s a
+ Proto.RequestReply_Fields: value :: forall f s a. (Functor f, HasField s "value" a) => LensLike' f s a
+ Proto.RequestReply_Fields: vec'missingValues :: forall f s a. (Functor f, HasField s "vec'missingValues" a) => LensLike' f s a
- Network.Flink.Internal.Stateful: flinkWrapper :: Serde s => (Any -> Function s ()) -> ByteString -> Env -> ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString))
+ Network.Flink.Internal.Stateful: flinkWrapper :: forall a s. (Serde a, Serde s) => s -> Expiration -> (a -> Function s ()) -> FuncExec
- Network.Flink.Internal.Stateful: jsonState :: Json s => (a -> Function s ()) -> a -> Function (JsonSerde s) ()
+ Network.Flink.Internal.Stateful: jsonState :: Json s => Function s () -> Function (JsonSerde s) ()
- Network.Flink.Internal.Stateful: protoState :: Message s => (a -> Function s ()) -> a -> Function (ProtoSerde s) ()
+ Network.Flink.Internal.Stateful: protoState :: Message s => Function s () -> Function (ProtoSerde s) ()
- Network.Flink.Internal.Stateful: sendMsg :: (StatefulFunc s m, Message a) => (Text, Text, Text) -> a -> m ()
+ Network.Flink.Internal.Stateful: sendMsg :: (StatefulFunc s m, Serde a) => Address -> a -> m ()
- Network.Flink.Internal.Stateful: sendMsgDelay :: (StatefulFunc s m, Message a) => (Text, Text, Text) -> Int -> a -> m ()
+ Network.Flink.Internal.Stateful: sendMsgDelay :: (StatefulFunc s m, Serde a) => Address -> NominalDiffTime -> a -> m ()
- Network.Flink.Internal.Stateful: type FunctionTable = Map (Text, Text) (ByteString, ByteString -> Env -> ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString)))
+ Network.Flink.Internal.Stateful: type FunctionTable = Map FuncType FuncExec
- Network.Flink.Stateful: flinkWrapper :: Serde s => (Any -> Function s ()) -> ByteString -> Env -> ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString))
+ Network.Flink.Stateful: flinkWrapper :: forall a s. (Serde a, Serde s) => s -> Expiration -> (a -> Function s ()) -> FuncExec
- Network.Flink.Stateful: jsonState :: Json s => (a -> Function s ()) -> a -> Function (JsonSerde s) ()
+ Network.Flink.Stateful: jsonState :: Json s => Function s () -> Function (JsonSerde s) ()
- Network.Flink.Stateful: protoState :: Message s => (a -> Function s ()) -> a -> Function (ProtoSerde s) ()
+ Network.Flink.Stateful: protoState :: Message s => Function s () -> Function (ProtoSerde s) ()
- Network.Flink.Stateful: sendMsg :: (StatefulFunc s m, Message a) => (Text, Text, Text) -> a -> m ()
+ Network.Flink.Stateful: sendMsg :: (StatefulFunc s m, Serde a) => Address -> a -> m ()
- Network.Flink.Stateful: sendMsgDelay :: (StatefulFunc s m, Message a) => (Text, Text, Text) -> Int -> a -> m ()
+ Network.Flink.Stateful: sendMsgDelay :: (StatefulFunc s m, Serde a) => Address -> NominalDiffTime -> a -> m ()
- Network.Flink.Stateful: type FunctionTable = Map (Text, Text) (ByteString, ByteString -> Env -> ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString)))
+ Network.Flink.Stateful: type FunctionTable = Map FuncType FuncExec

Files

README.md view
@@ -82,7 +82,7 @@   newCount <- (+ 1) <$> insideCtx greeterStateCount   let respMsg = "Saw " <> T.unpack name <> " " <> show newCount <> " time(s)" -  sendMsg ("printing", "printer") (response $ T.pack respMsg)+  sendMsgProto ("printing", "printer") (response $ T.pack respMsg)   modifyCtx (\old -> old {greeterStateCount = newCount})   where     name = msg ^. EX.name@@ -115,17 +115,15 @@   putStrLn "http://localhost:8000/"   run 8000 (logStdout $ createApp functionTable) -greeterState = BSL.toStrict . encode $ GreeterState 0- functionTable :: FunctionTable functionTable =   Map.fromList-    [ (("printing", "printer"), ("", flinkWrapper . protoInput $ printer)),-      (("greeting", "counter"), (greeterState, flinkWrapper . protoInput . jsonState $ counter))+    [ ((FuncType "greeting" "greeterEntry"), flinkWrapper () (Expiration NONE 0) (greeterEntry . getProto)),+      ((FuncType  "greeting" "counter"), flinkWrapper (JsonSerde (GreeterState 0)) (Expiration AFTER_CALL 5) (jsonState . counter . getProto))     ] ``` -The `FunctionTable` is a Map of `(namespace, functionType)` to `(initialState, wrappedFunction)`.+The `FunctionTable` is a Map of `(namespace, functionType)` to `wrappedFunction`. `jsonState` is a helper to serialize your function state as `JSON` for storage in the flink backend. `protoState` can also be used if that is your preference. `flinkWrapper` transforms your function into one that takes arbitrary `ByteString`s so that every function in the@@ -143,30 +141,18 @@ To use the functions that are now served from the API, we now need to declare it in the `module.yaml`.  ```yaml-version: "1.0"+version: "3.0" module:   meta:     type: remote   spec:-    functions:-      - function:-          meta:-            kind: http-            type: greeting/counter-          spec:-            endpoint: http://localhost:8000/statefun-            states:-              - flink_state-            maxNumBatchRequests: 500-            timeout: 2min-      - function:-          meta:-            kind: http-            type: printing/printer-          spec:-            endpoint: http://localhost:8000/statefun-            maxNumBatchRequests: 500-            timeout: 2min+    endpoints:+      - endpoint:+        meta: +          kind: http+        spec:+          functions: greeting/*+          urlPathTemplate: http://localhost:8000/statefun ```  Flink Statefun supports multiple states, but for simplicity the SDK just serializes the whole
@@ -1,7 +1,7 @@ cabal-version:       2.4  name:                flink-statefulfun-version:             0.3.0.0+version:             0.4.0.0 synopsis:            Flink stateful functions SDK description:     Typeclasses for serving Flink stateful functions@@ -20,7 +20,8 @@                      README.md                      proto/**/*.proto -tested-with: GHC==8.10.2,+tested-with: GHC==8.10.5,+             GHC==8.10.2,              GHC==8.8.4,              GHC==8.8.3,              GHC==8.6.5@@ -55,6 +56,7 @@                      , warp >= 3 && < 3.4                      , http-types >= 0.10 && < 0.13                      , http-media >= 0.7 && < 0.9+                     , microlens == 0.4.12.0                      , mtl >= 2 && < 2.3                      , lens-family >= 2 && < 2.2                      , proto-lens >= 0.5 && < 0.8@@ -62,6 +64,8 @@                      , proto-lens-protobuf-types >= 0.5 && < 0.8                      , servant >= 0.16 && < 0.19                      , servant-server >= 0.16 && < 0.19+                     , time == 1.9.3+                     , uuid == 1.3.15    default-extensions:  OverloadedStrings                        QuasiQuotes@@ -79,5 +83,8 @@                        MultiParamTypeClasses                        FunctionalDependencies                        ConstraintKinds+                       ScopedTypeVariables+                       TypeApplications+                       RankNTypes    default-language:    Haskell2010
− proto/Example.proto
@@ -1,11 +0,0 @@-syntax = "proto3";--package example;--message GreeterRequest {-  string name = 1;-}--message GreeterResponse {-  string greeting = 1;-}
proto/Kafka.proto view
@@ -16,14 +16,15 @@  * limitations under the License.  */ -syntax = "proto3";--package org.apache.flink.statefun.flink.io;-option java_package = "org.apache.flink.statefun.flink.io.generated";-option java_multiple_files = true;+ syntax = "proto3"; -message KafkaProducerRecord {-    string key = 1;-    bytes value_bytes = 2;-    string topic = 3;-}+ package io.statefun.sdk.egress;+ + option java_package = "org.apache.flink.statefun.sdk.egress.generated";+ option java_multiple_files = true;+ + message KafkaProducerRecord {+     string key = 1;+     bytes value_bytes = 2;+     string topic = 3;+ }
proto/RequestReply.proto view
@@ -16,130 +16,165 @@  * limitations under the License.  */ -syntax = "proto3";--package org.apache.flink.statefun.flink.core.polyglot;-option java_package = "org.apache.flink.statefun.flink.core.polyglot.generated";-option java_multiple_files = true;--import "google/protobuf/any.proto";--// --------------------------------------------------------------------------------------------------------------------// Common message definitions-// ---------------------------------------------------------------------------------------------------------------------// An Address is the unique identity of an individual StatefulFunction, containing-// a function's type and an unique identifier within the type. The function's-// type denotes the "class" of function to invoke, while the unique identifier addresses the-// invocation to a specific function instance.-message Address {-    string namespace = 1;-    string type = 2;-    string id = 3;-}--// --------------------------------------------------------------------------------------------------------------------// Messages sent to a Remote Function  -// ---------------------------------------------------------------------------------------------------------------------// The following section contains all the message types that are sent -// from Flink to a remote function.-message ToFunction {-    // PersistedValue represents a PersistedValue's value that is managed by Flink on behalf of a remote function. -    message PersistedValue {-        // The unique name of the persisted state.-        string state_name = 1;-        // The serialized state value-        bytes state_value = 2;-    }--    // Invocation represents a remote function call, it associated with an (optional) return address,-    // and an argument. -    message Invocation {-        // The address of the function that requested the invocation (possibly absent)-        Address caller = 1;-        // The invocation argument (aka the message sent to the target function)-        google.protobuf.Any argument = 2;-    }--    // InvocationBatchRequest represents a request to invoke a remote function. It is always associated with a target-    // address (the function to invoke), a list of eager state values.-    message InvocationBatchRequest {-        // The address of the function to invoke-        Address target = 1;-        // A list of PersistedValues that were registered as an eager state.-        repeated PersistedValue state = 2;-        // A non empty (at least one) list of invocations-        repeated Invocation invocations = 3;-    }--    oneof request {-        InvocationBatchRequest invocation = 100;-    }-}--// --------------------------------------------------------------------------------------------------------------------// Messages sent from a Remote Function  -// ---------------------------------------------------------------------------------------------------------------------// The following section contains messages sent from a remote function back to Flink. -message FromFunction {-    // MutatePersistedValueCommand represents a command sent from a remote function to Flink,-    // requesting a change to a persisted value.-    message PersistedValueMutation {-        enum MutationType {-            DELETE = 0;-            MODIFY = 1;-        }-        MutationType mutation_type = 1;-        string state_name = 2;-        bytes state_value = 3;-    }--    // Invocation represents a remote function call, it associated with a (mandatory) target address,-    // and an argument. -    message Invocation {-        // The target function to invoke -        Address target = 1;-        // The invocation argument (aka the message sent to the target function)-        google.protobuf.Any argument = 2;-    }--    // DelayedInvocation represents a delayed remote function call with a target address, an argument-    // and a delay in milliseconds, after which this message to be sent.-    message DelayedInvocation {-        // the amount of milliseconds to wait before sending this message-        int64 delay_in_ms = 1;-        // the target address to send this message to-        Address target = 2;-        // the invocation argument-        google.protobuf.Any argument = 3;-    }--    // EgressMessage an argument to forward to an egress.-    // An egress is identified by a namespace and type (see EgressIdentifier SDK class).-    // The argument is a google.protobuf.Any-    message EgressMessage {-        // The target egress namespace-        string egress_namespace = 1;-        // The target egress type-        string egress_type = 2;-        // egress argument-        google.protobuf.Any argument = 3;-    }--    // InvocationResponse represents a result of an org.apache.flink.statefun.flink.core.polyglot.ToFunction.InvocationBatchRequest-    // it contains a list of state mutation to preform as a result of computing this batch, and a list of outgoing messages.-    message InvocationResponse {-        repeated PersistedValueMutation state_mutations = 1;-        repeated Invocation outgoing_messages = 2;-        repeated DelayedInvocation delayed_invocations = 3;-        repeated EgressMessage outgoing_egresses = 4;-    }--    oneof response {-        InvocationResponse invocation_result = 100;-    }-}-+ syntax = "proto3"; + package io.statefun.sdk.reqreply;+ + option java_package = "org.apache.flink.statefun.sdk.reqreply.generated";+ option java_multiple_files = true;+ + // -------------------------------------------------------------------------------------------------------------------+ // Common message definitions+ // -------------------------------------------------------------------------------------------------------------------+ + // An Address is the unique identity of an individual StatefulFunction, containing+ // a function's type and an unique identifier within the type. The function's+ // type denotes the "class" of function to invoke, while the unique identifier addresses the+ // invocation to a specific function instance.+ message Address {+     string namespace = 1;+     string type = 2;+     string id = 3;+ }+ + message TypedValue {+     string typename = 1;+     // has_value is set to differentiate a zero length value bytes explicitly set,+     // or a non existing value.+     bool has_value = 2;+     bytes value = 3;+ }+ + // -------------------------------------------------------------------------------------------------------------------+ // Messages sent to a Remote Function  + // -------------------------------------------------------------------------------------------------------------------+ + // The following section contains all the message types that are sent + // from Flink to a remote function.+ message ToFunction {+     // PersistedValue represents a PersistedValue's value that is managed by Flink on behalf of a remote function. +     message PersistedValue {+         // The unique name of the persisted state.+         string state_name = 1;+         // The serialized state value+         TypedValue state_value = 2;+     }+ +     // Invocation represents a remote function call, it associated with an (optional) return address,+     // and an argument. +     message Invocation {+         // The address of the function that requested the invocation (possibly absent)+         Address caller = 1;+         // The invocation argument (aka the message sent to the target function)+         TypedValue argument = 2;+     }+ +     // InvocationBatchRequest represents a request to invoke a remote function. It is always associated with a target+     // address (the function to invoke), and a list of values for registered state.+     message InvocationBatchRequest {+         // The address of the function to invoke+         Address target = 1;+         // A list of PersistedValues that were registered as a persisted state.+         repeated PersistedValue state = 2;+         // A non empty (at least one) list of invocations+         repeated Invocation invocations = 3;+     }+ +     oneof request {+         InvocationBatchRequest invocation = 100;+     }+ }+ + // -------------------------------------------------------------------------------------------------------------------+ // Messages sent from a Remote Function  + // -------------------------------------------------------------------------------------------------------------------+ + // The following section contains messages sent from a remote function back to Flink. + message FromFunction {+     // MutatePersistedValueCommand represents a command sent from a remote function to Flink,+     // requesting a change to a persisted value.+     message PersistedValueMutation {+         enum MutationType {+             DELETE = 0;+             MODIFY = 1;+         }+         MutationType mutation_type = 1;+         string state_name = 2;+         TypedValue state_value = 3;+     }+ +     // Invocation represents a remote function call, it associated with a (mandatory) target address,+     // and an argument. +     message Invocation {+         // The target function to invoke +         Address target = 1;+         // The invocation argument (aka the message sent to the target function)+         TypedValue argument = 2;+     }+ +     // DelayedInvocation represents a delayed remote function call with a target address, an argument+     // and a delay in milliseconds, after which this message to be sent.+     message DelayedInvocation {+         // the amount of milliseconds to wait before sending this message+         int64 delay_in_ms = 1;+         // the target address to send this message to+         Address target = 2;+         // the invocation argument+         TypedValue argument = 3;+     }+ +     // EgressMessage an argument to forward to an egress.+     // An egress is identified by a namespace and type (see EgressIdentifier SDK class).+     // The argument is an io.statefun.sdk.reqreply.TypedValue.+     message EgressMessage {+         // The target egress namespace+         string egress_namespace = 1;+         // The target egress type+         string egress_type = 2;+         // egress argument+         TypedValue argument = 3;+     }+ +     // InvocationResponse represents a result of an io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest+     // it contains a list of state mutation to preform as a result of computing this batch, and a list of outgoing messages.+     message InvocationResponse {+         repeated PersistedValueMutation state_mutations = 1;+         repeated Invocation outgoing_messages = 2;+         repeated DelayedInvocation delayed_invocations = 3;+         repeated EgressMessage outgoing_egresses = 4;+     }+ +     // ExpirationSpec represents TTL (Time-To-Live) configuration for persisted states.+     message ExpirationSpec {+         enum ExpireMode {+             NONE = 0;+             AFTER_WRITE = 1;+             AFTER_INVOKE = 2;+         }+         ExpireMode mode = 1;+         int64 expire_after_millis = 2;+     }+ +     // PersistedValueSpec represents specifications of a function's persisted value state.+     message PersistedValueSpec {+         string state_name = 1;+         ExpirationSpec expiration_spec = 2;+         string type_typename = 3;+     }+ +     // IncompleteInvocationContext represents a result of an io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest,+     // which should be used as the response if the InvocationBatchRequest provided incomplete information about the+     // invocation, e.g. insufficient state values were provided.+     message IncompleteInvocationContext {+         repeated PersistedValueSpec missing_values = 1;+     }+ +     // Response sent from the function, as a result of an io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest.+     // It can be one of the following types:+     //   - io.statefun.sdk.reqreply.FromFunction.InvocationResponse+     //   - io.statefun.sdk.reqreply.FromFunction.IncompleteInvocationContext+     oneof response {+         InvocationResponse invocation_result = 100;+         IncompleteInvocationContext incomplete_invocation_context = 101;+     }+ } 
src/Network/Flink/Internal/Stateful.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE PatternSynonyms #-} module Network.Flink.Internal.Stateful   ( StatefulFunc       ( insideCtx,@@ -6,28 +7,29 @@         modifyCtx,         sendMsg,         sendMsgDelay,-        sendEgressMsg,-        sendByteMsg,-        sendByteMsgDelay+        sendEgressMsg       ),     flinkWrapper,     createApp,     flinkServer,     flinkApi,+    Address(.., Address'),+    FuncType (..),     Function (..),     Serde (..),     FunctionState (..),     FlinkError (..),     FunctionTable,     Env (..),+    Expiration(..),+    ExpirationMode(..),     newState,     ProtoSerde (..),     JsonSerde (..),     jsonState,     protoState,-    serdeInput,-    protoInput,-    jsonInput,+    sendProtoMsg,+    sendProtoMsgDelay   ) where @@ -41,29 +43,38 @@ import Data.Foldable (Foldable (toList)) import Data.Map (Map) import qualified Data.Map as Map-import Data.Maybe (listToMaybe)-import Data.ProtoLens (Message, defMessage, encodeMessage)+import Data.ProtoLens (Message, defMessage, encodeMessage, messageName) import Data.ProtoLens.Any (UnpackError)-import qualified Data.ProtoLens.Any as Any import Data.ProtoLens.Encoding (decodeMessage) import Data.ProtoLens.Prism import Data.Sequence (Seq) import qualified Data.Sequence as Seq import Data.Text (Text) import Data.Text.Lazy (fromStrict)+import Data.Coerce ( coerce ) import qualified Data.Text.Lazy.Encoding as T import Lens.Family2+import Lens.Micro ( traversed, filtered ) import Network.Flink.Internal.ProtoServant (Proto)-import Proto.Google.Protobuf.Any (Any)-import qualified Proto.Google.Protobuf.Any_Fields as Any import Proto.RequestReply (FromFunction, ToFunction) import qualified Proto.RequestReply as PR import qualified Proto.RequestReply_Fields as PR import Servant ---- | Table of stateful functions `(functionNamespace, functionType) -> (initialState, function)-type FunctionTable = Map (Text, Text) (ByteString, ByteString -> Env -> PR.ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString)))+import Data.Time.Clock ( NominalDiffTime ) +data FuncType = FuncType Text Text deriving (Eq, Ord)+data Address = Address FuncType Text++{-# COMPLETE Address' #-}+pattern Address' :: Text -> Text -> Text -> Address+pattern Address' fnamespace fnTp fid = Address (FuncType fnamespace fnTp) fid++data FuncRes = IncompleteContext Expiration Text | UpdatedState (FunctionState PR.TypedValue) deriving Show+type FuncExec = Env -> PR.ToFunction'InvocationBatchRequest -> IO (Either FlinkError FuncRes)+--- | Table of stateful functions `(functionNamespace, functionType) -> function+type FunctionTable = Map FuncType FuncExec+ data Env = Env   { envFunctionNamespace :: Text,     envFunctionType :: Text,@@ -83,22 +94,35 @@ newState :: a -> FunctionState a newState initialCtx = FunctionState initialCtx False mempty mempty mempty +data ExpirationMode =  NONE | AFTER_WRITE | AFTER_CALL deriving (Show, Eq)++data Expiration = Expiration {+  emode :: ExpirationMode,+  expireAfterMillis :: NominalDiffTime+} deriving (Show, Eq)+ -- | Monad stack used for the execution of a Flink stateful function -- Don't reference this directly in your code if possible newtype Function s a = Function {runFunction :: ExceptT FlinkError (StateT (FunctionState s) (ReaderT Env IO)) a}   deriving (Monad, Applicative, Functor, MonadState (FunctionState s), MonadError FlinkError, MonadIO, MonadReader Env)  class Serde a where+  -- | Type name+  tpName :: Proxy a -> Text+   -- | decodes types from strict 'ByteString's   deserializeBytes :: ByteString -> Either String a    -- | encodes types to strict 'ByteString's   serializeBytes :: a -> ByteString -newtype ProtoSerde a = ProtoSerde {getMessage :: a}+newtype ProtoSerde a = ProtoSerde {getProto :: a}   deriving (Functor)  instance Message a => Serde (ProtoSerde a) where+  tpName px = "type.googleapis.com/" <> messageName (unliftP px)+    where unliftP :: Proxy (f a) -> Proxy a+          unliftP Proxy = Proxy   deserializeBytes a = ProtoSerde <$> decodeMessage a   serializeBytes (ProtoSerde a) = encodeMessage a @@ -108,18 +132,22 @@   deriving (Functor)  instance Json a => Serde (JsonSerde a) where+  tpName _ = "json/json" -- TODO: add adt name   deserializeBytes a = JsonSerde <$> eitherDecode (BSL.fromStrict a)   serializeBytes (JsonSerde a) = BSL.toStrict $ encode a  instance Serde () where+  tpName _ = "ghc/Unit"   deserializeBytes _ = pure ()   serializeBytes _ = ""  instance Serde ByteString where+  tpName _ = "ghc/Data.ByteString"   deserializeBytes = pure   serializeBytes = id  instance Serde BSL.ByteString where+  tpName _ = "ghc/Data.ByteString.Lazy"   deserializeBytes = pure . BSL.fromStrict   serializeBytes = BSL.toStrict @@ -140,22 +168,6 @@   getCtx :: m s   setCtx :: s -> m ()   modifyCtx :: (s -> s) -> m ()-  sendMsg ::-    Message a =>-    -- | Function address (namespace, type, id)-    (Text, Text, Text) ->-    -- | protobuf message to send-    a ->-    m ()-  sendMsgDelay ::-    Message a =>-    -- | Function address (namespace, type, id)-    (Text, Text, Text) ->-    -- | delay before message send-    Int ->-    -- | protobuf message to send-    a ->-    m ()   sendEgressMsg ::     Message a =>     -- | egress address (namespace, type)@@ -163,22 +175,22 @@     -- | protobuf message to send (should be a Kafka or Kinesis protobuf record)     a ->     m ()--  sendByteMsg ::+  sendMsg ::     Serde a =>     -- | Function address (namespace, type, id)-    (Text, Text, Text) ->+    Address ->     -- | message to send     a ->     m ()-  sendByteMsgDelay ::+  sendMsgDelay ::     Serde a =>     -- | Function address (namespace, type, id)-    (Text, Text, Text) ->+    Address ->     -- | delay before message send-    Int ->+    NominalDiffTime ->     -- | message to send     a ->+    -- | returns cancelation token with which delivery of the message could be canceled     m ()  instance StatefulFunc s (Function s) where@@ -187,54 +199,29 @@   insideCtx func = func <$> getCtx   getCtx = gets functionStateCtx   setCtx new = modify (\old -> old {functionStateCtx = new, functionStateMutated = True})-  modifyCtx mutator = mutator <$> getCtx >>= setCtx-  sendMsg (namespace, funcType, id') msg = do-    invocations <- gets functionStateInvocations-    modify (\old -> old {functionStateInvocations = invocations Seq.:|> invocation})-    where-      target :: PR.Address-      target =-        defMessage-          & PR.namespace .~ namespace-          & PR.type' .~ funcType-          & PR.id .~ id'-      invocation :: PR.FromFunction'Invocation-      invocation =-        defMessage-          & PR.target .~ target-          & PR.argument .~ Any.pack msg-  sendMsgDelay (namespace, funcType, id') delay msg = do-    invocations <- gets functionStateDelayedInvocations-    modify (\old -> old {functionStateDelayedInvocations = invocations Seq.:|> invocation})-    where-      target :: PR.Address-      target =-        defMessage-          & PR.namespace .~ namespace-          & PR.type' .~ funcType-          & PR.id .~ id'-      invocation :: PR.FromFunction'DelayedInvocation-      invocation =-        defMessage-          & PR.delayInMs .~ fromIntegral delay-          & PR.target .~ target-          & PR.argument .~ Any.pack msg+  modifyCtx mutator = getCtx >>= setCtx . mutator+   sendEgressMsg (namespace, egressType) msg = do     egresses <- gets functionStateEgressMessages     modify (\old -> old {functionStateEgressMessages = egresses Seq.:|> egressMsg})     where+      wmsg = ProtoSerde msg       egressMsg :: PR.FromFunction'EgressMessage       egressMsg =         defMessage           & PR.egressNamespace .~ namespace           & PR.egressType .~ egressType-          & PR.argument .~ Any.pack msg-  sendByteMsg (namespace, funcType, id') msg = do+          & PR.argument .~ tpValue+      tpValue =+        defMessage+          & PR.typename .~ tpName (pure wmsg)+          & PR.hasValue .~ True          +          & PR.value .~ serializeBytes wmsg++  sendMsg (Address' namespace funcType id') msg = do     invocations <- gets functionStateInvocations     modify (\old -> old {functionStateInvocations = invocations Seq.:|> invocation})     where-      argument :: Any-      argument = defMessage & Any.value .~ serializeBytes msg       target :: PR.Address       target =         defMessage@@ -245,13 +232,17 @@       invocation =         defMessage           & PR.target .~ target-          & PR.argument .~ argument-  sendByteMsgDelay (namespace, funcType, id') delay msg = do+          & PR.argument .~ tpValue+      tpValue =+        defMessage+          & PR.typename .~ tpName (pure msg)+          & PR.hasValue .~ True+          & PR.value .~ serializeBytes msg++  sendMsgDelay (Address' namespace funcType id') delay msg = do     invocations <- gets functionStateDelayedInvocations     modify (\old -> old {functionStateDelayedInvocations = invocations Seq.:|> invocation})     where-      argument :: Any-      argument = defMessage & Any.value .~ serializeBytes msg       target :: PR.Address       target =         defMessage@@ -261,13 +252,28 @@       invocation :: PR.FromFunction'DelayedInvocation       invocation =         defMessage-          & PR.delayInMs .~ fromIntegral delay+          & PR.delayInMs .~ round (delay * 1000)           & PR.target .~ target-          & PR.argument .~ argument+          & PR.argument .~ tpValue+      tpValue =+        defMessage+          & PR.typename .~ tpName (pure msg)+          & PR.hasValue .~ True+          & PR.value .~ serializeBytes msg +-- | Convinience function to send protobuf messages+sendProtoMsg :: (StatefulFunc s m, Message a) => Address -> a -> m ()+sendProtoMsg addr = sendMsg addr . ProtoSerde++-- | Convinience function to send delayed protobuf messages+sendProtoMsgDelay :: (StatefulFunc s m, Message a) => Address -> NominalDiffTime -> a -> m ()+sendProtoMsgDelay addr delay = sendMsgDelay addr delay . ProtoSerde+ data FlinkError   = MissingInvocationBatch   | ProtodeserializeBytesError String+  | InvalidTypePassedError Text Text+  | EmptyArgumentPassed   | StateDecodeError String   | MessageDecodeError String   | ProtoMessageDecodeError UnpackError@@ -275,63 +281,53 @@   deriving (Show, Eq)  -- | Convenience function for wrapping state in newtype for JSON serialization-jsonState :: Json s => (a -> Function s ()) -> a -> Function (JsonSerde s) ()-jsonState f a = jsonWrapper-  where-    jsonWrapper = Function (ExceptT (StateT (\s -> ReaderT (\env -> (fmap . fmap) JsonSerde <$> runner (f a) env (getJson <$> s)))))-    runner res env state = runReaderT (runStateT (runExceptT $ runFunction res) state) env+jsonState :: Json s => Function s () -> Function (JsonSerde s) ()+jsonState = coerce  -- | Convenience function for wrapping state in newtype for Protobuf serialization-protoState :: Message s => (a -> Function s ()) -> a -> Function (ProtoSerde s) ()-protoState f a = protoWrapper-  where-    protoWrapper = Function (ExceptT (StateT (\s -> ReaderT (\env -> (fmap . fmap) ProtoSerde <$> runner (f a) env (getMessage <$> s)))))-    runner res env state = runReaderT (runStateT (runExceptT $ runFunction res) state) env+protoState :: Message s => Function s () -> Function (ProtoSerde s) ()+protoState = coerce +-- | Tries to unwrap typed value, possibly throwing FlinkError on broken input+unwrapA :: forall a m. (Serde a, MonadError FlinkError m) => PR.TypedValue -> m (Maybe a)+unwrapA arg = let+      atp = arg ^. PR.typename+      ctp = tpName @a Proxy+      in if not (arg^. PR.hasValue) then pure Nothing else+           if atp /= ctp+              then throwError (InvalidTypePassedError ctp atp)+              else Just <$> (liftEither . mapLeft MessageDecodeError $ deserializeBytes @a (arg ^. PR.value))+ -- | Takes a function taking an arbitrary state type and converts it to take 'ByteString's. -- This allows each function in the 'FunctionTable' to take its own individual type of state and just expose -- a function accepting 'ByteString' to the library code.-flinkWrapper :: Serde s => (Any -> Function s ()) -> ByteString -> Env -> PR.ToFunction'InvocationBatchRequest -> IO (Either FlinkError (FunctionState ByteString))-flinkWrapper func initialContext env invocationBatch = runExceptT $ do-  deserializeBytesdContext <- liftEither $ mapLeft StateDecodeError $ deserializeBytes initialContext-  (err, finalState) <- liftIO $ runner (newState deserializeBytesdContext)-  liftEither err-  return $ serializeBytes <$> finalState+flinkWrapper :: forall a s. (Serde a, Serde s) => s -> Expiration -> (a -> Function s ()) -> FuncExec+flinkWrapper s0 expr func env invocationBatch = runExceptT $ do+  (eiRes, _) <- liftIO $ runner (newState s0)+  liftEither eiRes   where-    runner state = runReaderT (runStateT (runExceptT $ runFunction runWithCtx) state) env+    passedArgs = invocationBatch ^.. PR.invocations . traversed . PR.argument+    mbInitCtx = (PR.state . traversed . filtered ((== "flink_state") . (^. PR.stateName)) . PR.stateValue) +      `firstOf` invocationBatch     runWithCtx = do-      defaultCtx <- gets functionStateCtx-      let initialCtx = getInitialCtx defaultCtx (listToMaybe $ invocationBatch ^. PR.state)-      case initialCtx of-        Left err -> throwError err-        Right ctx -> setInitialCtx ctx-      mapM_ func ((^. PR.argument) <$> invocationBatch ^. PR.invocations)--    getInitialCtx def pv = handleEmptyState def $ (^. PR.stateValue) <$> pv-    handleEmptyState def state' = case state' of-      Just "" -> return def-      Just other -> mapLeft StateDecodeError $ deserializeBytes other-      Nothing -> return def---- | Deserializes input messages as arbitrary bytes by extracting them out of the protobuf Any--- and ignoring the type since that's protobuf specific-serdeInput :: (Serde s, Serde a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b-serdeInput f input = f =<< liftEither (mapLeft MessageDecodeError $ deserializeBytes (input ^. Any.value))---- | Deserializes input messages as arbitrary bytes by extracting them out of the protobuf Any--- and ignoring the type since that's protobuf specific-jsonInput :: (Serde s, Json a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b-jsonInput f = serdeInput wrapJson-  where-    wrapJson (JsonSerde msg) = f msg---- | Deserializes input messages by unpacking the protobuf Any into the expected type.--- If you are passing messages via protobuf, this is much more typesafe than 'serdeInput'-protoInput :: (Serde s, Message a, StatefulFunc s m, MonadError FlinkError m, MonadReader Env m) => (a -> m b) -> Any -> m b-protoInput f input = f =<< liftEither (mapLeft ProtoMessageDecodeError $ Any.unpack input)+      case mbInitCtx of +        Nothing -> return $ IncompleteContext expr (tpName @s Proxy) -- if state was not propagated to the function - shorcut to incomplete context reponse+        Just tv -> do +          mbCtx <- unwrapA @s tv+          case mbCtx of+            Nothing -> pure () -- if null state value was propagated+            Just s1 -> setInitialCtx s1+          mbArgs <- traverse (unwrapA @a) passedArgs+          args <- traverse (maybe (throwError EmptyArgumentPassed) pure) mbArgs+          mapM_ func args+          gets (UpdatedState . fmap outS)+    runner state = runReaderT (runStateT (runExceptT $ runFunction runWithCtx) state) env+    outS fstate = defMessage+        & PR.typename .~ tpName (pure fstate)+        & PR.value .~ serializeBytes fstate -createFlinkResp :: FunctionState ByteString -> FromFunction-createFlinkResp (FunctionState state mutated invocations delayedInvocations egresses) =+createFlinkResp :: FuncRes -> FromFunction+createFlinkResp (UpdatedState (FunctionState state mutated invocations delayedInvocations egresses)) =   defMessage & PR.invocationResult     .~ ( defMessage            & PR.stateMutations .~ toList stateMutations@@ -348,6 +344,20 @@           & PR.stateValue .~ state         | mutated       ]+createFlinkResp (IncompleteContext (Expiration mode expireTime) typeName) = +  defMessage & PR.incompleteInvocationContext .~ ( +    defMessage & PR.missingValues .~ [+      defMessage+        & PR.stateName .~ "flink_state"+        & PR.typeTypename .~ typeName+        & PR.expirationSpec .~ (+          defMessage +            & PR.expireAfterMillis .~ round (expireTime * 1000.0)+            & PR.mode .~ pbmode mode)+      ])+  where pbmode NONE = PR.FromFunction'ExpirationSpec'NONE +        pbmode AFTER_CALL = PR.FromFunction'ExpirationSpec'AFTER_INVOKE  +        pbmode AFTER_WRITE = PR.FromFunction'ExpirationSpec'AFTER_WRITE    type FlinkApi =   "statefun" :> ReqBody '[Proto] ToFunction :> Post '[Proto] FromFunction@@ -363,14 +373,14 @@ flinkServer :: FunctionTable -> Server FlinkApi flinkServer functions toFunction = do   batch <- getBatch toFunction-  ((initialCtx, function), (namespace, type', id')) <- findFunc (batch ^. PR.target)-  result <- liftIO $ function initialCtx (Env namespace type' id') batch+  (function, (namespace, type', id')) <- findFunc (batch ^. PR.target)+  result <- liftIO $ function (Env namespace type' id') batch   finalState <- liftEither $ mapLeft flinkErrToServant result   return $ createFlinkResp finalState   where     getBatch input = maybe (throwError $ flinkErrToServant MissingInvocationBatch) return (input ^? PR.maybe'request . _Just . PR._ToFunction'Invocation')     findFunc addr = do-      res <- maybe (throwError $ flinkErrToServant $ NoSuchFunction (namespace, type')) return (Map.lookup (namespace, type') functions)+      res <- maybe (throwError $ flinkErrToServant $ NoSuchFunction (namespace, type')) return (Map.lookup (FuncType namespace type') functions)       return (res, address)       where         address@(namespace, type', _) = (addr ^. PR.namespace, addr ^. PR.type', addr ^. PR.id)@@ -383,3 +393,5 @@   MessageDecodeError msg -> err400 {errBody = "Failed to decode message " <> BSL.pack msg}   ProtoMessageDecodeError msg -> err400 {errBody = "Failed to decode message " <> BSL.pack (show msg)}   NoSuchFunction (namespace, type') -> err400 {errBody = "No such function " <> T.encodeUtf8 (fromStrict namespace) <> T.encodeUtf8 (fromStrict type')}+  InvalidTypePassedError expected passed -> err400 {errBody = "Expected type " <> T.encodeUtf8 (fromStrict expected) <> ", got " <>  T.encodeUtf8 (fromStrict passed)}+  EmptyArgumentPassed -> err400 {errBody = "Empty argument was passed to the function" }
src/Network/Flink/Stateful.hs view
@@ -17,23 +17,24 @@         modifyCtx,         sendMsg,         sendMsgDelay,-        sendEgressMsg,-        sendByteMsg,-        sendByteMsgDelay+        sendEgressMsg       ),     flinkWrapper,     createApp,     flinkServer,     flinkApi,+    Address(..),+    FuncType (..),     Serde (..),     FunctionTable,     JsonSerde (..),     ProtoSerde (..),+    Expiration(..),+    ExpirationMode(..),     jsonState,     protoState,-    serdeInput,-    protoInput,-    jsonInput,+    sendProtoMsg,+    sendProtoMsgDelay   ) where @@ -43,12 +44,16 @@       ProtoSerde(..),       Serde(..),       FunctionTable,+      Expiration(..),+      ExpirationMode(..),+      Address(..),+      FuncType (..),       jsonState,       protoState,       flinkApi,       createApp,       flinkServer,-      protoInput,-      serdeInput,       flinkWrapper,-      jsonInput )+      sendProtoMsg,+      sendProtoMsgDelay+ )