Published:
←
2025-05-21
Category:
←
Code
Tags:
Ansible
Caddy
Hatchbox
← Lobsters
Lua
TypeID
nginx
I’ve published a Lua implementation of TypeId:
https://github.com/pushcx/typeid-lua
TypeID is a nice standard for creating unique id tokens with a Stripe-like ⊕An aside to Stripe from an Xtripe: Please write a eng blog post about the features and history of tokens. It would be pillar content that would be enormously popular for meaningfully advancing the state of the art to a new standard. hungarian notation:
TypeIDs are a modern, type-safe extension of UUIDv7. Inspired by a similar use of prefixes in Stripe’s APIs.
TypeIDs are canonically encoded as lowercase strings consisting of three parts:
- A type prefix (at most 63 characters in all lowercase snake_case ASCII [a-z_]).
- An underscore ‘_’ separator
- A 128-bit UUIDv7 encoded as a 26-character string using a modified base32 encoding.
Here’s an example of a TypeID of type user:
A formal specification defines the encoding in more detail.
Cleverly, the spec comes with a suite of labeled test cases of valid and invalid examples. I wish more specs did this!
I’m happy with the functionality my library offers, and there was the familiar delight of making things the first time I round-tripped a TypeID.
This was a fun practice project for me. I’ve used Lua more and more over the last few years in video game scripts and my window manager, and while 1-based array indexes will always feel odd, I think there’s a lot of potential in the language.
I experimented with style while implementing, and a lot of what I’m taking away from it is idioms I’m ignorant of. The TypeID is more OO style and returns an object with a method; the Base32 and UUID7 modules work on primitives. After implementing, I guess users would probably prefer getting a primitive back, as there doesn’t seem to be an idiomatic way to type-check. A module can export a trusted constructor, but without types there’s no way to use that to prevent instantiating invalid objects; everything is a table anyways. Coming from Ruby and ActiveRecord it’s frustrating to have most of a solution to the pervasive problem of passing around invalid objects but not be able to complete it.
I guess have to read popular libraries to get a feel for style. I don’t really know what level to aim at between “data-hiding high-level interface” and “yolo, all primitives and seams showing for perf”. Maybe it’s different inside and outside of games.
Along those lines, I ported Base32 from the official TypeID Golang implementation and then wrote UUID7 in bytes to match it. But all that intermediate bit twiddling could be simplified by generating a UUID7 directly into the Base32 encoding if I wanted to spend a lot more time on this.
Maybe I’m looking under the wrong name, but it seems odd there isn’t a bitfield type I could use, given Lua’s popularity in games. Some searching turned up a library but the absence of multi-bit operations seems inconvenient. Which points to:
There’s a code smell in TypeID: the nested conversions in the metatable uuid function suggest the internal representation of suffix is wrong. The syntactic distinction between .field and :method() means duplicating the data into two fields, exposing the internal representation by it being a field and the other a method, or getting away from what seems like common struct-y style and making both into methods. I’ve really grown to like the way Ruby’s optional parenthesis blur the line on fields and methods.
I wrote this library because I’d like to add a TypeID request identifier as a trace ID that nginx would generate and log, and pass along through Rails logs to MariaDB logs. It’s overkill for Lobsters but once a year I really wanted the ability to correlate logs like that. While the ROI may not really justify the time, it was a uniquely well-scoped small practice project.
Writing in Lua and adding the Lua module support to nginx seemed an easier path than writing in C and adding that compilation step to the deploy pipeline. Ultimately though, I’m not going to write that wrapper module.
On a parallel track, our ansible setup has slowly been succumbing to bit rot and my inexpert maintenance. I learned that Hatchbox could fill the same role and paying a couple bucks a month means it’s maintained by an expert professional. So 355e3b are going to move our hosting over soon, and it uses Caddy instead of nginx, so I guess I’ll wrap the official TypeID Golang implementation in a Caddy module instead. Still, it’s rewarding to contribute to TypeID’s list of supported languages.