Expand description
§TNID
UUID-compatible IDs with names and compile-time type safety.
TNIDs are UUIDv8-compatible identifiers that include a human-readable name and can be strictly typed at compile time.
use tnid::{Case, NameStr, Tnid, TnidName};
struct User;
impl TnidName for User {
const ID_NAME: NameStr<'static> = NameStr::new_const("user");
}
// Create a time-ordered ID (like UUIDv7)
let user_id = Tnid::<User>::new_time_ordered();
println!("{}", user_id); // user.Br2flcNDfF6LYICnT
// Or a high-entropy ID (like UUIDv4)
let session_id = Tnid::<User>::new_high_entropy();§Why TNIDs?
- Type-safe:
Tnid<User>andTnid<Post>are different types. Accidentally passing a post ID to a user function? Compile error! - Named: IDs include a human-readable name prefix. See
user.Br2flcNDfF6LYICnTin your logs and instantly know what it is. - UUID-compatible: TNIDs are valid UUIDv8s that work directly with Postgres UUID columns and UUID-expecting APIs.
- Compile-time validated: Try to create a TNID with name “INVALID”? Your code won’t even compile.
- Sortable strings: Unlike UUID hex (case-insensitive mess), TNID strings sort correctly and have exactly one representation.
§Status
⚠️ Beta: The TNID spec is still being finalized and shouldn’t be relied on for production use yet. This implementation tracks the evolving spec.
A full specification site will be available at tnid.info.
§Installation
cargo add tnid§Examples
§Creating TNIDs
ⓘ
use tnid::{NameStr, Tnid, TnidName};
use tnid::{NameStr, Tnid, TnidName};
struct Post;
impl TnidName for Post {
const ID_NAME: NameStr<'static> = NameStr::new_const("post");
}
// Time-ordered (v0) - sorts by creation time
let id = Tnid::<Post>::new_v0();
// High-entropy (v1) - maximum randomness
let id = Tnid::<Post>::new_v1();§String Representations
ⓘ
// TNID string format - human-readable, sortable, unambiguous
let tnid_str = id.to_tnid_string();
// "post.Br2flcNDfF6LYICnT" - you can SEE it's a post ID!
// UUID hex format - for databases and APIs that expect UUIDs
let uuid_str = id.to_uuid_string(Case::Lower);
// "cab1952a-f09d-86d9-928e-96ea03dc6af3" - works in Postgres, MySQL, etc.
// Time-ordered IDs sort correctly in BOTH representations!
let id1 = Tnid::<Post>::new_v0();
std::thread::sleep(std::time::Duration::from_millis(10));
let id2 = Tnid::<Post>::new_v0();
assert!(id1.to_tnid_string() < id2.to_tnid_string()); // Sorts correctly!
assert!(id1.as_u128() < id2.as_u128()); // In all representations!§Parsing
ⓘ
// Parse from TNID string
let id = Tnid::<Post>::parse_tnid_string("post.Br2flcNDfF6LYICnT").unwrap();
// Parse from UUID string
let id = Tnid::<Post>::parse_uuid_string("cab1952a-f09d-86d9-928e-96ea03dc6af3").unwrap();
// From raw u128
let id = Tnid::<Post>::from_u128(0xCAB1952A_F09D_86D9_928E_96EA03DC6AF3).unwrap();§Type Safety in Action
ⓘ
struct User;
impl TnidName for User {
const ID_NAME: NameStr<'static> = NameStr::new_const("user");
}
struct Post;
impl TnidName for Post {
const ID_NAME: NameStr<'static> = NameStr::new_const("post");
}
fn delete_user(user_id: Tnid<User>) { /* ... */ }
fn delete_post(post_id: Tnid<Post>) { /* ... */ }
let user_id = Tnid::<User>::new_v0();
let post_id = Tnid::<Post>::new_v0();
delete_user(user_id); // Works!
delete_post(post_id); // Works!
// delete_user(post_id); // Compile error! Can't pass a Post ID to a User function
// delete_post(user_id); // Compile error! Type mismatch caught at compile time§Features
| Feature | Default | Ready | Description |
|---|---|---|---|
time | ✓ | stable | Time-based v0 TNID generation (like UUIDv7) |
rand | ✓ | stable | Random v1 TNID generation (like UUIDv4) |
encryption | beta | Encrypt v0 to v1 to hide timestamps from clients, decrypt on the backend | |
uuid | stable | Convert to/from the uuid crate’s Uuid type | |
serde | alpha | serde::Serialize / serde::Deserialize for Tnid<Name>, DynamicTnid, UuidLike | |
sqlx-postgres | alpha | SQLx Type/Encode/Decode for Postgres UUID columns | |
sqlx-mysql | alpha | SQLx Type/Encode/Decode for MySQL/MariaDB (BINARY/BLOB/TEXT) | |
sqlx-sqlite | alpha | SQLx Type/Encode/Decode for SQLite (BLOB/TEXT) |
§Documentation
See the API documentation for complete details.
§License
MIT
Re-exports§
pub use dynamic_tnid::DynamicTnid;
Modules§
- dynamic_
tnid - Runtime-determined TNIDs without compile-time type checking.
- encryption
encryption - TNID encryption utilities.
- internals
internals - Internal functions exposed for advanced usage behind the
internalsfeature.
Structs§
- NameStr
- A validated TNID name string.
- Tnid
- A type-safe TNID parameterized by name.
- Uuid
Like - A wrapper for 128-bit values that may or may not be valid TNIDs (or UUIDs for that matter).
Enums§
- Case
- The case (upper/lower) for hexadecimal string formatting.
- Data
Encoding Error - Error when decoding a TNID data string.
- Name
Bits Validation - Result of validating the name bits in a TNID.
- Name
Error - Error when creating a
NameStrfrom a string. - Parse
Tnid Error - Error when parsing a TNID from a string or u128.
- Parse
Uuid String Error - Error when parsing a UUID string.
- Tnid
Variant - The 4 possible TNID variants.
Traits§
- Tnid
Name - Intended to be used on empty structs to create type checked TNID names.