<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raif Harik</title>
    <description>The latest articles on DEV Community by Raif Harik (@reharik).</description>
    <link>https://dev.to/reharik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F845981%2Ffa5c77eb-d9af-4117-ad40-fc1e5d766743.jpeg</url>
      <title>DEV Community: Raif Harik</title>
      <link>https://dev.to/reharik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reharik"/>
    <language>en</language>
    <item>
      <title>I argued IoC was pointless in JavaScript, forgetting I'd published my own container to npm.</title>
      <dc:creator>Raif Harik</dc:creator>
      <pubDate>Tue, 20 Oct 2026 18:12:05 +0000</pubDate>
      <link>https://dev.to/reharik/i-argued-ioc-was-pointless-in-javascript-forgetting-id-published-my-own-container-to-npm-57of</link>
      <guid>https://dev.to/reharik/i-argued-ioc-was-pointless-in-javascript-forgetting-id-published-my-own-container-to-npm-57of</guid>
      <description>&lt;p&gt;&lt;em&gt;Why I built ioc-manifest: convention-based DI codegen for TypeScript and Awilix.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Some weeks ago I had a job interview.&lt;/p&gt;

&lt;p&gt;We were chatting away — this is the tech stack we use, and this lib and that lib, and we use Awilix for IoC.&lt;/p&gt;

&lt;p&gt;"Really," I said. "You use IoC?"&lt;/p&gt;

&lt;p&gt;Inversion of control. In JavaScript.&lt;/p&gt;

&lt;p&gt;"Nobody uses IoC in JavaScript. What would you even use it for? You've got that delightful ES6 modules implementation that almost does some of what you wish it did. I mean, I get why you &lt;em&gt;might&lt;/em&gt; want to use it. But nobody does. You'll be fighting every engineer you come across."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What a dunce,&lt;/em&gt; I thought.&lt;/p&gt;

&lt;p&gt;"Yes, we use IoC, and every developer I know uses it. If they don't use a library they at least wire up a poor man's container."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What a dunce,&lt;/em&gt; he thought.&lt;/p&gt;

&lt;p&gt;Well. That interview went really well.&lt;/p&gt;

&lt;p&gt;When I started using Node about ten years ago I came from C#, where I'd used IoC heavily. In Node I found not just little appetite for it but open hostility — the very notion that JavaScript might have the same concerns and patterns as, say, any other language was contemptible. So I wrote my own container. Screw 'em. Eventually, once I finally accepted I was never going to win this me-against-the-whole-world battle, I quietly succumbed.&lt;/p&gt;

&lt;p&gt;By the time I was sitting in that room I'd licked my wounds into well-worn calluses. But somewhere in my primordial brain I could still hear, or smell, the memory of why IoC was worth having in the first place. So when this guy told me he was using it, my ears perked up.&lt;/p&gt;

&lt;p&gt;I looked up Awilix.&lt;/p&gt;

&lt;p&gt;Hm. Not bad. Lets me use functions instead of classes ( don't get me started on classes in JS ) and basically wrap a module in a factory and feed it the dependencies.&lt;/p&gt;

&lt;p&gt;Wait a minute!&lt;/p&gt;

&lt;p&gt;That's what I wrote almost a decade ago! It was called dagon and it's still pulling eight downloads a week on npm.&lt;/p&gt;

&lt;p&gt;Awilix is better than dagon no doubt. Mine was pretty naive. Errors surfaced at runtime, dependencies resolved by filename, magic strings abounded -- and nothing checked any of it, because there was nothing to check it &lt;em&gt;with&lt;/em&gt;. I mean TypeScript existed but I didn't know what the hell it was back then.&lt;/p&gt;

&lt;p&gt;Awilix has TypeScript. Sort of. It has types for its APIs. But the container itself is a bag of strings, and the graph inside it is invisible to the compiler. You still find out what's wrong by running the program and reading a stack trace. Which, to be fair, is what every container in this ecosystem does.&lt;/p&gt;

&lt;p&gt;So I decided to give it a try.&lt;/p&gt;

&lt;p&gt;It wasn't long before the old muscle memory started to kick in and the joy of design to interfaces and other patterns started to return. But one thing I just couldn't get past was writing the same dependency name over and over to configure it. And I soon started running into problems around packages in my mono-repo. Registrations split across packages, no way to express that one supplies what another demands — so you find out you’ve broken the contract when the app that composes them fails to boot.&lt;/p&gt;

&lt;p&gt;One thing I really loved about StructureMap in C# was how it was configured mostly by convention with configuration only for the edge cases. Awilix has &lt;code&gt;loadModules&lt;/code&gt;. But in C#, convention-based registration is safe because the consumer side is typed independently of the container. A constructor taking &lt;code&gt;ILogger&lt;/code&gt; is checked by the compiler whether StructureMap wired it or I did. In TypeScript, a factory destructuring &lt;code&gt;{ logger }&lt;/code&gt; from a cradle has no type of its own — so convention-based loading means giving up the only check you had. You can have conventions or you can have types. Not both. Again things break at runtime not at design time.&lt;/p&gt;

&lt;p&gt;It seemed to me that a lot of this and more could be handled with a quick codegen step. A lot of libraries require codegen these days e.g. graphql with ts, prisma. Adding one more especially if the payoff is really high seemed an easy decision. So I created what I considered an optimal configuration scenario. Largely stolen from StructureMap :).&lt;/p&gt;

