defmodule UUIDv7.Boxed do @moduledoc """ Ecto type that autogenerates `%UUID{}` structs holding raw UUIDv7 bytes. ## Why The default `Ecto.UUID` (and `UUIDv7` by delegation) stores UUIDs as 36-char hex strings in your schemas. Every `Repo.insert/1` pays a hex → binary decode in `dump/1`, and every `autogenerate/0` pays a binary → hex encode it will undo a moment later. `UUIDv7.Boxed` cuts the round-trip: it autogenerates `%UUID{}` structs carrying the raw 16 bytes, and `dump/1` returns those bytes directly. Hex encoding is deferred to `to_string/1`, `inspect/1`, and JSON encoding — paid once at display time, not on every write. ## Use as a primary key schema "users" do @primary_key {:id, UUIDv7.Boxed, autogenerate: true} ... end Values come back as `%UUID{bytes: <<_::128>>}`. `cast/1` accepts the struct, a 16-byte raw binary, or a 36-char hex string. """ if Code.ensure_loaded?(Ecto.Type) do use Ecto.Type @impl true def type, do: :uuid @impl true def autogenerate, do: %UUID{bytes: UUIDv7.bingenerate()} @impl true def cast(%UUID{bytes: <<_::128>>} = uuid), do: {:ok, uuid} def cast(<<_::128>> = raw), do: {:ok, %UUID{bytes: raw}} def cast(<<_::288>> = hex), do: UUID.parse(hex) def cast(_), do: :error @impl true def dump(%UUID{bytes: <<_::128>> = raw}), do: {:ok, raw} def dump(<<_::128>> = raw), do: {:ok, raw} def dump(_), do: :error @impl true def load(<<_::128>> = raw), do: {:ok, %UUID{bytes: raw}} def load(_), do: :error end @doc """ Bulk-generates `n` boxed UUIDv7 values sharing the same millisecond. Delegates to `UUIDv7.bingenerate_many/1`; see that function for the ordering and counter semantics. ## Example iex> uuids = UUIDv7.Boxed.generate_many(4) iex> length(uuids) 4 iex> Enum.all?(uuids, &match?(%UUID{}, &1)) true """ @spec generate_many(pos_integer()) :: [UUID.t()] def generate_many(n) do for bytes <- UUIDv7.bingenerate_many(n), do: %UUID{bytes: bytes} end end