diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala new file mode 100644 index 0000000..c8bdba8 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -0,0 +1,14 @@ +package works.iterative.event + +import works.iterative.core.* +import zio.* + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) + +object EventRecord: + def apply(userHandle: UserHandle): UIO[EventRecord] = + Moment.now.map(EventRecord(userHandle, _)) + + def now(using userHandle: UserHandle): UIO[EventRecord] = + apply(userHandle) diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala new file mode 100644 index 0000000..c8bdba8 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -0,0 +1,14 @@ +package works.iterative.event + +import works.iterative.core.* +import zio.* + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) + +object EventRecord: + def apply(userHandle: UserHandle): UIO[EventRecord] = + Moment.now.map(EventRecord(userHandle, _)) + + def now(using userHandle: UserHandle): UIO[EventRecord] = + apply(userHandle) diff --git a/core/shared/src/main/scala/works/iterative/event/EventStore.scala b/core/shared/src/main/scala/works/iterative/event/EventStore.scala new file mode 100644 index 0000000..3226df3 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventStore.scala @@ -0,0 +1,13 @@ +package works.iterative.event + +import zio.* + +/** Abstraction of event log + * + * The log can persist events and restore the state of an entity from the + * events. + */ +trait EventStore[Id, T <: Event[_]]: + type Op[A] = UIO[A] + def persist(id: Id, event: T): Op[Unit] + def get(id: Id): Op[Seq[T]] diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala new file mode 100644 index 0000000..c8bdba8 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -0,0 +1,14 @@ +package works.iterative.event + +import works.iterative.core.* +import zio.* + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) + +object EventRecord: + def apply(userHandle: UserHandle): UIO[EventRecord] = + Moment.now.map(EventRecord(userHandle, _)) + + def now(using userHandle: UserHandle): UIO[EventRecord] = + apply(userHandle) diff --git a/core/shared/src/main/scala/works/iterative/event/EventStore.scala b/core/shared/src/main/scala/works/iterative/event/EventStore.scala new file mode 100644 index 0000000..3226df3 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventStore.scala @@ -0,0 +1,13 @@ +package works.iterative.event + +import zio.* + +/** Abstraction of event log + * + * The log can persist events and restore the state of an entity from the + * events. + */ +trait EventStore[Id, T <: Event[_]]: + type Op[A] = UIO[A] + def persist(id: Id, event: T): Op[Unit] + def get(id: Id): Op[Seq[T]] diff --git a/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala new file mode 100644 index 0000000..6ccccd7 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala @@ -0,0 +1,12 @@ +package works.iterative.event + +import zio.* + +/** Snapshot store + * + * Stores a snapshot of an entity. + */ +trait SnapshotStore[Id, T]: + type Op[A] = UIO[A] + def update(id: Id, snapshot: T): Op[Unit] + def get(id: Id): Op[Option[T]] diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala new file mode 100644 index 0000000..c8bdba8 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -0,0 +1,14 @@ +package works.iterative.event + +import works.iterative.core.* +import zio.* + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) + +object EventRecord: + def apply(userHandle: UserHandle): UIO[EventRecord] = + Moment.now.map(EventRecord(userHandle, _)) + + def now(using userHandle: UserHandle): UIO[EventRecord] = + apply(userHandle) diff --git a/core/shared/src/main/scala/works/iterative/event/EventStore.scala b/core/shared/src/main/scala/works/iterative/event/EventStore.scala new file mode 100644 index 0000000..3226df3 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventStore.scala @@ -0,0 +1,13 @@ +package works.iterative.event + +import zio.* + +/** Abstraction of event log + * + * The log can persist events and restore the state of an entity from the + * events. + */ +trait EventStore[Id, T <: Event[_]]: + type Op[A] = UIO[A] + def persist(id: Id, event: T): Op[Unit] + def get(id: Id): Op[Seq[T]] diff --git a/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala new file mode 100644 index 0000000..6ccccd7 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala @@ -0,0 +1,12 @@ +package works.iterative.event + +import zio.* + +/** Snapshot store + * + * Stores a snapshot of an entity. + */ +trait SnapshotStore[Id, T]: + type Op[A] = UIO[A] + def update(id: Id, snapshot: T): Op[Unit] + def get(id: Id): Op[Option[T]] diff --git a/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala b/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala new file mode 100644 index 0000000..964d420 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala @@ -0,0 +1,21 @@ +package works.iterative.event +package impl + +import zio.* + +class InMemoryEventStore[Id, T <: Event[_]](data: Ref[Map[Id, List[T]]]) + extends EventStore[Id, T]: + override def persist(id: Id, event: T): Op[Unit] = + data.update { m => + val events = m.getOrElse(id, Nil) + m.updated(id, event :: events) + }.unit + override def get(id: Id): Op[Seq[T]] = + data.get.map(_.getOrElse(id, Nil)) + +object InMemoryEventStore: + def layer[Id: Tag, T <: Event[_]: Tag]: ULayer[EventStore[Id, T]] = + ZLayer { + for data <- Ref.make(Map.empty[Id, List[T]]) + yield InMemoryEventStore(data) + } diff --git a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala b/core/shared/src/main/scala/works/iterative/core/EventRecord.scala deleted file mode 100644 index 8ae9a64..0000000 --- a/core/shared/src/main/scala/works/iterative/core/EventRecord.scala +++ /dev/null @@ -1,4 +0,0 @@ -package works.iterative.core - -/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ -final case class EventRecord(userHandle: UserHandle, timestamp: Moment) diff --git a/core/shared/src/main/scala/works/iterative/core/Moment.scala b/core/shared/src/main/scala/works/iterative/core/Moment.scala index 260ca13..d3bd41f 100644 --- a/core/shared/src/main/scala/works/iterative/core/Moment.scala +++ b/core/shared/src/main/scala/works/iterative/core/Moment.scala @@ -2,6 +2,7 @@ import java.time.Instant import scala.Conversion +import zio.* /** A moment in time, represented as an instant * @@ -13,6 +14,12 @@ object Moment: def apply(value: Instant): Moment = value + + def now: UIO[Moment] = + for now <- Clock.instant + yield Moment(now) + extension (m: Moment) def toInstant: Instant = m + given Conversion[Instant, Moment] = Moment(_) given Conversion[Moment, Instant] = _.toInstant diff --git a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala index 7007704..46cf59b 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserHandle.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserHandle.scala @@ -5,7 +5,7 @@ * we should be able to do without resorting to external services. This handle * is to be used in all user-generated content, like comments, reviews, etc. */ -final case class UserHandle private ( +final case class UserHandle( userId: UserId, userName: UserName ) diff --git a/core/shared/src/main/scala/works/iterative/core/UserId.scala b/core/shared/src/main/scala/works/iterative/core/UserId.scala index 130dd3a..2bffd60 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserId.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserId.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserId] = // Validate that the value is not empty Validated.nonEmptyString("user.id")(value) + + def unsafe(value: String): UserId = value diff --git a/core/shared/src/main/scala/works/iterative/core/UserName.scala b/core/shared/src/main/scala/works/iterative/core/UserName.scala index 664bff0..82006e2 100644 --- a/core/shared/src/main/scala/works/iterative/core/UserName.scala +++ b/core/shared/src/main/scala/works/iterative/core/UserName.scala @@ -7,3 +7,5 @@ def apply(value: String): Validated[UserName] = // Validate that the value is not empty Validated.nonEmptyString("user.name")(value) + + def unsafe(value: String): UserName = value diff --git a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala index dcd202b..358cb9d 100644 --- a/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala +++ b/core/shared/src/main/scala/works/iterative/core/service/impl/UUIDGenerator.scala @@ -5,3 +5,7 @@ class UUIDGenerator[A](f: String => A) extends IdGenerator[A]: def nextId: UIO[A] = Random.nextUUID.map(v => f(v.toString)) + +object UUIDGenerator: + def layer[A: Tag](f: String => A): ULayer[IdGenerator[A]] = + ZLayer.succeed(UUIDGenerator(f)) diff --git a/core/shared/src/main/scala/works/iterative/event/Event.scala b/core/shared/src/main/scala/works/iterative/event/Event.scala new file mode 100644 index 0000000..7907ac0 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/Event.scala @@ -0,0 +1,4 @@ +package works.iterative.event + +trait Event[A]: + def record: EventRecord diff --git a/core/shared/src/main/scala/works/iterative/event/EventRecord.scala b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala new file mode 100644 index 0000000..c8bdba8 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventRecord.scala @@ -0,0 +1,14 @@ +package works.iterative.event + +import works.iterative.core.* +import zio.* + +/** A simple holder for _who_ did _when_, meant to be bound to the _what_ */ +final case class EventRecord(userHandle: UserHandle, timestamp: Moment) + +object EventRecord: + def apply(userHandle: UserHandle): UIO[EventRecord] = + Moment.now.map(EventRecord(userHandle, _)) + + def now(using userHandle: UserHandle): UIO[EventRecord] = + apply(userHandle) diff --git a/core/shared/src/main/scala/works/iterative/event/EventStore.scala b/core/shared/src/main/scala/works/iterative/event/EventStore.scala new file mode 100644 index 0000000..3226df3 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/EventStore.scala @@ -0,0 +1,13 @@ +package works.iterative.event + +import zio.* + +/** Abstraction of event log + * + * The log can persist events and restore the state of an entity from the + * events. + */ +trait EventStore[Id, T <: Event[_]]: + type Op[A] = UIO[A] + def persist(id: Id, event: T): Op[Unit] + def get(id: Id): Op[Seq[T]] diff --git a/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala new file mode 100644 index 0000000..6ccccd7 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/SnapshotStore.scala @@ -0,0 +1,12 @@ +package works.iterative.event + +import zio.* + +/** Snapshot store + * + * Stores a snapshot of an entity. + */ +trait SnapshotStore[Id, T]: + type Op[A] = UIO[A] + def update(id: Id, snapshot: T): Op[Unit] + def get(id: Id): Op[Option[T]] diff --git a/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala b/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala new file mode 100644 index 0000000..964d420 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/impl/InMemoryEventStore.scala @@ -0,0 +1,21 @@ +package works.iterative.event +package impl + +import zio.* + +class InMemoryEventStore[Id, T <: Event[_]](data: Ref[Map[Id, List[T]]]) + extends EventStore[Id, T]: + override def persist(id: Id, event: T): Op[Unit] = + data.update { m => + val events = m.getOrElse(id, Nil) + m.updated(id, event :: events) + }.unit + override def get(id: Id): Op[Seq[T]] = + data.get.map(_.getOrElse(id, Nil)) + +object InMemoryEventStore: + def layer[Id: Tag, T <: Event[_]: Tag]: ULayer[EventStore[Id, T]] = + ZLayer { + for data <- Ref.make(Map.empty[Id, List[T]]) + yield InMemoryEventStore(data) + } diff --git a/core/shared/src/main/scala/works/iterative/event/impl/InMemorySnapshotStore.scala b/core/shared/src/main/scala/works/iterative/event/impl/InMemorySnapshotStore.scala new file mode 100644 index 0000000..722d7d7 --- /dev/null +++ b/core/shared/src/main/scala/works/iterative/event/impl/InMemorySnapshotStore.scala @@ -0,0 +1,19 @@ +package works.iterative.event +package impl + +import zio.* + +class InMemorySnapshotStore[Id, T](data: Ref[Map[Id, T]]) + extends SnapshotStore[Id, T]: + override def update(id: Id, snapshot: T): UIO[Unit] = + data.update(_.updated(id, snapshot)).unit + + override def get(id: Id): UIO[Option[T]] = + data.get.map(_.get(id)) + +object InMemorySnapshotStore: + def layer[Id: Tag, T: Tag](): ULayer[SnapshotStore[Id, T]] = + ZLayer { + for data <- Ref.make(Map.empty[Id, T]) + yield InMemorySnapshotStore(data) + }