<?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 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/we-forked-typescript/&quot;&gt;We forked TypeScript&lt;/a&gt; &amp;rarr;&lt;/p&gt;
</content></entry><entry><title>We forked TypeScript</title><id>https://www.tseeley.com/posts/we-forked-typescript/</id><updated>2025-03-10T00:00:00+00:00</updated><author><name>Thomas Seeley</name></author><link href="https://www.tseeley.com/posts/we-forked-typescript/" rel="alternate" type="text/html"/><published>2025-03-10T00:00:00+00:00</published><content type="html">
&lt;p&gt;At &lt;a href=&quot;https://membrane.io&quot;&gt;Membrane&lt;/a&gt;, we forked TypeScript to extend contextual typing so our programming model gets type inference for free.&lt;/p&gt;
&lt;p&gt;The changes were made by &lt;a href=&quot;https://x.com/juancampa&quot;&gt;Juan Campa&lt;/a&gt;. I went through them recently while updating our fork to the latest TypeScript version. What I found was surprisingly small and worth explaining.&lt;/p&gt;
&lt;section id=&quot;What-Is-Contextual-Typing&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#What-Is-Contextual-Typing&quot;&gt;What Is Contextual Typing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most type inference works bottom-up. You write &lt;code&gt;let x = 42&lt;/code&gt; and the compiler infers &lt;code&gt;number&lt;/code&gt; from the value. &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing&quot;&gt;Contextual typing&lt;/a&gt; works top-down. The compiler looks at where an expression appears and infers types from the surrounding context.&lt;/p&gt;
&lt;p&gt;You’ve seen this in TypeScript even if you didn’t know the name:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// TypeScript knows `e` is MouseEvent from addEventListener&amp;#39;s signature
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;window.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;click&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, (e) =&amp;gt; {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;clientX&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;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// TypeScript knows `x` is a string from the array type
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;].&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;((x) =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;toUpperCase&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;());
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The type flows down from the context into the expression. You don’t annotate the callback parameters because the compiler already knows what they should be.&lt;/p&gt;
&lt;p&gt;This isn’t unique to TypeScript. &lt;a href=&quot;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/&quot;&gt;Swift&lt;/a&gt; does it for closures. &lt;a href=&quot;https://kotlinlang.org/docs/lambdas.html&quot;&gt;Kotlin&lt;/a&gt; infers lambda parameter types from the expected function type. &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/new-operator#target-typed-new&quot;&gt;C#&lt;/a&gt; has target-typed &lt;code&gt;new&lt;/code&gt; expressions and lambda inference. Scala has its own flavor called “colored local type inference.” The theoretical foundation is &lt;a href=&quot;https://arxiv.org/abs/1908.05839&quot;&gt;bidirectional typing&lt;/a&gt;, which combines bottom-up synthesis with top-down checking.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Where-TypeScripts-Contextual-Typing-Stops&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Where-TypeScripts-Contextual-Typing-Stops&quot;&gt;Where TypeScript’s Contextual Typing Stops&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Contextual typing in TypeScript works for callbacks, assignment right-hand sides, object literal members, array elements, and return statements. It does not work for top-level exported function declarations or variable declarations against an external type.&lt;/p&gt;
&lt;p&gt;This means if you have a type somewhere that describes the shape of a function, and you write a top-level &lt;code&gt;export function&lt;/code&gt; that should match it, TypeScript won’t infer the parameter types. You have to annotate them manually.&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// These types exist somewhere
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;type ConfigureArgs &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= { &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Schema &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;};
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;type &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;Configure &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= (args&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;ConfigureArgs) =&amp;gt; void;
&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;// TypeScript does NOT infer `args` here
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;function configure&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;args&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;// args is implicitly `any`
&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;// You have to do this instead
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;function configure&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;args: ConfigureArgs&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;// now args is typed
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For most codebases this is fine. For Membrane, it’s the core of the developer experience.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Membranes-Programming-Model&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Membranes-Programming-Model&quot;&gt;Membrane’s Programming Model&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Membrane programs are durable TypeScript programs that expose their data and functionality through a &lt;a href=&quot;https://docs.membrane.io/concepts/the-graph&quot;&gt;type-safe graph&lt;/a&gt;. Each program has a schema (defined in &lt;code&gt;memconfig.json&lt;/code&gt;) that declares its node types with fields, actions, and events. Think of it like a GraphQL schema for your program’s API.&lt;/p&gt;
&lt;p&gt;Every program has an &lt;code&gt;index.ts&lt;/code&gt; that exports &lt;strong&gt;&lt;strong&gt;resolvers&lt;/strong&gt;&lt;/strong&gt;: functions and objects that implement the schema. When someone queries a node in the graph, Membrane invokes the corresponding resolver to produce the value.&lt;/p&gt;
&lt;p&gt;A typical &lt;code&gt;index.ts&lt;/code&gt; looks like this:&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;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;nodes&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;root &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:#888888;&quot;&gt;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Root &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-weight:bold;color:#eeeeee;&quot;&gt;status&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 &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;ready&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:#888888;&quot;&gt;},
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  configure&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-weight:bold;color:#eeeeee;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: () =&amp;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;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;EmailCollection &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-weight:bold;color:#eeeeee;&quot;&gt;async one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ id }) {
&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;await api&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, `&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;emails/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&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-weight:bold;color:#eeeeee;&quot;&gt;async page&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;result &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;await api&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;emails&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:#888888;&quot;&gt;return { &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;items&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;data &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;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Email &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-weight:bold;color:#eeeeee;&quot;&gt;gref&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(_, { obj }) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return root.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id &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-weight:bold;color:#eeeeee;&quot;&gt;async cancel&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(_, { self }) {
&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;id &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;} = self.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;$argsAt&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(root.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;one&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;api&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, `&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;emails/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;/cancel&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;Every one of those exported objects and functions needs to match the schema’s type definitions. The types are generated into a &lt;code&gt;membrane.d.ts&lt;/code&gt; file and live in a global &lt;code&gt;resolvers&lt;/code&gt; namespace. Without the fork, every resolver would need explicit type annotations. With dozens of resolvers per program, that’s a lot of boilerplate. And if the schema changes, every annotation needs updating.&lt;/p&gt;
&lt;p&gt;We wanted the types to flow automatically: define the schema once, and the implementations get typed from context.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;The-Fork&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#The-Fork&quot;&gt;The Fork&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The entire fork is about 70 lines of changes in &lt;code&gt;src/compiler/checker.ts&lt;/code&gt;. No parser changes, no new syntax, no emitter modifications. Three commits, all by Juan.&lt;/p&gt;
&lt;p&gt;The changes do three things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;strong&gt;1. Identify Membrane resolvers.&lt;/strong&gt;&lt;/strong&gt; Three helper functions check whether a declaration is an exported, top-level, named function or variable in an &lt;code&gt;index.ts&lt;/code&gt; file:&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;isMembraneFunctionResolver&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;FunctionDeclaration) {
&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;    isInIndexTs&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;    isInTopLevelContext&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;    hasSyntacticModifier&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;font-weight:bold;color:#eeeeee;&quot;&gt;ModifierFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Export&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;name &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;    isIdentifier&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;name&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;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;function &lt;/span&gt;&lt;span style=&quot;font-weight:bold;color:#eeeeee;&quot;&gt;isInIndexTs&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;Node)&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:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;sourceFile &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;getSourceFileOfNode&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;sourceFile &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;amp;&amp;amp; /&lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;\/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;[^&lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;]+&lt;/span&gt;&lt;span style=&quot;color:#e0c020;&quot;&gt;\/index\.ts&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;$/.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;sourceFile&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;fileName&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;strong&gt;&lt;strong&gt;2. Look up the contextual type.&lt;/strong&gt;&lt;/strong&gt; When the checker encounters a qualifying declaration, it looks up the function or variable name in the global &lt;code&gt;resolvers&lt;/code&gt; namespace and uses the matching type:&lt;/p&gt;
&lt;pre data-lang=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span style=&quot;font-style:italic;color:#666666;&quot;&gt;// For variable declarations like: export const Root = { ... }
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ns &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;globals&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;escapeLeadingUnderscores&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;resolvers&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;));
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;const exports = &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ns &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;getExportsOfSymbol&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;ns&lt;/span&gt;&lt;span style=&quot;color:#888888;&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;exportSymbol &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= exports?.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;declaration&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;escapedText&lt;/span&gt;&lt;span style=&quot;color:#888888;&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;typeAlias &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;exportSymbol &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;exportSymbol&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;declarations&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:#888888;&quot;&gt;const &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:#e2e2e5;&quot;&gt;typeAlias &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;getTypeFromTypeNode&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;typeAlias&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&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:#888888;&quot;&gt;return &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;strong&gt;3. Mark exported functions as context-sensitive.&lt;/strong&gt;&lt;/strong&gt; Standard TypeScript only considers function expressions, arrow functions, and object literal methods as context-sensitive. The fork adds exported functions in &lt;code&gt;index.ts&lt;/code&gt; to that set, which is what makes the checker attempt contextual typing for them at all:&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;isContextSensitiveFunctionOrObjectLiteralMethod&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(func) {
&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;isMembraneExport &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;=
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    hasSyntacticModifier&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;func&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;ModifierFlags&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Export&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;isInIndexTs&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;func&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;    isMembraneExport &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;isFunctionExpressionOrArrowFunction&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;func&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;) ||
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;      isObjectLiteralMethod&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;func&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;      isContextSensitiveFunctionLikeDeclaration&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;func&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;That’s it. If there’s no global &lt;code&gt;resolvers&lt;/code&gt; namespace, all the guards return early and standard TypeScript behavior applies. The changes are purely additive.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;What-It-Looks-Like&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#What-It-Looks-Like&quot;&gt;What It Looks Like&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before the fork, a Membrane &lt;code&gt;index.ts&lt;/code&gt; would need explicit annotations everywhere:&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;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;nodes&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;root &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:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;function configure&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;args: ConfigureArgs&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;  state.apiKey = args.apiKey;
&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 const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Root: RootResolvers &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-weight:bold;color:#eeeeee;&quot;&gt;status&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;    return state.apiKey ? &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;ready&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot; : &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;not configured&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;  &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;},
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;  emails: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;() =&amp;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;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;EmailCollection: EmailCollectionResolvers &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-weight:bold;color:#eeeeee;&quot;&gt;async one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ id }&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;{ id&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&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;return &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;await api&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, `&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;emails/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&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;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Email: EmailResolvers &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-weight:bold;color:#eeeeee;&quot;&gt;gref&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(_, { obj }&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;{ obj&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;any }) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return root.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id &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;After the fork:&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;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;nodes&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;root &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:#888888;&quot;&gt;export &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;function configure&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#888888;&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;apiKey &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;= &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;args&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:#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 const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Root &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-weight:bold;color:#eeeeee;&quot;&gt;status&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;apiKey &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;? &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;ready&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot; : &amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;not configured&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:#888888;&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;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: () =&amp;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;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;EmailCollection &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-weight:bold;color:#eeeeee;&quot;&gt;async one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ id }) {
&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;await api&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(&amp;quot;&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&amp;quot;, `&lt;/span&gt;&lt;span style=&quot;font-style:italic;color:#e0c020;&quot;&gt;emails/&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&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;span style=&quot;color:#e2e2e5;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;export const &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;Email &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-weight:bold;color:#eeeeee;&quot;&gt;gref&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;(_, { obj }) {
&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;return root.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#e2e2e5;&quot;&gt;id &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;Same code, no annotations. The types are inferred from the schema. You get autocomplete, error checking, and hover information. If the schema changes (say you add a &lt;code&gt;subject&lt;/code&gt; field to &lt;code&gt;Email&lt;/code&gt;), the resolver types update automatically. No manual syncing.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Why-Fork-Instead-of&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Why-Fork-Instead-of&quot;&gt;Why Fork Instead of…&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The obvious question: why not use &lt;code&gt;satisfies&lt;/code&gt;, explicit annotations, or a code generation step?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;satisfies&lt;/code&gt; works for objects (&lt;code&gt;export const Root = { ... } satisfies RootResolvers&lt;/code&gt;) but not for function declarations. It also requires the developer to remember to add it. We wanted types to flow automatically from the schema with no ceremony.&lt;/p&gt;
&lt;p&gt;Code generation (generating annotated stubs from the schema) adds a build step and creates files the developer has to maintain alongside the schema. We wanted the source of truth to be the schema alone.&lt;/p&gt;
&lt;p&gt;A TypeScript language service plugin could provide autocomplete but can’t add real type checking. We wanted both.&lt;/p&gt;
&lt;p&gt;Forking the compiler was the cleanest path. ~70 lines, scoped to &lt;code&gt;index.ts&lt;/code&gt; files in the presence of a &lt;code&gt;resolvers&lt;/code&gt; namespace, no impact on non-Membrane code. The tradeoff is maintaining the fork across TypeScript releases, which is what I was doing when I decided to write this post.&lt;/p&gt;
&lt;/section&gt;
&lt;section id=&quot;Maintaining-the-Fork&quot;&gt;
&lt;h2&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;#Maintaining-the-Fork&quot;&gt;Maintaining the Fork&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Updating the fork means rebasing our changes onto the latest TypeScript release. Since the changes are small and concentrated in one file, this has been straightforward so far. The checker’s internal API does change between versions, so some adaptation is needed, but the core pattern (look up a name in a namespace, return its type) stays stable.&lt;/p&gt;
&lt;p&gt;The bigger risk is if TypeScript ever adds native support for this kind of module-level contextual typing. That would be great, actually. We’d happily drop the fork. There are &lt;a href=&quot;https://github.com/microsoft/TypeScript/issues/53701&quot;&gt;open issues&lt;/a&gt; requesting more contextual typing for function return values, and the general direction of TypeScript’s inference has been to expand where context flows. But top-level declarations against a global type namespace isn’t on the roadmap as far as I know.&lt;/p&gt;
&lt;p&gt;Until then, ~70 lines in the checker gets us what we need.&lt;/p&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/inferring-state-types/&quot;&gt;Inferring types from usage&lt;/a&gt; · &lt;a href=&quot;https://www.tseeley.com/posts/how-to-use-chrome-devtools/&quot;&gt;How to Use Chrome DevTools&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/how-to-use-chrome-devtools/&quot;&gt;How to Use Chrome DevTools&lt;/a&gt; · &lt;a href=&quot;https://www.tseeley.com/posts/nvim/&quot;&gt;Neovim Cheatsheet&lt;/a&gt; &amp;rarr;&lt;/p&gt;
</content></entry></feed>