Typed Named ID
UUID compatible, but strictly improved
- Typed. Each TNID carries its type name, so code can reject the wrong kind at runtime, and type checkers at compile time.
- Sortable. Sorting the strings as text sorts the TNIDs by value; TNIDv0 sorts by creation time.
- Readable.
user.Br2flcNDfF6LYICnTnames what it identifies. A UUID doesn’t.
TNID is a draft (spec version 0); libraries are pre-1.0.
One 128-bit value, two stringsexample · TNIDv1
TNID
test.x8MRU0xetVa6QZeZR
22 chars
UUID
cab19f49-5dc7-8c1f-9ab9-8261db92a91c
36 chars
TNIDUUID
Says what it identifiesTNID yes — prefix testUUID no
One exact spellingTNID yes — case-sensitiveUUID no — upper or lower hex
URL-safeTNID yesUUID yes
Valid UUIDTNID yes — UUIDv8UUID yes
nameUUID version and variant (fixed)TNID variantdata
01
One value, four representations
Store it as a UUID or integer; show it as a TNID string.
Guide: Storing and showing TNIDs →TNID string
test.x8MRU0xetVa6QZeZR
UUID form
cab19f49-5dc7-8c1f-9ab9-8261db92a91c
u128 (hex)
0xCAB19F495DC78C1F9AB98261DB92A91C
bytes
CA B1 9F 49 5D C7 8C 1F 9A B9 82 61 DB 92 A9 1C
1111.1111.1111.1111.1111.5555.5555.5555-
5555.5555.5555.5555-
2222.5555.5555.5555-
3344.5555.5555.5555-
5555.5555.5555.5555.5555.5555.5555.5555.5555.5555.5555.5555
scroll →128 bits
#ComponentBitsHolds
1Name20 bits1–4 characters, 5 bits each
2UUID version4 bitsMarks it as UUIDv8
3UUID variant2 bitsMarks it as a standard UUID
4TNID variant2 bits0: TNIDv0, 1: TNIDv1, 2–3: reserved
5Payload100 bitsDefined by the variant
03
Variants
TNIDv0 (variant 0) is time-sortable and, like UUIDv7, reveals its creation time. TNIDv1 is random.
Guide: Choosing a variant →TNIDv0
Time-sortable
- Millisecond timestamp plus 57 random bits
- TNIDs with the same name sort in creation order
- Recommended default for database keys
TNIDv1
Random
- 100 random bits
- No ordering, no embedded time
- For public TNIDs that shouldn’t reveal creation time
Variants 2, 3Reserved, not yet defined.
04
The trade-offs
Real, but rarely decisive. For nearly every system, TNIDs are a better default than UUIDs.
Guide: Compared with other IDs →- Collision odds Higher than UUIDv4’s, still negligible: a billion TNIDv1s per name have about a 1 in 2.5 trillion collision chance.
- Permanent names The name is in the value; renaming a type changes all its TNIDs.
- Case-sensitive by design One spelling per TNID makes the strings sortable. Case-insensitive columns can’t tell them apart; store the UUID form instead.
- Draft spec The spec is version 0, marked draft.
05
Checked by the compiler
Where the language allows, the name is part of the type. A Post TNID won’t compile where a User TNID is expected.
struct User; impl TnidName for User { const ID_NAME: NameStr<'static> = NameStr::new_const("user"); } let user_id = Tnid::<User>::new_v0(); println!("{}", user_id); // user.Br2flcNDfF6LYICnT fn delete_user(id: Tnid<User>) { /* … */ } delete_user(user_id);compiles delete_user(post_id);error[E0308]: mismatched typesconst UserId = Tnid("user"); type UserId = TnidType<typeof UserId>; const userId = UserId.new_v0(); console.log(userId); // user.Br2flcNDfF6LYICnT function deleteUser(id: UserId) { /* … */ } deleteUser(userId);compiles deleteUser(postId);error TS2345: Type 'TnidValue<"post">' is not assignable to 'TnidValue<"user">'