Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
43a6669
Rename positive to natural
NickSeagull Jun 11, 2025
f24016b
Improve types
NickSeagull Jun 14, 2025
14f819f
Fix global stream ordering tests
NickSeagull Jun 14, 2025
58bb3d8
Fix types
NickSeagull Jun 14, 2025
c168ad9
Remove natural from limit
NickSeagull Jun 15, 2025
783e05b
Add missing INLINE pragmas
NickSeagull Jun 15, 2025
5a36487
Fix more errors
NickSeagull Jun 15, 2025
871f01e
Comment out GlobalStreamOrdering tests
NickSeagull Jun 16, 2025
a621221
WIP
NickSeagull Jun 16, 2025
8e0388e
red: currently failing on same-position append for different entities
NickSeagull Jun 16, 2025
05cb2da
WIP
NickSeagull Jun 16, 2025
700015f
Modify in memory event store to use a composite key for streams
NickSeagull Jun 17, 2025
31ab3c0
pass
NickSeagull Jun 17, 2025
5f2deed
Removes TOML dependency
NickSeagull Jun 18, 2025
3db3c9e
Enables individual stream ordering tests
NickSeagull Jun 18, 2025
368009b
FAILING: Enables global stream ordering tests
NickSeagull Jun 18, 2025
7924ded
Disables global stream ordering test
NickSeagull Jun 19, 2025
3871cd1
Fix compilation
NickSeagull Jul 1, 2025
dd7cf08
Removes comment
NickSeagull Jul 1, 2025
b767008
red: failing test
NickSeagull Jul 1, 2025
d0291a3
fix test
NickSeagull Jul 2, 2025
960295d
Reenable tests
NickSeagull Jul 2, 2025
2a6f5e9
Re-enables optimistic concurrency tests
NickSeagull Jul 2, 2025
68e281a
Increases initial event stream count
NickSeagull Jul 2, 2025
010864b
Adds resumption to readAllEventsForwardFrom
NickSeagull Jul 2, 2025
089219b
Adds backward event stream reading
NickSeagull Jul 2, 2025
f271edf
Adds maximum and minimum functions to Array
NickSeagull Jul 2, 2025
0f2e466
Adds read all backwards from end
NickSeagull Jul 2, 2025
cfe8308
Adds read from arbitrary position test
NickSeagull Jul 2, 2025
d7dcd62
Adds filtered event stream reader
NickSeagull Jul 2, 2025
8fa40a8
Adds event filtering by entity ID
NickSeagull Jul 2, 2025
bd59206
Adds filtered backward event reading
NickSeagull Jul 2, 2025
c8461c8
Simplifies test setup and assertions.
NickSeagull Jul 2, 2025
2902be9
Fixes event global position assignment
NickSeagull Jul 3, 2025
bddc9d0
fix test
NickSeagull Jul 3, 2025
5ad0042
Adds optimistic concurrency test
NickSeagull Jul 6, 2025
4f3b525
Fixes backward stream reading position filtering
NickSeagull Jul 13, 2025
46fb3b7
Adds backward stream reading test for end position
NickSeagull Jul 13, 2025
ea67edc
Removes unused import
NickSeagull Jul 13, 2025
91bc314
Makes array maximum and minimum functions safe for empty arrays
NickSeagull Jul 13, 2025
ceb2cb1
Removes debug logging from stream event reading
NickSeagull Jul 13, 2025
b1648a8
style(test): remove unnecessary parentheses
NickSeagull Jul 13, 2025
590125d
Removes outdated C# test references from test descriptions
NickSeagull Jul 13, 2025
ba59886
Refactors variable assignments for clarity
NickSeagull Jul 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cli/src/Neo/Build.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Path qualified
import Subprocess qualified
import Task qualified
import Text qualified
import ToText (toText)