&lt;p&gt;I wanted modules to be as container-agnostic as possible, while still being discoverable, but I also needed to distinguish between what goes in a container and what doesn't. Adopting a configurable naming convention was the easiest and best solution for that. So I decided a declared function should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AddUserDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AddUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NewUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;build__addUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AddUserDeps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AddUser&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the &lt;code&gt;build__&lt;/code&gt; prefix is what marks a function as container-eligible. I started with just &lt;code&gt;build&lt;/code&gt;, but I kept writing &lt;code&gt;buildQuery&lt;/code&gt; and &lt;code&gt;buildPath&lt;/code&gt; for things that had no business in a container. The double underscore made the distinction unambiguous — and visually obvious at a glance. You can configure it to whatever you like.&lt;/p&gt;

&lt;p&gt;Classes work the same way. &lt;code&gt;implements&lt;/code&gt; declares the contract, the constructor’s deps object declares the demands, and the codegen reads both. The convention is about what gets registered, not how you write it.&lt;/p&gt;

&lt;p&gt;The beauty is that this is how I’d define the module anyway. You have to declare your inputs. You have to declare your outputs. The codegen reads both and builds the container from them. Nothing is declared twice. The type is the registration.&lt;/p&gt;

&lt;p&gt;And here's the payoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserServiceDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;addUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SomeInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;build__userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;addUser&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;UserServiceDeps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;addUser is wired. Not written into a registration, not added to a cradle interface. Just declared as the type that it is.&lt;/p&gt;

&lt;p&gt;I also spent considerable effort on how errors are expressed. A cryptic error at runtime makes me crazy. Because we can accrue so much information from the AST walk while building the graph, the error messages can be very explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[externals] Unsatisfied: nothing supplies "logger", which @apps/api expects the container to already have.
  key:       "logger"  demanded by @apps/api
  demanded:  Logger
  No composed manifest offers this key in its IocGeneratedCradle.
  Suggested fix: Register a factory for Logger under key "logger" in this app, or compose another manifest that supplies it.
  → docs: https://reharik.github.io/ioc-manifest/monorepo/composition#externals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this error comes during the build, not at runtime. Missing dependencies, cyclic references, and lifetime violations are all caught and reported with the file, the line and the fix.&lt;/p&gt;

&lt;p&gt;A runtime-only container can’t come close to this. It has keys and nothing else — no file, no line, no idea which package expected what. Furthermore, runtime errors only show up when they happen, one at a time. With in-depth build-time analysis you get all the errors up front whether they are exercised or not.&lt;/p&gt;

&lt;p&gt;The build-time graph doesn't just catch things early, it makes the runtime errors legible too. If your code does make it to runtime with an issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ioc] Cannot resolve dependency "readRepository".

Resolution chain:
Server (server) [src/server.ts]
  -&amp;gt; KoaServer (koaServer) [src/koaServer.ts]
    -&amp;gt; GraphQLServer (graphQLServer) [.../createGraphQLServer.ts]
      -&amp;gt; readRepository ✖ no registered implementation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here I am, ten years later, having written the same library twice.&lt;/p&gt;

&lt;p&gt;dagon walked a directory and named things after their files. It worked, for the most part, until it threw at startup with an error message that was barely comprehensible. I knew what I wanted — convention over configuration, modules that don’t know a container exists, functionality with a decent DX. I just didn't have the tools or the Node experience to really make it happen.&lt;/p&gt;

&lt;p&gt;Many years later, knowing the language much better, I find that TypeScript’s AST knows what every module declares and what every module demands. ioc-manifest just reads it and hands Awilix a container it couldn’t have built for itself — one the compiler can see, and complain about, before anything runs.&lt;/p&gt;

