Skip to contents

Identifiers arrive as opaque strings: a UUID from a database, a ULID from a log line, a KSUID from an event stream, a Snowflake from a chat platform. Each one carries information, often a creation time, sometimes a node or a counter. Reading it means knowing which format the string is, and that is the step most code guesses at.

zuid is a complete UUID implementation for R, with the formats around it. The core is RFC 9562 in full: versions 1 to 8, nil and max, and every text form. Around it are the same 128 bits in other alphabets, and fourteen kindred formats: ULID, TypeID, UPID, SCRU128, SCRU64, KSUID, Snowflake and Sonyflake, XID, ObjectId, NanoID, Cuid2, Sqids and CID. Each format’s generator is named after it, and one id_ vocabulary reads, checks and converts all of them.

  • Detection never guesses. A string that could be two formats is NA, with the candidates listed. A hint settles it.
  • Inspection never raises on input. id_valid(), id_fields() and id_time() return NA for what they cannot read.
  • 128 bits do not make a UUID. A ULID’s bytes stay a ULID until you convert them, and converting is explicit.
  • Entropy comes from the operating system. set.seed() never changes a generated identifier. Tests pass the random bytes in instead.
  • No Imports. The C code is the package’s own, checked against the reference vectors of each format that publishes them.

Installation

Install the released version from CRAN:

Or the development version from GitHub:

# install.packages("pak")
pak::pak("pedrobtz/zuid")

Usage

id_detect() names the format of each string. The lowercase ULID could also be a TypeID without a prefix, so its answer is NA:

ids <- c(
  "018f6b1c-2d3e-7a4b-8c5d-6e7f8091a2b3",
  "01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "01arz3ndektsv4rrffq69g5fav",
  "0ujtsYcgvSTl8PAuAdqWYSMnLOv"
)
found <- id_detect(ids)
as.vector(found)
#> [1] "uuid"  "ulid"  NA      "ksuid"
attr(found, "candidates")[[3]]
#> [1] "ulid"   "typeid"

id_parse() gives a typed vector, and id_time() reads the creation time each format records:

uuid <- id_parse(ids[1])
uuid
#> <zuid_uuid[1]>
#> [1] 018f6b1c-2d3e-7a4b-8c5d-6e7f8091a2b3
id_time(uuid)
#> [1] "2024-05-12 04:42:39 UTC"
id_time(id_parse(ids[4], "ksuid"))
#> [1] "2017-10-10 04:00:47 UTC"

id_fields() reads everything else a format knows. A Snowflake means nothing without its layout, so the layout is an argument:

discord <- id_layout("discord")
discord
#> <zuid_layout discord>
#> | time 42 | worker 5 | process 5 | sequence 12 |
#> time: ms units from 2015-01-01T00:00:00.000Z
id_fields(id_parse("175928847299117063", "snowflake", layout = discord))[
  c("time", "node", "sequence")
]
#>                  time               node sequence
#> 1 2016-04-30 11:18:25 worker=1,process=0        7

The same 16 bytes can be written in other alphabets, read back, or carried into another format:

id_format(uuid, "base58")
#> [1] "CB7z5qdx3BKrzyaKnqWbU"
id_format(uuid, "words")
#> [1] "advertised-impunity-counterrevolutionary-lecher-munition-injecting-maladroit-pitch"
id_parse("CB7z5qdx3BKrzyaKnqWbU", "uuid", form = "base58")
#> <zuid_uuid[1]>
#> [1] 018f6b1c-2d3e-7a4b-8c5d-6e7f8091a2b3
id_convert(uuid, "typeid", prefix = "user")
#> <zuid_typeid[1]>
#> [1] user_01hxnhrb9yf95rrqbefy0938nk

Generators read the clock and the operating system’s entropy once per call. Version 7 UUIDs and ULIDs are monotonic within a session:

uuid(3)
#> <zuid_uuid[3]>
#> [1] 01a1ab9e-45b4-733a-8622-f01f4357e063 01a1ab9e-45b4-733b-ba6a-ac9443264140
#> [3] 01a1ab9e-45b4-733c-b6d1-18c903b1debd
ulid(2)
#> <zuid_ulid[2]>
#> [1] 01M6NSWHDP667MEMNBW2F8SFFY 01M6NSWHDP667MEMNBW2F8SFFZ

A generator is named after its format, as in uuid() and ulid(). Every function that takes identifiers starts with id_ and takes the vector first. A function that inspects returns NA where the input says nothing; a function that builds raises a classed zuid_error.

Learn more

Acknowledgements

zuid is checked against the reference vectors of RFC 9562, TypeID’s valid.yml and invalid.yml, Discord’s Snowflake example, and the specifications or reference implementations of ULID, UPID, SCRU128, SCRU64, KSUID, XID, Sqids, NanoID, Proquint, Niceware and multiformats CID. The Sqids blocklist and the Niceware word list are data from their upstream projects; inst/COPYRIGHTS names their sources and authors.