data Error
Expand Down
27 changes: 27 additions & 0 deletions core/core/Array.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ module Array (
isEmpty,
length,
get,
maximum,
minimum,

-- * Manipulate
set,
Expand Down Expand Up @@ -52,9 +54,11 @@ module Array (
zip,
sumIntegers,
reverse,
flatten,
) where

import Basics
import Control.Monad qualified
import Data.Foldable qualified
import Data.Vector ((!?), (++), (//))
import Data.Vector qualified
Expand Down Expand Up @@ -416,3 +420,26 @@ sumIntegers (Array vector) = Data.Vector.sum vector
-- | Reverse an array.
reverse :: Array a -> Array a
reverse (Array vector) = Array (Data.Vector.reverse vector)


-- | Flatten an array of arrays into a single array.
flatten :: Array (Array a) -> Array a
flatten (Array vector) = Array (Control.Monad.join (Data.Vector.map unwrap vector))


-- | Find the maximum element in an array.
-- If the array is empty, returns `Nothing`.
maximum :: forall (value :: Type). (Ord value) => Array value -> Maybe value
maximum (Array vector) =
if Data.Vector.null vector
then Nothing
else Just (Data.Vector.maximum vector)


-- | Find the minimum element in an array.
-- If the array is empty, returns `Nothing`.
minimum :: forall (value :: Type). (Ord value) => Array value -> Maybe value
minimum (Array vector) =
if Data.Vector.null vector
then Nothing
else Just (Data.Vector.minimum vector)
27 changes: 14 additions & 13 deletions core/core/Basics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ module Basics (
-- * Math
Int,
Float,
Positive (..),
makePositive,
makePositiveOrPanic,
Natural (..),
makeNatural,
makeNaturalOrPanic,
(+),
(-),
(*),
Expand Down Expand Up @@ -123,6 +123,7 @@ module Basics (
Type,
Control.Monad.forever,
GHC.Generics.Generic,
GHC.Stack.HasCallStack,
) where

import Applicable (Applicable)
Expand Down Expand Up @@ -238,27 +239,27 @@ type Int = Prelude.Int
type Float = Prelude.Double


-- | A @Positive@ number is a number that is greater than zero. It is used to
-- | A @Natural@ number is a number that is greater than zero. It is used to
-- represent things like the length of a list or the number of items in a
-- collection. It is a wrapper around @a@. But the constructor is not exported,
-- so you cannot create a @Positive@ number directly, except by using the constructor
newtype Positive a = Positive a
-- so you cannot create a @Natural@ number directly, except by using the constructor
newtype Natural a = Natural a
Comment thread
NickSeagull marked this conversation as resolved.
deriving
(Prelude.Eq, Prelude.Ord, Prelude.Show, Prelude.Read, Prelude.Num, Prelude.Real, Prelude.Enum, Prelude.Integral)


-- | Create a Positive number from a regular number.
-- | Create a Natural number from a regular number.
-- Returns Nothing if the input is not positive ( > 0).
makePositive :: (Prelude.Ord number, Prelude.Num number) => number -> Prelude.Maybe (Positive number)
makePositive number = if number > 0 then Prelude.Just (Positive number) else Prelude.Nothing
makeNatural :: (Prelude.Ord number, Prelude.Num number) => number -> Prelude.Maybe (Natural number)
makeNatural number = if number > 0 then Prelude.Just (Natural number) else Prelude.Nothing


-- | Create a Positive number from a regular number.
-- | Create a Natural number from a regular number.
-- Panics if the input is not positive.
makePositiveOrPanic :: (Prelude.Ord number, Prelude.Num number, Prelude.Show number) => number -> Positive number
makePositiveOrPanic number =
makeNaturalOrPanic :: (Prelude.Ord number, Prelude.Num number, Prelude.Show number) => number -> Natural number
makeNaturalOrPanic number =
if number > 0
then Positive number
then Natural number
else panic ("Expected a positive number, but got: " Prelude.<> Text.pack (Prelude.show number))


Expand Down
2 changes: 1 addition & 1 deletion core/core/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Result as Reexported (Result (..))
-- import Service.Core as Reexported (View)
import Task as Reexported (Task)
import Text as Reexported (Text)
import ToText as Reexported (Show, ToText, toPrettyText)
import ToText as Reexported (Show, ToText, toPrettyText, toText)
import Trigger as Reexported (Trigger)
import Unknown as Reexported (Unknown)
import Uuid as Reexported (Uuid)
Expand Down
28 changes: 25 additions & 3 deletions core/core/Task.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,41 +56,49 @@ newtype Task err value = Task

yield :: value -> Task _ value
yield value = Task (Applicable.pure value)
{-# INLINE yield #-}


throw :: err -> Task err _
throw err = Task (Except.throwE err)
{-# INLINE throw #-}


map :: (input -> output) -> Task err input -> Task err output
map f self = Task (Mappable.map f (runTask self))
{-# INLINE map #-}


mapError :: (err1 -> err2) -> Task err1 value -> Task err2 value
mapError f self =
runTask self
|> Except.withExceptT f
|> Task
{-# INLINE mapError #-}


apply :: Task err (input -> output) -> Task err input -> Task err output
apply taskFunction self = Task (Applicable.apply (runTask taskFunction) (runTask self))
{-# INLINE apply #-}


recover :: (err -> Task err2 value) -> Task err value -> Task err2 value
recover f self = Task (Except.catchE (runTask self) (runTask <. f))
{-# INLINE recover #-}


asResult :: Task err value -> Task err2 (Result err value)
asResult task =
runResult task
|> fromIO
{-# INLINE asResult #-}


andThen :: (input -> Task err output) -> Task err input -> Task err output
andThen f self = Task do
value <- runTask self
runTask (f value)
{-# INLINE andThen #-}


-- TODO: Figure out the best API to ensure that the main function is just a Task that cannot fail and returns a unit
Expand All @@ -100,6 +108,7 @@ runResult task = do
runTask task
|> Except.runExceptT
|> IO.map Result.fromEither
{-# INLINE runResult #-}


run :: (Result err value -> IO value) -> Task err value -> IO value
Expand All @@ -109,6 +118,7 @@ run reducer task = do
|> IO.map Result.fromEither
|> IO.andThen reducer
|> withUtf8
{-# INLINE run #-}


runNoErrors :: Task Never value -> IO value
Expand All @@ -120,15 +130,19 @@ runNoErrors task = do
|> IO.map Result.fromEither
|> IO.andThen reducer
|> withUtf8
{-# INLINE runNoErrors #-}


runOrPanic :: (Show err) => Task err value -> IO value
runOrPanic :: (HasCallStack, Show err) => Task err value -> IO value
runOrPanic task = do
let reducer (Result.Ok value) = IO.yield value
reducer (Result.Err err) = panic (toPrettyText err)
let reducer result = case result of
Result.Ok value -> IO.yield value
Result.Err err -> panic (toPrettyText err)
{-# INLINE reducer #-}
task
|> run reducer
|> withUtf8
{-# INLINE runOrPanic #-}


runMain :: Task Text Unit -> IO Unit
Expand All @@ -138,6 +152,7 @@ runMain task = do
task
|> run reducer
|> withUtf8
{-# INLINE runMain #-}


-- fromFailableIO is the reverse of run
Expand All @@ -155,13 +170,15 @@ fromFailableIO io = do
case result of
Either.Left exception -> throw exception
Either.Right value -> yield value
{-# INLINE fromFailableIO #-}


fromIO :: IO value -> Task _ value
fromIO io =
io
|> Monad.liftIO
|> Task
{-# INLINE fromIO #-}


fromIOResult :: (Show err) => IO (Result err value) -> Task err value
Expand All @@ -170,6 +187,7 @@ fromIOResult io =
|> Prelude.fmap Result.toEither
|> Except.ExceptT
|> Task
{-# INLINE fromIOResult #-}


forEach ::
Expand All @@ -179,13 +197,15 @@ forEach ::
Task err Unit
forEach callback array =
Data.Foldable.traverse_ callback (Array.unwrap array)
{-# INLINE forEach #-}


mapArray :: (element -> Task err output) -> Array element -> Task err (Array output)
mapArray f array =
Array.unwrap array
|> Control.Monad.mapM f
|> Task.map Array.fromLegacy
{-# INLINE mapArray #-}


-- | Run a task if a condition is false
Expand All @@ -194,6 +214,7 @@ unless condition task =
if condition
then Applicable.pure unit
else task
{-# INLINE unless #-}


-- | Run a task if a condition is true
Expand All @@ -202,3 +223,4 @@ when condition task =
if condition
then task
else Applicable.pure unit
{-# INLINE when #-}
11 changes: 7 additions & 4 deletions core/nhcore.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ common common_cfg
ghc-prim ,
async ,
aeson ,
brick ,
vty ,
bytestring ,
data-default ,
text ,
Expand Down Expand Up @@ -98,7 +96,6 @@ library
Unknown,
Var,
Task,
Toml,

-- System
File,
Expand Down Expand Up @@ -126,6 +123,9 @@ library
-- Service
Trigger,
Service.Event,
Service.Event.EntityId,
Service.Event.StreamId,
Service.Event.StreamPosition,
Service.EventStore,
Service.EventStore.Core,
Service.EventStore.InMemory,
Expand Down Expand Up @@ -153,6 +153,10 @@ library
Test.Service.EventStore.IndividualStreamOrdering.Context,
Test.Service.EventStore.OptimisticConcurrency.Spec,
Test.Service.EventStore.OptimisticConcurrency.Context,
Test.Service.EventStore.ReadAllForwardsFromStart.Spec,
Test.Service.EventStore.ReadAllForwardsFromStart.Context,
Test.Service.EventStore.ReadAllBackwardsFromEnd.Spec,
Test.Service.EventStore.ReadAllBackwardsFromEnd.Context,

-- other-modules:
-- other-extensions:
Expand All @@ -161,7 +165,6 @@ library
concurrency,
service,
json,
toml,
traits,
system,
http,
Expand Down
37 changes: 26 additions & 11 deletions core/service/Service/Event.hs
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
module Service.Event (
Event (..),
InsertionEvent (..),
StreamId (..),
StreamPosition (..),
EntityId (..),
fromInsertionEvent,
) where

import Core
import Service.Event.EntityId (EntityId (..))
import Service.Event.StreamId (StreamId (..))
import Service.Event.StreamPosition (StreamPosition (..))


data Event = Event
{ id :: Text, -- FIXME: Use Uuid
{ id :: Uuid,
streamId :: StreamId,
position :: StreamPosition, -- Local position in stream

-- | Global position across all streams. Nothing when event is created
-- but not yet persisted to the global stream; Just when assigned a
-- position in the global event ordering. Used for global event queries
-- and maintaining causal ordering across different streams.
globalPosition :: Maybe StreamPosition
entityId :: EntityId,
localPosition :: StreamPosition,
globalPosition :: StreamPosition
}
deriving (Eq, Show, Ord, Generic)


newtype StreamId = StreamId Text
data InsertionEvent = InsertionEvent
{ id :: Uuid,
streamId :: StreamId,
entityId :: EntityId,
localPosition :: StreamPosition
}
deriving (Eq, Show, Ord, Generic)


newtype StreamPosition = StreamPosition (Positive Int)
deriving (Eq, Show, Ord, Generic)
-- | Convert an insertion event to an event with a global position.
fromInsertionEvent :: StreamPosition -> InsertionEvent -> Event
fromInsertionEvent globalPosition event =
Event
{ id = event.id,
streamId = event.streamId,
entityId = event.entityId,
localPosition = event.localPosition,
globalPosition
}
18 changes: 18 additions & 0 deletions core/service/Service/Event/EntityId.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Service.Event.EntityId (
EntityId (..),
new,
) where

import Core
import Task qualified
import Uuid qualified


newtype EntityId = EntityId Uuid
deriving (Eq, Show, Ord, Generic)


new :: Task _ EntityId
new = do
uuid <- Uuid.generate
EntityId uuid |> Task.yield
Comment thread
NickSeagull marked this conversation as resolved.
Loading
Loading