&lt;p&gt;It may not have been my finest hour or best interview, but I'm now happily back in the land of IOC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ioc-manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>node</category>
    </item>
    <item>
      <title>Your status enum lives in five files</title>
      <dc:creator>Raif Harik</dc:creator>
      <pubDate>Thu, 13 Aug 2026 20:50:11 +0000</pubDate>
      <link>https://dev.to/reharik/your-status-enum-lives-in-five-files-147h</link>
      <guid>https://dev.to/reharik/your-status-enum-lives-in-five-files-147h</guid>
      <description>&lt;p&gt;You need to add one value to a status. &lt;code&gt;PENDING&lt;/code&gt;, &lt;code&gt;AWAITING_PAYMENT&lt;/code&gt;, &lt;code&gt;ACTIVE&lt;/code&gt;, &lt;code&gt;SUSPENDED&lt;/code&gt; — and now &lt;code&gt;ARCHIVED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So you go looking for where statuses live. Here's what you find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;constants.ts&lt;/code&gt; — a frozen const object, &lt;code&gt;ORDER_STATUS&lt;/code&gt;, screaming case.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;types.ts&lt;/code&gt; — a union of string literals, written by someone else, with the same members in a different order.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;orderService.ts&lt;/code&gt; — a switch with the literals inlined, because whoever wrote it didn't find either of the above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrations/0031_orders.ts&lt;/code&gt; — a &lt;code&gt;CHECK&lt;/code&gt; constraint listing the values a third time, in SQL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderFilter.tsx&lt;/code&gt; — a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; with hardcoded &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; elements, and display labels — someone typed &lt;em&gt;Awaiting payment&lt;/em&gt; by hand — that exist nowhere else in the codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five representations. None of them is the source of truth.&lt;/p&gt;

&lt;p&gt;The problem isn't which of the five you pick. It's that there's no canonical place or shape for this kind of data, so the concept gets re-invented every time someone needs it, and none of the copies know about each other.&lt;/p&gt;

&lt;p&gt;That hurts in three ways, and they get worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's tedious.&lt;/strong&gt; Adding &lt;code&gt;ARCHIVED&lt;/code&gt; means touching all five, and nothing — not the compiler, not a test — will tell you when you've found four.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's undiscoverable.&lt;/strong&gt; Six months ago someone needed this same concept, searched for &lt;code&gt;OrderStatus&lt;/code&gt;, found nothing, and added &lt;code&gt;OrderState&lt;/code&gt; in a different file. Now there are six. Nobody knows. It'll surface as a bug in about a year.&lt;/p&gt;

&lt;p&gt;Or alternatively you're the one adding it fresh. You know the concept doesn't exist yet. You also know that whichever form you pick, the next person won't find it, and you'll be back here. So you sit there for four minutes deciding between a const object and a union and an actual &lt;code&gt;enum&lt;/code&gt;, aware the whole time that the decision doesn't matter and the discoverability does. That friction is small and constant, and it never stops, and it is entirely self-inflicted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's silently wrong.&lt;/strong&gt; This is the big one.&lt;/p&gt;

&lt;p&gt;The migration seeds rows as &lt;code&gt;'archived'&lt;/code&gt;. The union says &lt;code&gt;'ARCHIVED'&lt;/code&gt;. The filter compares them, matches nothing, and archived orders quietly stop appearing in the list. No error. No exception. No failing test.&lt;/p&gt;

&lt;p&gt;Postgres is satisfied: the value passes the &lt;code&gt;CHECK&lt;/code&gt; constraint, which was written in SQL by someone reading a different file. TypeScript is satisfied: the row was cast to &lt;code&gt;OrderStatus&lt;/code&gt; on the way in, and a cast is a promise, not a check. Both halves of the system are internally consistent, they disagree with each other, and there is no layer whose job it is to notice.&lt;/p&gt;

&lt;p&gt;That's not just a maintenance burden. It's a bug class — two independent declarations of the same truth, in two languages, that no tool compares.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing everyone writes
&lt;/h2&gt;

&lt;p&gt;You've written this. I've written it in every codebase I've worked in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ORDER_STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;AWAITING_PAYMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AWAITING_PAYMENT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ACTIVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;SUSPENDED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUSPENDED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ORDER_STATUS&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ORDER_STATUS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a good trick. It gets you a value you can iterate and a type you can annotate with, from one declaration.&lt;/p&gt;

