idgenkit

Getting started

Each library has the same five parts: ULID (with a monotonic generator), UUIDv4 and UUIDv7 (with a monotonic v7 generator and a strict parser), relative IDs (keyed by a secret, with a monotonic mode), Snowflake (with compose and parse), and Nano ID (with custom alphabets). Java and Python return their standard UUID types. The README shows the relative ID API for each language and database, and how each one loads its secret.

Python

pip install idgenkit
from idgenkit import ULID, MonotonicULID, MonotonicUUID7, Snowflake, nanoid, custom_alphabet
from idgenkit import uuid4, uuid7, uuid7_timestamp, uuids

id = ULID.generate()            # str(id) -> '01J...', id.timestamp_ms, id.to_uuid()
parsed = ULID.parse("01ARYZ6S41TSV4RRFFQ69G5FAV")
mono = MonotonicULID()          # thread-safe, strictly increasing
nxt = mono.next()

v7 = uuid7()                    # uuid.UUID; the stdlib's uuid.uuid7() on 3.14+
v4 = uuid4()                    # uuid.uuid4()
ms = uuid7_timestamp(v7)        # ValueError for other versions
u = uuids.parse("017f22e2-79b0-7cc3-98c4-dc0c0c07398f")  # rejects braces, urn:, no hyphens
nxt7 = MonotonicUUID7().next()

gen = Snowflake(machine_id=7, epoch_ms=1288834974657)
sf = gen.next_id()
parts = gen.parse(sf)           # SnowflakeParts(timestamp_ms, machine_id, sequence)

s = nanoid()                    # 21 URL-safe characters
hex_id = custom_alphabet("0123456789abcdef", 12)
h = hex_id()

Rust

[dependencies]
idgenkit = "0.1"
use idgenkit::{nanoid, snowflake::Snowflake, ulid::{MonotonicGenerator, Ulid}};
use idgenkit::uuid::{MonotonicV7Generator, Uuid};

let id = Ulid::new();                       // Display -> 26 chars
let parsed = Ulid::parse("01ARYZ6S41TSV4RRFFQ69G5FAV")?;
let mono = MonotonicGenerator::new();       // Sync, strictly increasing
let next = mono.next()?;

let v7 = Uuid::new_v7()?;                   // Display -> 36 chars
let v4 = Uuid::new_v4();
let ms = v7.timestamp_ms()?;                // Error::NotUuidV7 for other versions
let next7 = MonotonicV7Generator::new().next()?;

let gen = Snowflake::new(7, 1288834974657)?;
let sf = gen.next_id()?;
let parts = gen.parse(sf);

let s = nanoid::nanoid();
let hex = nanoid::CustomAlphabet::new("0123456789abcdef", 12)?;
let s = hex.generate();

Go

go get github.com/rahulbsw/idgenkit/go
import (
    "github.com/rahulbsw/idgenkit/go/nanoid"
    "github.com/rahulbsw/idgenkit/go/snowflake"
    "github.com/rahulbsw/idgenkit/go/ulid"
    "github.com/rahulbsw/idgenkit/go/uuid"
)

id := ulid.New()                    // ulid.ULID ([16]byte), id.String() -> 26 chars
u, err := ulid.Parse("01ARYZ6S41TSV4RRFFQ69G5FAV")
mono := ulid.NewMonotonic()         // safe for concurrent use
next, err := mono.Next()

v7 := uuid.NewV7()                  // uuid.UUID ([16]byte), v7.String() -> 36 chars
v4 := uuid.NewV4()
ms, err := v7.Time()                // uuid.ErrNotV7 for other versions
next7, err := uuid.NewMonotonic().Next()

gen, err := snowflake.New(7, 1288834974657) // machine id, epoch ms
sf, err := gen.Next()
parts := gen.Parse(sf)              // TimestampMs, MachineID, Sequence

s := nanoid.New()
hex, err := nanoid.CustomAlphabet("0123456789abcdef", 12)
s = hex.Generate()

Java

<dependency>
  <groupId>io.github.rahulbsw</groupId>
  <artifactId>idgenkit</artifactId>
  <version>0.1.0</version>
</dependency>
import io.github.rahulbsw.idgenkit.*;
import java.util.UUID;

Ulid id = Ulid.generate();                  // id.toString(), id.toUuid(), id.timestamp()
Ulid parsed = Ulid.parse("01ARYZ6S41TSV4RRFFQ69G5FAV");
MonotonicUlid mono = new MonotonicUlid();   // thread-safe
Ulid next = mono.next();

UUID v7 = Uuids.v7();                       // java.util.UUID
UUID v4 = Uuids.v4();                       // UUID.randomUUID()
long ms = Uuids.timestamp(v7);              // IllegalArgumentException for other versions
UUID u = Uuids.parse("017f22e2-79b0-7cc3-98c4-dc0c0c07398f"); // stricter than UUID.fromString
UUID next7 = new MonotonicUuidV7().next();

