Advanced use-cases for TypeScript pre-processing: 🎩 Gents Part 2
Earlier this year I wrote an introductory blog post to a small tool I had developed called 🎩 Gents. Essentially it served as a pre-processor for code I wanted to use, such as the keys of certain lookup tables while maintaining type-safety.
The main reason was that I was using this code for a side-project, and I have little spare time to work on it. This has made me paranoid, as managing a complex project with frequent deployments and little testing has led me to depend heavily on TypeScript to ensure my code will behave as I hope, without any holes.
Of course, that’s the ultimate dream and I’m far from that. Still, the v1.0.0 introduction of my tool did improve my accuracy a bit by ensuring that a variety of lookup tables could be used in type-safe ways.
I’ve taken the feedback of folks into account and created a new release of my tool, v1.1, that solves my original use-case without generating a lot of unnecessary code. It also saves me a bit of time in having to regenerate these types.
It also removed hundreds of lines of code and several files.
This is a project borne out of some of the limitations I found in the TypeScript types system. Namely, there are times…
While I don’t necessarily need to import this function from a library, as its implementation is relatively trivial, it does help provide some consistency through the many lookup tables in my project.
Using the type system directly makes the code cleaner and more convenient for me.
However since my original post, a number of more complex use-cases have appeared to make the original intention behind 🎩 Gents still useful.
Now I’m adding a battle system with my characters and items. I want the user to be able to use items during the battle. However, not every item is going to be useful. In my original example everything was a healing item. However, I’ve added treasure items like gold and silver that have no battle purpose.
I need to make sure that every battle-ready item I create appears in this battle system lookup table, while I can safely ignore any other item.
However, I’m not putting a lot of time into this! I may add a new healing item that players pick up and they’ll be frustrated if I’ve forgotten to add a battle implementation.
This is something that I can still manage with the pre-processing system. The
Inventory type is configured so that every
BattleItemId needs to have some implementation.
Due to my code’s organization between frontend and backend, I can’t just merge these two interfaces together in
all-items.ts . That could make my life easier, but I’m not going to have time to refactor it all if it’s even possible.
battle-system.ts will throw an error until I add another implementation for
cursedscroll , while I can add all kinds of treasure at a whim.
However, I also cannot create a separate map for battle items, since there’s a second dimension of items that can be bought or sold. I can create a
ValueItemId as a separate type that captures a different slice of my item pool. I may also imagine a special scroll that can be used in battle that has no value.
The battle system runs in the backend. Players can select spells on the frontend that can be executed with specific callbacks. They may want to know more information on what they can do. I can use a pre-processor to extract only a few fields to read on the frontend.
This simplified map provides me with only necessary fields, removing the functions and having to know about the battle system. Additionally, I can create a separate array that contains support spells like charms for allies in a battle. This can be useful in designing the battle UI, since not every spell will be used on an enemy.
This example is rather specific, but the implementation does work.
Constructing new constants
When battling against others of the
Character class, I have a lookup table with all of the characters defined with an incrementing id.
If I want to generate an encounter with a silver dragon, I need to remember that the identifier is
c002. While I can create a key to make sure the encounter is technically valid, it doesn’t mean that it points to the right character.
I can use a pre-processor to generate constants that use the much more useful
These use-cases could have other workarounds perhaps. I’m sure if I really sat down for a while and planned out this project I could’ve done something much better. Yet it’s a side project, which I cannot dedicate lots of time to planning. (Of course that’s perhaps a problem in itself, but not one I have time to resolve).
While I’d rather these things be baked into TypeScript directly to grant me some relief, I’m relatively happy by this approach to short circuit the complexity of types.