&lt;p&gt;Then you keep going, and watch what it doesn't cover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Display labels: somewhere else entirely.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STATUS_LABELS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Dropdown options: a third shape, derived by hand.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ORDER_STATUS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STATUS_LABELS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// Parsing a string from the database: unchecked.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 👈 a lie, and it will bite&lt;/span&gt;

&lt;span class="c1"&gt;// Exhaustiveness: only if you remember the incantation.&lt;/span&gt;
&lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;_exhaustive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`unhandled: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the const-object trick solved &lt;em&gt;one&lt;/em&gt; of the five places. The labels, the options, the parse boundary, and the exhaustiveness check each grew their own file. You've replaced "five representations of the values" with "one representation of the values and four satellites that drift from it."&lt;/p&gt;

&lt;p&gt;And the drift is worse than it looks. Add &lt;code&gt;ARCHIVED&lt;/code&gt; and &lt;code&gt;STATUS_LABELS&lt;/code&gt; fails to compile — good. But nobody &lt;em&gt;chose&lt;/em&gt; that. &lt;code&gt;Record&amp;lt;OrderStatus, string&amp;gt;&lt;/code&gt; just happens to be exhaustive by construction; the safety is an accident of how the type got written. The &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; doesn't fail. The &lt;code&gt;as OrderStatus&lt;/code&gt; cast doesn't fail. The &lt;code&gt;CHECK&lt;/code&gt; constraint doesn't fail.&lt;/p&gt;

&lt;p&gt;So some of your satellites break loudly and some break silently, for reasons that have nothing to do with which ones matter — and you can't tell which is which by looking. Worse, the ones that break loudly give you false confidence about the ones that don't. You add a value, something turns red, you fix it, the build goes green, and you ship the bug anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  One declaration, and the satellites come with it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Enumeration&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enumeration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OrderStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;awaitingPayment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;suspended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The string &lt;code&gt;'OrderStatus'&lt;/code&gt; isn't redundant with the variable name.&lt;/strong&gt; JavaScript has no reflection — a function can't know what it's being assigned to — and while that drives me absolutely insane, it's only half the reason. The real reason is that this string is the wire tag. It survives JSON. It's what lets a value that crossed a process boundary be recognized as an &lt;code&gt;OrderStatus&lt;/code&gt; when it comes back, rather than as a string that happens to look like one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The type alias looks circular and isn't.&lt;/strong&gt; TypeScript resolves types and values in separate namespaces, so &lt;code&gt;OrderStatus&lt;/code&gt; is legally both. That one line is what buys you the parameter annotation further down.&lt;/p&gt;

&lt;p&gt;Those two lines produce three representations of the concept, all derived from the one you typed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awaitingPayment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'awaitingPayment'   — code: the name you reach for&lt;/span&gt;
&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awaitingPayment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'AWAITING_PAYMENT'  — persistence: the column, the wire&lt;/span&gt;
&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awaitingPayment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Awaiting payment'  — human: the label, the dropdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both casing conventions are defaults, and both are overrideable for when a column or a label has to be something else.&lt;/p&gt;

&lt;p&gt;Those three are exactly what got scattered across five files. The const object and the union hold the persistence form. The switch inlines it again. The migration writes it a third time, in SQL. The &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; holds the human form, typed out by hand. And the code form — the name you actually reach for — is whatever each file happened to call it.&lt;/p&gt;

&lt;p&gt;Here's the part that should bother you: the three aren't independent. &lt;code&gt;awaitingPayment&lt;/code&gt; → &lt;code&gt;AWAITING_PAYMENT&lt;/code&gt; → &lt;code&gt;Awaiting payment&lt;/code&gt; is a mechanical transformation. You could write it as a function in about four lines.&lt;/p&gt;

&lt;p&gt;Nobody does. Instead, every developer who needs one of the forms types it out themselves, in a different file, from memory. The convention lives in everyone's head and in no code — so as far as the toolchain is concerned, the &lt;code&gt;AWAITING_PAYMENT&lt;/code&gt; in the migration and the &lt;code&gt;AWAITING_PAYMENT&lt;/code&gt; in the union are two unrelated string literals that happen to look alike. Nothing compares them.&lt;/p&gt;

&lt;p&gt;That's &lt;code&gt;'archived'&lt;/code&gt; versus &lt;code&gt;'ARCHIVED'&lt;/code&gt; from the top of this post. Not a hard problem solved wrong. A trivial problem solved by hand, twice, by two people who never compared notes.&lt;/p&gt;

&lt;p&gt;Here they're one member. You can't have one representation without the others, because there's nowhere else to put them.&lt;/p&gt;

&lt;p&gt;Everything the const-object trick left on the floor is now on the enum itself. Same list, same order:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; with hardcoded options and hand-derived labels becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// [{ value: 'AWAITING_PAYMENT', label: 'Awaiting payment' }, ...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cast at the database boundary is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cast is a promise. This is a check — it parses, and it rejects anything that isn't a member.&lt;/p&gt;

&lt;p&gt;And the exhaustiveness incantation — the &lt;code&gt;never&lt;/code&gt; assignment and the throw you have to remember to write — becomes a method on the member:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;banner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Waiting to be processed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;awaitingPayment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payment not received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;In progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;suspended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;On hold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arms are keyed by the code representation — &lt;code&gt;awaitingPayment&lt;/code&gt;, the name you reach for — not the wire value. Add &lt;code&gt;archived&lt;/code&gt; to the enum and every &lt;code&gt;match&lt;/code&gt; that doesn't handle it turns red until you add an arm. That's the same guarantee the &lt;code&gt;never&lt;/code&gt; incantation gave you, except you don't write it, you can't forget it, and it returns a value instead of assigning into a mutable &lt;code&gt;let&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;CHECK&lt;/code&gt; constraint that disagreed in a different case: there is exactly one &lt;code&gt;.value&lt;/code&gt;, produced by the enum. The migration and the filter can't disagree, because neither of them is holding its own copy of the string.&lt;/p&gt;

&lt;p&gt;Because the type and the value share a name, you use it the way you'd use a real enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's four satellites collapsed. But the thing that actually mattered is duller than any of it: this is now data with a shape, so it has somewhere to live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;enums&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file. Named after the concept. The next person who needs an order status finds it in one search, in one shape, with the labels already attached — and never writes &lt;code&gt;OrderState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And adding &lt;code&gt;ARCHIVED&lt;/code&gt;? You add &lt;code&gt;'archived'&lt;/code&gt; to the array. That's the change. No labels file to update, no &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; to audit, no migration to keep in sync, no cast to re-verify. The edit that used to take five files and a prayer takes one file and a typecheck.&lt;/p&gt;

&lt;p&gt;None of this is novel. The casing convention was always mechanical, the labels were always derivable, and the concept always wanted one home. All that changed is that a thing you were doing by hand in five files is now done once, by code, where nothing can disagree with it.&lt;/p&gt;

&lt;p&gt;The library does more — a member that goes out over the wire comes back as the same member. That's the next post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wrote it
&lt;/h2&gt;

&lt;p&gt;The pattern isn't mine. "Smart enum" / "rich enum" has been written about for years, mostly by people working in C# and Java. I was introduced to the concept way back in the day by &lt;a href="https://jeremydmiller.com/about/" rel="noopener noreferrer"&gt;Jeremy Miller&lt;/a&gt; and the &lt;a href="https://lostechies.com/" rel="noopener noreferrer"&gt;Los Techies&lt;/a&gt; crew — that's where I learned that an enum could carry behavior as well as data.&lt;/p&gt;

&lt;p&gt;The JS and TypeScript writeups I've found are good at explaining &lt;em&gt;why&lt;/em&gt; &lt;code&gt;enum&lt;/code&gt; is a bad fit and what you'd want instead. Then they either stop at the const-object trick, which is where the drift starts, or they show a quick roll-your-own version that whets your appetite without getting you there. The real implementation, especially with all the TypeScript goodness, is a good deal harder to write. But once you've got it, it's still lightweight and easy to use.&lt;/p&gt;

&lt;p&gt;My first implementation was in JS, long ago, and it was simple to write and easy to use. When TypeScript came along and the code had to get serious about what it was doing — that was a lift. I learned an insane amount about TypeScript while pulling my eyes out. I've been running it in production in a real application for months, which makes me the library's author and its most annoyed user. Every capability in it exists because the app needed it and the absence hurt. Dogfooding made it very clear what was necessary, what was silliness, and where there be dragons. Those stories are worth telling on their own, and I'll get to them.&lt;/p&gt;

&lt;p&gt;But this is the flat version: one canonical, discoverable, behavior-carrying declaration per concept, instead of five representations and four satellites.&lt;/p&gt;

&lt;p&gt;Full documentation: &lt;a href="https://reharik.github.io/smart-enums/" rel="noopener noreferrer"&gt;smart-enum docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @reharik/smart-enum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Next in this series: what happens when a smart enum crosses a serialization boundary and &lt;code&gt;===&lt;/code&gt; quietly starts returning &lt;code&gt;false&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