Snowflake gen = new Snowflake(7, 1288834974657L);
long sf = gen.nextId();
Snowflake.Parts parts = gen.parse(sf);

String s = NanoId.generate();
NanoId hex = NanoId.customAlphabet("0123456789abcdef", 12);
String h = hex.next();

PostgreSQL

Install from the release tarball for your PostgreSQL major version: copy lib/* to $(pg_config --pkglibdir) and extension/* to $(pg_config --sharedir)/extension. Or build it with make -C postgres install.

# postgresql.conf
shared_preload_libraries = 'idgenkit'         # needed for Snowflake before PostgreSQL 17
idgenkit.machine_id = 7                       # 0..1023, unique per server
idgenkit.snowflake_epoch_ms = '1288834974657' # set once, never change
CREATE EXTENSION idgenkit;

-- ULID stored in the native 16-byte uuid type
CREATE TABLE orders (
  id uuid PRIMARY KEY DEFAULT ulid_to_uuid(ulid_generate_monotonic()),
  ...
);
SELECT ulid_from_uuid(id), ulid_timestamp(id) FROM orders;

-- UUIDv7 on PostgreSQL 14+ (18 also has a built-in uuidv7())
CREATE TABLE invoices (id uuid PRIMARY KEY DEFAULT uuidv7_generate_monotonic(), ...);
SELECT uuidv7_timestamp(id) FROM invoices;

-- Snowflake as bigint
CREATE TABLE events (id bigint PRIMARY KEY DEFAULT snowflake_generate(), ...);
SELECT * FROM events WHERE id >= snowflake_from_timestamp(now() - interval '1 hour');

-- Nano ID for public tokens
ALTER TABLE orders ADD COLUMN share_token text COLLATE "C" UNIQUE DEFAULT nanoid_generate();

Every function:

Function Returns
ulid_generate() text, 26 characters
ulid_generate_monotonic() text, strictly increasing within the session
ulid_generate_uuid() uuid, a ULID in the native type
ulid_to_uuid(text) / ulid_from_uuid(uuid) conversion
ulid_timestamp(text or uuid) timestamptz
uuidv7_generate() uuid, version 7
uuidv7_generate_monotonic() uuid, version 7, strictly increasing within the session
uuidv7_timestamp(uuid) timestamptz; error for other versions
gen_random_uuid() (built in) uuid, version 4
snowflake_generate() bigint
snowflake_timestamp(bigint) timestamptz
snowflake_machine_id(bigint), snowflake_sequence(bigint) int
snowflake_from_timestamp(timestamptz) smallest id at that time, for range scans
nanoid_generate(size = 21, alphabet = URL alphabet) text

MySQL

cp idgenkit_udf.so "$(mysql_config --plugindir)"
mysql -u root < install.sql
CREATE TABLE orders (id BINARY(16) PRIMARY KEY, ...);
INSERT INTO orders (id, ...) VALUES (ulid_to_bin(ulid_generate_monotonic()), ...);
SELECT bin_to_ulid(id), ulid_timestamp(bin_to_ulid(id)) FROM orders;

CREATE TABLE invoices (id BINARY(16) PRIMARY KEY, ...);
INSERT INTO invoices (id, ...) VALUES (UUID_TO_BIN(uuidv7_generate_monotonic()), ...);
SELECT BIN_TO_UUID(id), uuidv7_timestamp(id) FROM invoices;   -- ms since 1970
SELECT uuidv4_generate();

SELECT snowflake_generate(7, 1288834974657);   -- machine id, epoch
SELECT nanoid_generate(12, '0123456789abcdef');

MySQL folds table-independent function calls inside aggregates, just as it does for UUID(): SELECT COUNT(DISTINCT ulid_generate()) returns 1. Values generated per row, including INSERT ... SELECT, are unique.

Redis

redis-server --loadmodule /path/idgenkit.so MACHINE_ID 7 EPOCH_MS 1288834974657
> ULID.GENERATE
"01J8Y4Q9M3X3B0N6E3P8V7T2QK"
> ULID.MONOTONIC
> ULID.TIME 01ARYZ6S41TSV4RRFFQ69G5FAV
(integer) 1469918176385
> UUIDV7.GENERATE
> UUIDV7.MONOTONIC
> UUIDV7.TIME 017f22e2-79b0-7cc3-98c4-dc0c0c07398f
(integer) 1645557742000
> UUIDV4.GENERATE
> SNOWFLAKE.GENERATE
> SNOWFLAKE.PARSE 1541815603606036480
> NANOID.GENERATE 12 0123456789abcdef

Build from source

git clone https://github.com/rahulbsw/idgenkit && cd idgenkit
make test        # every library and database extension
make bench       # benchmark report in bench/results/