<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Thomas Seeley - typescript</title><id>https://www.tseeley.com/tags/typescript/atom.xml</id><updated>2025-04-01T00:00:00+00:00</updated><generator>tseeley.com</generator><link href="https://www.tseeley.com/tags/typescript/atom.xml" rel="self" type="application/atom+xml"/><link href="https://www.tseeley.com" rel="alternate" type="text/html"/><subtitle>Posts about software and making things.</subtitle><entry><title>Inferring types from usage</title><id>https://www.tseeley.com/posts/inferring-state-types/</id><updated>2025-04-01T00:00:00+00:00</updated><author><name>Thomas Seeley</name></author><link href="https://www.tseeley.com/posts/inferring-state-types/" rel="alternate" type="text/html"/><published>2025-04-01T00:00:00+00:00</published><content type="html">
&lt;p&gt;Membrane programs have a &lt;a href=&quot;https://docs.membrane.io/guides/state&quot;&gt;persistent &lt;code&gt;state&lt;/code&gt; object&lt;/a&gt; that survives across deploys. You import it and use it like a regular JavaScript object:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;import { &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;} from &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;membrane&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;??= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;function increment&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;() {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return ++&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire JS heap is durable, but &lt;code&gt;state&lt;/code&gt; is the conventional way to pass data between code versions. The question is: what type is &lt;code&gt;state&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;We could make developers define a &lt;code&gt;State&lt;/code&gt; type for every program. But that’s boilerplate, and for quick scripts and prototypes it slows you down. We already had a TypeScript language service plugin for Membrane’s graph types, so I added state type inference to it.&lt;/p&gt;
&lt;section id=&quot;idea&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#idea&quot;&gt;The Idea&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you write &lt;code&gt;state.count ??= 0&lt;/code&gt;, we know &lt;code&gt;state.count&lt;/code&gt; is a &lt;code&gt;number&lt;/code&gt;. If you write &lt;code&gt;state.apiKey = args.apiKey&lt;/code&gt; and &lt;code&gt;args.apiKey&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;, we know &lt;code&gt;state.apiKey&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;. Walk the abstract syntax tree&lt;a id=&quot;fnref1&quot; href=&quot;#fn1&quot; role=&quot;doc-noteref&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;, find every assignment to a &lt;code&gt;state&lt;/code&gt; property, extract the type from the right-hand side, and generate a type declaration.&lt;/p&gt;
&lt;p&gt;The generated type looks like this:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;type State &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  count&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;number;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  apiKey&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;string;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;key: string&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;]: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;any;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gets injected into the generated &lt;code&gt;membrane.d.ts&lt;/code&gt; file. You get autocomplete and type checking on &lt;code&gt;state&lt;/code&gt; without writing any type annotations. If you do want to define the type explicitly, export a &lt;code&gt;State&lt;/code&gt; type or interface from &lt;code&gt;index.ts&lt;/code&gt; and the inference backs off.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;tricky&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#tricky&quot;&gt;The Tricky Parts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The idea is simple, but as always implementation had more edge cases than expected.&lt;/p&gt;
&lt;section id=&quot;Assignment-Patterns&quot;&gt;
&lt;h3&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Assignment-Patterns&quot;&gt;Assignment Patterns&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Turns out people assign to &lt;code&gt;state&lt;/code&gt; in a lot of different ways:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// direct assignment
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;??= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// nullish coalescing assignment
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;||= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// logical OR assignment
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;+= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// compound assignment
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;value &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;as &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// type assertion
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The AST visitor needs to catch all of these. Direct assignments and compound assignments (&lt;code&gt;+=&lt;/code&gt;, &lt;code&gt;-=&lt;/code&gt;, etc.) fall in a range of operator token kinds between &lt;code&gt;FirstAssignment&lt;/code&gt; and &lt;code&gt;LastAssignment&lt;/code&gt;, which makes them easy to handle together. Nullish coalescing assignment (&lt;code&gt;??=&lt;/code&gt;) is its own token kind and needs a separate check:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isBinaryExpression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) &amp;amp;&amp;amp;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;operatorToken&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;kind &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;SyntaxKind&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;FirstAssignment &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;amp;&amp;amp;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;operatorToken&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;kind &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;SyntaxKind&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;LastAssignment
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  processStateAssignment&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;right&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;} else if (
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isBinaryExpression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) &amp;amp;&amp;amp;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;operatorToken&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;kind &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;=== &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;SyntaxKind&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;QuestionQuestionEqualsToken
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  processStateAssignment&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;right&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section id=&quot;Nullish-Coalescing-and-any&quot;&gt;
&lt;h3&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Nullish-Coalescing-and-any&quot;&gt;Nullish Coalescing and &lt;code&gt;any&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;state.count ??= 0&lt;/code&gt; is interesting because the right-hand side is a binary expression with &lt;code&gt;??&lt;/code&gt;. The left side is &lt;code&gt;state.count&lt;/code&gt;, which might be &lt;code&gt;any&lt;/code&gt; if we haven’t inferred a type for it yet. The right side is &lt;code&gt;0&lt;/code&gt;, which is &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we naively get the type of the whole expression, TypeScript might give us &lt;code&gt;any&lt;/code&gt; because one side is &lt;code&gt;any&lt;/code&gt;. So we check both sides individually:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;operatorToken&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;kind &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;=== &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;SyntaxKind&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;QuestionQuestionToken &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;||
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;operatorToken&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;kind &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;=== &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;SyntaxKind&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;BarBarToken
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;leftType &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;checker&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getTypeAtLocation&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;rightType &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;checker&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getTypeAtLocation&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;right&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isLeftAny &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;leftType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;() &amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;TypeFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) !== &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isRightAny &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;rightType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;() &amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;TypeFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Any&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) !== &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isLeftAny &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;amp;&amp;amp; !&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isRightAny&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;rightType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (!&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isLeftAny &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isRightAny&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;leftType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;checker&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getTypeAtLocation&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If one side is &lt;code&gt;any&lt;/code&gt; and the other is concrete, use the concrete type. If both are concrete, use the union. This is what makes &lt;code&gt;state.count ??= 0&lt;/code&gt; correctly infer &lt;code&gt;number&lt;/code&gt; instead of &lt;code&gt;any&lt;/code&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Explicit-vs-Inferred-Priority&quot;&gt;
&lt;h3&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Explicit-vs-Inferred-Priority&quot;&gt;Explicit vs Inferred Priority&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Consider this:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;??= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// inferred: number
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;count &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getCount&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// explicit: number (from getCount&amp;#39;s return type)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both assignments tell us &lt;code&gt;state.count&lt;/code&gt; is a &lt;code&gt;number&lt;/code&gt;, but they have different confidence levels. A direct assignment (&lt;code&gt;=&lt;/code&gt;) is explicit. A nullish coalescing assignment (&lt;code&gt;??=&lt;/code&gt;) or logical OR assignment (&lt;code&gt;||=&lt;/code&gt;) is inferred, because they’re fallback patterns that might not represent the primary type.&lt;/p&gt;
&lt;p&gt;When we see a new assignment to a property we’ve already typed, explicit assignments override inferred ones. More specific types override &lt;code&gt;any&lt;/code&gt;. This means the order of assignments in your code doesn’t matter as much as the kind of assignment.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Type-Assertions&quot;&gt;
&lt;h3&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Type-Assertions&quot;&gt;Type Assertions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you write &lt;code&gt;state.data = response as ApiResponse&lt;/code&gt;, the type should be &lt;code&gt;ApiResponse&lt;/code&gt;, not whatever TypeScript infers from &lt;code&gt;response&lt;/code&gt;. The plugin checks for &lt;code&gt;as&lt;/code&gt; expressions and type assertion expressions, unwrapping parenthesized expressions along the way:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;function &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;getAssertedType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(node&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;Expression)&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: Type &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;| &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;undefined &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isAsExpression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) || &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isTypeAssertionExpression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;)) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;checker&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getTypeAtLocation&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;isParenthesizedExpression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;)) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getAssertedType&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;node&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;expression&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// ...
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id=&quot;assembly&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#assembly&quot;&gt;How It Fits Together&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The inference hooks into &lt;code&gt;getSemanticDiagnostics&lt;/code&gt;. Every time the editor updates diagnostics, the plugin walks the AST, collects &lt;code&gt;state&lt;/code&gt; assignments, and generates the &lt;code&gt;State&lt;/code&gt; type string. If it changed since last time, &lt;code&gt;membrane.d.ts&lt;/code&gt; gets regenerated.&lt;/p&gt;
&lt;p&gt;If a developer exports their own &lt;code&gt;State&lt;/code&gt; type:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;interface State &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  count&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  apiKey&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plugin detects that and imports it instead of generating one. Explicit always wins.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;general&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#general&quot;&gt;The General Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The technique here isn’t specific to state. It’s a general approach: infer the type of an untyped object by walking all the places it’s assigned to across a codebase. You could apply the same pattern to config objects, environment variables, feature flags, or any global singleton that accumulates properties over time.&lt;/p&gt;
&lt;p&gt;TypeScript itself has &lt;a href=&quot;https://github.com/microsoft/TypeScript/issues/34738&quot;&gt;open issues&lt;/a&gt; requesting “infer type from usage” for implicit &lt;code&gt;any&lt;/code&gt; parameters, but it’s scoped to a single function body, not whole-program analysis. &lt;a href=&quot;https://flow.org/&quot;&gt;Flow&lt;/a&gt; does whole-program type inference, but that’s an entire type system. &lt;a href=&quot;https://github.com/JoshuaKGoldberg/TypeStat&quot;&gt;TypeStat&lt;/a&gt; infers types from usage patterns and writes annotations back into source files, but it targets individual variables and parameters, not a shared object.&lt;/p&gt;
&lt;p&gt;The architecture closest to ours is &lt;a href=&quot;https://github.com/vuejs/language-tools&quot;&gt;Volar&lt;/a&gt; (Vue Language Tools), which generates virtual TypeScript files from &lt;code&gt;.vue&lt;/code&gt; single-file components. The pattern is the same: take a non-standard source of type information, generate a &lt;code&gt;.d.ts&lt;/code&gt; that the TypeScript language service can understand, and keep it updated as the source changes.&lt;/p&gt;
&lt;p&gt;We’re doing a narrower version of that. Walk assignments to one object, build a type from what we find, inject it into a generated declaration file.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;takeaways&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#takeaways&quot;&gt;What I Learned&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Working on this gave me a better feel for &lt;a href=&quot;https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API&quot;&gt;TypeScript’s compiler API&lt;/a&gt; than anything else I’ve done. The &lt;code&gt;TypeChecker&lt;/code&gt; is powerful but not always intuitive. &lt;code&gt;getTypeAtLocation&lt;/code&gt; can return &lt;code&gt;any&lt;/code&gt; in cases where you’d expect something more specific, especially when the thing you’re checking is a property of an untyped object (which &lt;code&gt;state&lt;/code&gt; is, until we infer its type). The &lt;code&gt;getBaseTypeOfLiteralType&lt;/code&gt; call is important too. Without it, &lt;code&gt;state.count = 0&lt;/code&gt; would infer the literal type &lt;code&gt;0&lt;/code&gt; instead of &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The biggest takeaway: type inference is a game of priorities! You have multiple sources of type information (direct assignments, fallback patterns, type assertions, compound assignments) and you need rules for which one wins. Getting those priority rules right is what makes the experience feel natural instead of surprising.&lt;/p&gt;
&lt;/section&gt;
&lt;section role=&quot;doc-endnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree&lt;/a&gt;, a tree representation of source code structure.&lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt; &lt;code&gt;membrane&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;larr; &lt;a href=&quot;https://www.tseeley.com/posts/onboarding/&quot;&gt;How To Onboard&lt;/a&gt; · &lt;a href=&quot;https://www.tseeley.com/posts/hello-membrane/&quot;&gt;Hello, Membrane&lt;/a&gt; &amp;rarr;&lt;/p&gt;
</content></entry><entry><title>Maze Playground</title><id>https://www.tseeley.com/posts/maze-playground/</id><updated>2024-08-22T00:00:00+00:00</updated><author><name>Thomas Seeley</name></author><link href="https://www.tseeley.com/posts/maze-playground/" rel="alternate" type="text/html"/><published>2024-08-22T00:00:00+00:00</published><content type="html">
&lt;p&gt;I read Andrew Healey’s &lt;a href=&quot;https://healeycodes.com/generating-mazes&quot;&gt;Generating Mazes&lt;/a&gt; and wanted to see the algorithms work. Not just the finished maze, but each step of the generation. Walls getting removed one at a time, the path carving through the grid.&lt;/p&gt;
&lt;p&gt;So I built a &lt;a href=&quot;https://github.com/iamseeley/maze-playground&quot;&gt;small playground&lt;/a&gt; for it. Deno, TypeScript, and a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;section id=&quot;The-Data-Model&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#The-Data-Model&quot;&gt;The Data Model&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A maze is a grid of cells. Each cell has four walls and knows whether it’s been visited.&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;class Cell implements CellInterface &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;{
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  position&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  hasTopWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  hasRightWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  hasBottomWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  hasLeftWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  visited&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  removeWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;neighbor: CellInterface&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;void {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    const dx = neighbor.position.x - this.position.x;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    const dy = neighbor.position.y - this.position.y;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;if &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(dx === &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;this.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;hasRightWall &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      neighbor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;hasLeftWall &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// ... and so on for each direction
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;removeWall&lt;/code&gt; figures out which direction the neighbor is in and removes the wall on both sides. That’s the core operation of maze generation: two cells become connected.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Generators-for-Stepping&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Generators-for-Stepping&quot;&gt;Generators for Stepping&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The part I liked most was using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_generators&quot;&gt;JavaScript generators&lt;/a&gt; for the algorithms. DFS yields after each step, which means the UI can consume it at whatever pace it wants.&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#888888;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;generateSteps&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;maze: MazeInterface&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;startCell: CellInterface&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: Generator&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;lt;{ &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;currentCell&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;CellInterface &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}&amp;gt; {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  const stack&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;CellInterface&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[] = [&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;startCell&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  startCell.visited = true;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;while &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(stack&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt; &amp;gt; 0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;currentCell &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;stack&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;length &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;];
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;yield { &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;currentCell &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;};
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;unvisitedNeighbors &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;maze&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getNeighbors&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;currentCell&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;)
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(neighbor =&amp;gt; !&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;neighbor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;visited&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;if (&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;unvisitedNeighbors&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;length &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;randomIndex &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;() * &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;unvisitedNeighbors&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;chosenNeighbor &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;unvisitedNeighbors&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;randomIndex&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;];
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      currentCell&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;removeWall&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;chosenNeighbor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      chosenNeighbor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;visited &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;;
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      stack&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;chosenNeighbor&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;);
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;} else {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      stack&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;pop&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;();
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The UI runs a &lt;code&gt;setInterval&lt;/code&gt; at 50ms that calls &lt;code&gt;generator.next()&lt;/code&gt;, snapshots the maze state, and renders it. After generation is done, a slider lets you scrub back through every step. You can watch the algorithm think.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Whats-There-Whats-Not&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Whats-There-Whats-Not&quot;&gt;What’s There, What’s Not&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Right now only &lt;a href=&quot;https://en.wikipedia.org/wiki/Depth-first_search&quot;&gt;DFS&lt;/a&gt; is implemented. The dropdown lists &lt;a href=&quot;https://en.wikipedia.org/wiki/Maze_generation_algorithm#Aldous-Broder_algorithm&quot;&gt;Aldous-Broder&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Prim&apos;s_algorithm&quot;&gt;Prim’s&lt;/a&gt;, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Kruskal&apos;s_algorithm&quot;&gt;Kruskal’s&lt;/a&gt; but they’re stubs. I’d like to add them eventually because the interesting thing about maze algorithms is how different the mazes &lt;em&gt;feel&lt;/em&gt;. DFS creates long winding corridors with few branches. Aldous-Broder creates more uniform, random-looking mazes. Same grid, different character.&lt;/p&gt;
&lt;p&gt;The rendering is simple Canvas drawing. Visited cells get a gray fill, the current cell gets a blue highlight, and walls are just lines. Nothing fancy, but it’s enough to see what’s happening.&lt;/p&gt;
&lt;/section&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt; &lt;code&gt;algorithms&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tseeley.com/about/&quot;&gt;Thomas Seeley&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/tseeley.com&quot;&gt;@tseeley.com on Bluesky&lt;/a&gt;, &lt;a href=&quot;https://mastodon.social/@iamseeley&quot;&gt;@iamseeley on Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;larr; &lt;a href=&quot;https://www.tseeley.com/posts/nvim/&quot;&gt;Neovim Cheatsheet&lt;/a&gt; · &lt;a href=&quot;https://www.tseeley.com/posts/schemeing/&quot;&gt;Scheme-ing&lt;/a&gt; &amp;rarr;&lt;/p&gt;
</content></entry></feed>