<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Thomas Seeley - membrane</title><id>https://www.tseeley.com/tags/membrane/atom.xml</id><updated>2025-04-01T00:00:00+00:00</updated><generator>tseeley.com</generator><link href="https://www.tseeley.com/tags/membrane/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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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&gt;&lt;code data-lang=&quot;typescript&quot;&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>Hello, Membrane</title><id>https://www.tseeley.com/posts/hello-membrane/</id><updated>2024-11-10T00:00:00+00:00</updated><author><name>Thomas Seeley</name></author><link href="https://www.tseeley.com/posts/hello-membrane/" rel="alternate" type="text/html"/><published>2024-11-10T00:00:00+00:00</published><content type="html">
&lt;p&gt;It’s been a busy, exciting past few months. I joined &lt;a href=&quot;http://membrane.io/&quot;&gt;membrane.io&lt;/a&gt; as an intern in September, and I’m having a great time making improvements to the platform and building things in user space.&lt;/p&gt;
&lt;section id=&quot;Whats-Membrane&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Whats-Membrane&quot;&gt;What’s Membrane?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Membrane is a serverless platform for developing JavaScript/TypeScript programs.&lt;/p&gt;
&lt;p&gt;Every program you create in your Membrane workspace becomes part of your graph, and the programs can connect to each other through this graph.&lt;/p&gt;
&lt;video controls width=&quot;100%&quot; src=&quot;https://docs.membrane.io/videos/add-connections.mp4#t=0.1&quot;&gt;&lt;/video&gt;
&lt;p&gt;There are a few key things that set Membrane apart from other serverless platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
Programs have built-in state - no need to set up a database
&lt;/li&gt;
&lt;li&gt;
Everything gets logged before it happens - if it’s not in the logs, it didn’t happen
&lt;/li&gt;
&lt;li&gt;
Each program gets an email address - handle incoming emails by exporting an &lt;code&gt;email&lt;/code&gt; function
&lt;/li&gt;
&lt;li&gt;
Each program gets its own endpoint - handle HTTP requests by exporting an &lt;code&gt;endpoint&lt;/code&gt; function
&lt;/li&gt;
&lt;li&gt;
Programs can use other programs as building blocks through graph connections
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id=&quot;What-Ive-Been-Working-On&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#What-Ive-Been-Working-On&quot;&gt;What I’ve Been Working On.&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most of my time has been spent on Membrane’s API drivers. Drivers let you interact with external APIs through Membrane’s graph.&lt;/p&gt;
&lt;p&gt;I’ve been improving existing drivers like &lt;a href=&quot;https://www.membrane.io/share/membrane/discord&quot;&gt;Discord&lt;/a&gt; and &lt;a href=&quot;https://www.membrane.io/share/membrane/anthropic&quot;&gt;Anthropic&lt;/a&gt; and building a &lt;a href=&quot;https://membrane.io/share/iamseeley/driver-generator&quot;&gt;tool&lt;/a&gt; to generate new ones. The more drivers on Membrane, the more useful tools and automations we can build!&lt;/p&gt;
&lt;p&gt;A driver exposes API endpoints as nodes (fields, actions, events) that you can reference in your graph with a simple syntax.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;github:users.one(name:&quot;membrane-io&quot;)&lt;/code&gt; tells Membrane to get a specific GitHub user - but here’s what’s cool: even though that API call returns all of the user’s data, you can selectively reference just the pieces you need in other programs, like if we want to get their avatar: &lt;code&gt;github:users.one(name:&quot;membrane-io&quot;).avatar_url&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;strong&gt;Other Projects and Improvements:&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
Implemented type inference for Membrane &lt;code&gt;state&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Dynamic OG images for the changelog, packages, and docs
&lt;/li&gt;
&lt;li&gt;
Made a driver for the Membrane API: &lt;a href=&quot;https://www.membrane.io/share/iamseeley/mem&quot;&gt;mem&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m excited to start building more drivers and example tools/automations :).&lt;/p&gt;
&lt;p&gt;If you’d like to learn more about Membrane check these out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href=&quot;https://notes.just-be.dev/Membrane%2C+A+Living+Computational+Environment&quot;&gt;Justin Bennett: Membrane, A Living Computational Environment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.devtools.fm/episode/46&quot;&gt;Juan Campa - devtoolsFM episode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://www.membrane.io/blog/changelog-0.9&quot;&gt;Membrane Changelog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href=&quot;https://docs.membrane.io/getting-started/intro/&quot;&gt;Membrane Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And join the &lt;a href=&quot;https://discord.gg/gBK9xP3z&quot;&gt;Discord&lt;/a&gt;!&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Want-to-Help-Shape-Membrane&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Want-to-Help-Shape-Membrane&quot;&gt;Want to Help Shape Membrane?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’re looking for developers to try out Membrane and share their feedback!&lt;/p&gt;
&lt;p&gt;If you’re interested in participating in user interviews, reach out to me at &lt;a href=&quot;mailto:thomas@membrane.io&quot;&gt;thomas@membrane.io&lt;/a&gt;. We’d love to hear your thoughts and help you get started building tools for yourself or for your company.&lt;/p&gt;
&lt;aside&gt;Be on the lookout for some updates coming to Membrane...soon you&apos;ll be able to create UIs with the programs on your graph!&lt;/aside&gt;
&lt;/section&gt;

&lt;hr&gt;
&lt;p&gt;&lt;code&gt;membrane&lt;/code&gt; &lt;code&gt;javascript&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/inferring-state-types/&quot;&gt;Inferring types from usage&lt;/a&gt; · &lt;a href=&quot;https://www.tseeley.com/posts/maze-playground/&quot;&gt;Maze Playground&lt;/a&gt; &amp;rarr;&lt;/p&gt;
</content></entry></feed>