gulid

ULID implementation in Gleam (Erlang target only)


License
Other

Documentation

Gleam ULID

Universally Unique Lexicographically Sortable Identifier implementation in Gleam.

What's a ULID? Some say it's a better UUID. In a string form it is shorter (26 characters vs. 32) and sortable.

Caveats

  1. Only Erlang build target is supported (at the moment)

Package Version Hex Docs

gleam add gulid

Basic Use

import gulid.{ new_as_string, new, new_monotonic, from_string_function,
               to_string_function }

pub fn main() {
  // Quick and dirty ULID string - performance implications
  let ulid_str = new_as_string()
  io.println("ULID is " <> ulid_str)

  // Convert many ULIDs to String with a generator function
  let to_string = to_string_function()
  // `to_string_function` returns a function that then can be used to
  // convert `Ulid` values to `String`. The reason this is done this way is
  // that Gleam doesn't have a way to define module scoped global constants
  // that could use function calls to initialize. Nor does it have module scoped
  // `let`. Therefore, the only way to have a `private` reused value is to
  // have it as a capture. So, there is a `let` in `to_string_function`, which
  // binds an Erlang array with ULID character encodings, captured in returned
  // function.
  let bunch_of_ulids = list.map([new(), new(), new(), new(), new()], to_string)
  io.println("A bunch of ULIDs:")
  io.debug(bunch_of_ulids)

  // Parse ULIDs from string
  let from_string = from_string_function()
  // Quick and dirty
  let assert Ok(ulid) = from_string("01J9HSAQG7YR6Z16SS7ZTH26WQ")
  io.println("Quick and dirty parsed ULID is")
  io.debug(ulid)

  // More proper
  io.println("A more properly parsed ULID is")
  case from_string("01J9HS6WA9ZBA045WTNYWAGPM5") {
    Ok(ulid) -> {
      io.debug(ulid)
      Nil
    }
    Error(InvalidLength(error)) | Error(DecodeError(error)) ->
      io.println("Oh, noes: " <> error)
  }

  // Monotonic ULIDs:
  // 1. Generate initial `Ulid` with `new()`
  // 2. Then use `new_monotonic(Ulid)` with initial and subsequently generated
  list.range(0, 9) // We want 10 monotonic ULIDs
  |> list.scan(new(), fn(ulid, _) { new_monotonic(ulid) })
  // Convert'em to strings
  |> list.map(to_string_function())
  |> io.debug
}

Advanced Use

In case one needs to create or outpur ULIDs out of

  • predefined constituents (timestamp and random)
  • binary representation
import gleam/erlang
import gleam/int
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import gleam/bool
import gulid.{from_parts, from_tuple, to_parts, from_bitarray, to_bitarray}

pub fn main() {
  let ulid =
    from_parts(
      erlang.system_time(erlang.Millisecond),
      int.random(99_999_999_999),
    )
  io.println("My ulid made from spare parts:")
  io.debug(ulid)

  let #(timestamp, random) = to_parts(ulid)
  io.println("Now, extracted its constituent timestamp and random:")
  io.println(
    "\tTime: "
    <> { system_time_to_rfc3339(timestamp / 1000) |> from_codepoints },
  )
  // io.debug(system_time_to_rfc3339(timestamp / 1000))
  io.println("\tRandom: " <> int.to_string(random))

  let same_ulid = from_tuple(#(timestamp, random))

  io.println("Same ulids? " <> { bool.to_string(same_ulid == ulid) })

  // Ulid from a binary
  let assert Ok(bin_ulid) =
    <<
      erlang.system_time(erlang.Millisecond):big-48,
      int.random(9_999_999_999):big-80,
    >>
    |> from_bitarray
  io.println("Ulid from a bitarray: ")
  io.debug(bin_ulid)

  // Ulid to a bitarray
  io.println("Ulid to binary: ")
  bin_ulid
  |> to_bitarray
  |> io.debug
}

@external(erlang, "calendar", "system_time_to_rfc3339")
fn system_time_to_rfc3339(seconds_since_epoch: Int) -> List(Int)

fn from_codepoints(code_points: List(Int)) -> String {
  code_points
  |> list.map(string.utf_codepoint)
  |> list.map(fn(res) {
    let assert Ok(default_codepoint) = string.utf_codepoint(97)
    result.unwrap(res, default_codepoint)
  })
  |> string.from_utf_codepoints
}

Further documentation can be found at https://hexdocs.pm/gulid.

Examples

gleam run -m examples/example1   # Run the example one
gleam run -m examples/example2   # Run the example two

Development

gleam test  # Run the tests