Sheetshow.ULID (Sheetshow v0.1.0)

Copy Markdown View Source

Identifiers that sort by the time they were made.

A ULID is 26 characters: 48 bits of milliseconds since the epoch, then 80 more, in Crockford's base32. Sorting a column of them puts the log in the order it was written, they are short enough to read in a cell, and the client makes them, which is what lets a write be retried, since the same id appended twice folds to one row.

The 80 bits are usually all random. Here the first ten of them are the microsecond within the millisecond, and the other seventy are random, so two ids made a microsecond apart sort in the order they were made without any process having to remember the last one it handed out. A batch of events written in one go therefore reads back in write order on a tab sorted by id, which is what Sheetshow.Log.view/2 sorts by. The cost is ten bits of randomness and an id that is a ULID to every reader but not quite to the letter of the spec.

iex> Sheetshow.ULID.generate() |> byte_size()
26

A log does not insist on these: any non-empty string works as an id, so an invoice number or a UUID your app already has is fine. This is only what Sheetshow.Log.Event.new/2 reaches for when you give it nothing.

Summary

Functions

A new identifier, timed by the system clock in microseconds unless you say otherwise.

The moment an identifier was made. Raises ArgumentError for anything that is not one.

Whether the string is one of ours: 26 canonical characters, and a first one low enough that the time fits in 48 bits.

Functions

generate(time \\ System.system_time(:microsecond))

@spec generate(integer()) :: String.t()

A new identifier, timed by the system clock in microseconds unless you say otherwise.

Two made in the same microsecond sort by their random half; two made a microsecond apart sort in that order.

iex> Sheetshow.ULID.generate(0) |> binary_part(0, 10)
"0000000000"
iex> Sheetshow.ULID.generate(1_700_000_000_000_001) < Sheetshow.ULID.generate(1_700_000_000_000_002)
true

timestamp(ulid)

@spec timestamp(String.t()) :: DateTime.t()

The moment an identifier was made. Raises ArgumentError for anything that is not one.

iex> Sheetshow.ULID.generate(0) |> Sheetshow.ULID.timestamp()
~U[1970-01-01 00:00:00.000Z]

valid?(ulid)

@spec valid?(term()) :: boolean()

Whether the string is one of ours: 26 canonical characters, and a first one low enough that the time fits in 48 bits.

iex> Sheetshow.ULID.generate() |> Sheetshow.ULID.valid?()
true
iex> Sheetshow.ULID.valid?("invoice-104")
false