A pure-Scala ULID implementation built on Cats and Cats Effect. Polymorphic in the effect type, cross-built for Scala 2.13 and 3 across JVM, Scala.js and Native, with a type-safe Ulid value and injectable clock and randomness for deterministic tests.
libraryDependencies += "de.thatscalaguy" %%% "ulid4cats" % "2.0.0" A pure, tagless-final ULID implementation for Scala, built on Cats and Cats Effect.
F[_]Ulid value type (opaque in Scala 3, AnyVal in Scala 2)RandomSource and TimestampProvider for deterministic testsAdd the dependency to your build.sbt:
libraryDependencies += "de.thatscalaguy" %%% "ulid4cats" % "2.0.0"
For JVM-only projects, you can use %% instead of %%%.
import cats.effect.{IO, IOApp, ExitCode}
import de.thatscalaguy.ulid4cats.{FULID, Ulid}
object Main extends IOApp:
def run(args: List[String]): IO[ExitCode] = for {
// Generate a typed Ulid
ulid <- FULID[IO].generateUlid
_ <- IO.println(s"ULID: ${ulid.value}")
_ <- IO.println(s"Timestamp: ${ulid.timestamp}")
// Or generate as String (backward compatible)
ulidStr <- FULID[IO].generate
_ <- IO.println(s"ULID String: $ulidStr")
} yield ExitCode.Success
import cats.effect.{IO, IOApp, ExitCode}
import de.thatscalaguy.ulid4cats.{FULID, Ulid}
object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] = for {
ulid <- FULID[IO].generateUlid
_ <- IO.println(s"ULID: ${ulid.value}")
} yield ExitCode.Success
}
UlidGen DirectlyFor more control, use the UlidGen algebra directly:
import cats.effect.IO
import de.thatscalaguy.ulid4cats.UlidGen
// Random generator (new randomness each call)
val randomGen: UlidGen[IO] = UlidGen.randomDefault[IO]
// Monotonic generator (strictly increasing within same millisecond)
val monotonicGen: IO[UlidGen[IO]] = UlidGen.monotonicDefault[IO]
import de.thatscalaguy.ulid4cats.{Ulid, UlidCodec}
// Safe parsing
val parsed: Either[UlidError, Ulid] = Ulid.fromString("01ARZ3NDEKTSV4RRFFQ69G5FAV")
val parsedOpt: Option[Ulid] = Ulid.fromStringOption("01ARZ3NDEKTSV4RRFFQ69G5FAV")
// Unsafe parsing (throws on invalid input)
val ulid: Ulid = Ulid.unsafeFromString("01ARZ3NDEKTSV4RRFFQ69G5FAV")
// Validation
val isValid: Boolean = UlidCodec.isValid("01ARZ3NDEKTSV4RRFFQ69G5FAV")
// Extract components
val timestamp: Long = ulid.timestamp
val bytes: Array[Byte] = ulid.toBytes
Inject test doubles for reproducible tests:
import cats.effect.IO
import de.thatscalaguy.ulid4cats.{UlidGen, RandomSource, TimestampProvider}
val fixedTimestamp = 1702300800000L
val fixedRandomness = Array.fill[Byte](10)(0x42)
implicit val randomSource: RandomSource[IO] = RandomSource.constant[IO](fixedRandomness)
implicit val timestampProvider: TimestampProvider[IO] = TimestampProvider.constant[IO](fixedTimestamp)
val deterministicGen: UlidGen[IO] = UlidGen.random[IO]
// All generated ULIDs will have the same timestamp and randomness
Ulid — Value TypeUlid.fromString(s: String): Either[UlidError, Ulid]Ulid.fromStringOption(s: String): Option[Ulid]Ulid.unsafeFromString(s: String): UlidUlid.fromBytes(bytes: Array[Byte]): Either[UlidError, Ulid]ulid.value: String — the 26-character ULID stringulid.timestamp: Long — milliseconds since Unix epochulid.toBytes: Array[Byte] — 16-byte representationUlidGen[F[_]] — Generator AlgebraUlidGen.random[F] — random generator (requires implicit RandomSource and TimestampProvider)UlidGen.randomDefault[F] — random generator with default implsUlidGen.monotonic[F] — monotonic generator (strictly increasing)UlidGen.monotonicDefault[F] — monotonic generator with default implsFULID[F[_]] — Backward-Compatible APIFULID[F].generate: F[String] — generate ULID as StringFULID[F].generateUlid: F[Ulid] — generate typed UlidFULID[F].isValid(s: String): F[Boolean]FULID[F].timeStamp(s: String): F[Option[Long]]FULID[F].parseUlid(s: String): F[Option[Ulid]]This library implements the ULID specification:
MIT License — see LICENSE for details.
Built and maintained by Sven — need it in production, extended or reviewed?
GitHub ↗Hire the author →