<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>tcm blog</title>
<link>https://tcm.codes/blog/</link>
<atom:link href="https://tcm.codes/blog/rss.xml" rel="self" type="application/rss+xml"/>
<description>Writing about tcm: Unison's content-addressed codebase model, for plain TypeScript.</description>
<language>en</language>
<lastBuildDate>Sun, 08 Nov 2026 00:00:00 GMT</lastBuildDate>
<item>
<title>The Client/Server Boundary</title>
<link>https://tcm.codes/blog/client-server-boundary.html</link>
<guid isPermaLink="true">https://tcm.codes/blog/client-server-boundary.html</guid>
<pubDate>Sun, 08 Nov 2026 00:00:00 GMT</pubDate>
<dc:creator>Andrew Lisowski</dc:creator>
<description>&lt;p&gt;Every full-stack app has a line running through it: some code runs on the server, some runs in the browser, and a
network sits between them.
Most tools make you draw that line yourself.&lt;/p&gt;
&lt;p&gt;You might split your code into a &lt;code&gt;server/&lt;/code&gt; and a &lt;code&gt;client/&lt;/code&gt; package.
You might write &lt;code&gt;&amp;quot;use server&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;use client&amp;quot;&lt;/code&gt; at the top of files.
You might write an API route, then a &lt;code&gt;fetch&lt;/code&gt; to call it, then types to keep the two in step.&lt;/p&gt;
&lt;p&gt;All of that is bookkeeping, and it goes wrong in familiar ways.
A secret ends up in the browser bundle.
A file that imports &lt;code&gt;node:fs&lt;/code&gt; gets pulled into the client, and the build blows up.
A deploy goes out, and a tab that was already open calls an endpoint that changed shape under it.&lt;/p&gt;
&lt;p&gt;In tcm, you don&amp;#39;t draw the line. tcm works it out from your code.&lt;/p&gt;
&lt;h2 id=&quot;writing-it&quot;&gt;Writing it&lt;/h2&gt;
&lt;p&gt;Here&amp;#39;s a whole app:&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { readFileSync } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &quot;node:fs&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; hostCount&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; readFileSync&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;/etc/hosts&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; render&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `hosts: ${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  document.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;#app&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.textContent &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; render&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; hostCount&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&amp;#39;s no annotation anywhere.
&lt;code&gt;hostCount&lt;/code&gt; reads a file, so it runs on the server.
&lt;code&gt;main&lt;/code&gt; touches &lt;code&gt;document&lt;/code&gt;, so it runs in the browser.
&lt;code&gt;render&lt;/code&gt; uses neither, so it runs wherever it&amp;#39;s called.
When &lt;code&gt;main&lt;/code&gt; calls &lt;code&gt;hostCount&lt;/code&gt;, that call goes over the network.&lt;/p&gt;
&lt;p&gt;The rules you write by are short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use Node, and your code runs on the server.&lt;/strong&gt; That&amp;#39;s &lt;code&gt;node:*&lt;/code&gt; modules, &lt;code&gt;process&lt;/code&gt;, &lt;code&gt;Buffer&lt;/code&gt;, an npm package that
needs Node, or tcm&amp;#39;s server library (tables, sessions, cookies).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the DOM, and it runs in the browser.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use neither, and it runs anywhere.&lt;/strong&gt; It ships to whichever side calls it, or both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send plain data.&lt;/strong&gt; JSON, plus &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt; and &lt;code&gt;BigInt&lt;/code&gt;. A function or class instance is refused with a
message saying what couldn&amp;#39;t be sent, rather than quietly turning into &lt;code&gt;{}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you&amp;#39;re ever unsure, &lt;code&gt;tcm view&lt;/code&gt; tells you where a definition runs and why: &amp;quot;runs on the server: it uses node:fs&amp;quot;.&lt;/p&gt;
&lt;h2 id=&quot;why-this-is-hard-with-files&quot;&gt;Why this is hard with files&lt;/h2&gt;
&lt;p&gt;A file is a bag of things.
Import one function from it, and you pull in everything the file imports, too.
So bundlers have to decide where code runs one file at a time.
If a file mixes a server helper with something the browser needs, the tools can&amp;#39;t split it.
That&amp;#39;s why you end up annotating files and sorting them into folders: you&amp;#39;re doing the analysis for them.&lt;/p&gt;
&lt;p&gt;In tcm, there are no files to split.
Your code lives in a database (a codebase), and each definition is its own row: one function, one constant, one type.
Each row records exactly what that definition uses: other definitions (by hash), globals like &lt;code&gt;document&lt;/code&gt; or &lt;code&gt;process&lt;/code&gt;,
and the npm exports it imports.&lt;/p&gt;
&lt;p&gt;So &amp;quot;where can this run?&amp;quot; is a question about one small node in a graph, and its edges are already written down.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works-under-the-hood&quot;&gt;How it works under the hood&lt;/h2&gt;
&lt;h3 id=&quot;1-what-each-definition-uses-directly&quot;&gt;1. What each definition uses directly&lt;/h3&gt;
&lt;p&gt;tcm looks at each definition on its own first.&lt;/p&gt;
&lt;p&gt;A definition needs the server if it uses a &lt;code&gt;node:&lt;/code&gt; module, a Node global (&lt;code&gt;process&lt;/code&gt;, &lt;code&gt;Buffer&lt;/code&gt;), tcm&amp;#39;s server
library, or an npm export whose browser build still imports Node&amp;#39;s modules.
tcm reads that last one from the package&amp;#39;s code, so it doesn&amp;#39;t rely on a list of known packages.&lt;/p&gt;
&lt;p&gt;A definition needs the browser if it uses a DOM global (&lt;code&gt;document&lt;/code&gt;, &lt;code&gt;window&lt;/code&gt;, &lt;code&gt;IntersectionObserver&lt;/code&gt;), a client-side
React API like &lt;code&gt;useState&lt;/code&gt;, or handles an event.&lt;/p&gt;
&lt;p&gt;Only code that runs counts.
Types are erased, so &lt;code&gt;(el: HTMLElement) =&amp;gt; …&lt;/code&gt; doesn&amp;#39;t put anything in the browser.
&lt;code&gt;el instanceof HTMLElement&lt;/code&gt; does.&lt;/p&gt;
&lt;h3 id=&quot;2-following-the-graph&quot;&gt;2. Following the graph&lt;/h3&gt;
&lt;p&gt;Then the answer spreads along the edges.
If &lt;code&gt;loadConfig&lt;/code&gt; uses &lt;code&gt;node:fs&lt;/code&gt;, and &lt;code&gt;greeting&lt;/code&gt; uses &lt;code&gt;loadConfig&lt;/code&gt;, then &lt;code&gt;greeting&lt;/code&gt; needs the server too.
tcm keeps the chain, so it can tell you why: &amp;quot;uses app.loadConfig, which uses node:fs&amp;quot;.&lt;/p&gt;
&lt;p&gt;It repeats until nothing changes, so code that calls itself (or two functions that call each other) gets the right
answer too.&lt;/p&gt;
&lt;h3 id=&quot;3-planning-the-app&quot;&gt;3. Planning the app&lt;/h3&gt;
&lt;p&gt;To build an app, tcm walks the graph from its entry point (&lt;code&gt;main&lt;/code&gt;, or a React component).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code that can run in the browser goes in the browser bundle, and tcm keeps walking through what it uses.&lt;/li&gt;
&lt;li&gt;When it reaches an async server function, it stops. That function becomes an &lt;strong&gt;endpoint&lt;/strong&gt;, and nothing behind it
is followed into the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-stubs-in-place-of-server-code&quot;&gt;4. Stubs in place of server code&lt;/h3&gt;
&lt;p&gt;In the browser build, each endpoint&amp;#39;s module is swapped out before the bundler ever resolves it.
In its place is a one-line stub:&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; hostCount&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;&amp;#x3C;hash of hostCount&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;app.hostCount&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;remote&lt;/code&gt; sends the arguments to &lt;code&gt;POST /__tcm/rpc/&amp;lt;hash&amp;gt;&lt;/code&gt;, and turns the reply (or the error) back into a promise.
The server function&amp;#39;s code, and everything it uses, never reaches the browser.&lt;/p&gt;
&lt;h3 id=&quot;5-the-server-side&quot;&gt;5. The server side&lt;/h3&gt;
&lt;p&gt;On the server, the only callable functions are the endpoints the plan found.
Anything else is refused.&lt;/p&gt;
&lt;p&gt;TypeScript&amp;#39;s types are gone at runtime, so an endpoint is effectively public input.
tcm derives a runtime schema from each server function&amp;#39;s parameter types, and checks every call against it.
Wrong types, missing fields or extra arguments get a 400 before your function runs.
Calls also have to come from the same origin and send JSON, which rules out cross-site form posts.&lt;/p&gt;
&lt;h3 id=&quot;6-calls-by-hash&quot;&gt;6. Calls by hash&lt;/h3&gt;
&lt;p&gt;The browser calls a server function by its hash, not its name or a route.
A hash never changes meaning: if &lt;code&gt;hostCount&lt;/code&gt; changes, it gets a new hash.&lt;/p&gt;
&lt;p&gt;So deploys don&amp;#39;t break open tabs.
A tab loaded before a deploy keeps calling the hashes it was built with, and the server keeps those builds&amp;#39; endpoints
callable.&lt;/p&gt;
&lt;h2 id=&quot;the-line-falls-out-of-the-graph&quot;&gt;The line falls out of the graph&lt;/h2&gt;
&lt;p&gt;None of this is a separate feature bolted on.
Once code is stored as a graph of small definitions, each with its uses written down, the boundary is just a
question you can ask the graph.&lt;/p&gt;
&lt;p&gt;You write functions.
tcm works out which side of the network each one belongs on, and builds the bridge between them.&lt;/p&gt;
</description>
</item>
<item>
<title>Code Validation</title>
<link>https://tcm.codes/blog/code-validation.html</link>
<guid isPermaLink="true">https://tcm.codes/blog/code-validation.html</guid>
<pubDate>Sun, 08 Nov 2026 00:00:00 GMT</pubDate>
<dc:creator>Andrew Lisowski</dc:creator>
<description>&lt;p&gt;One thing that representing your code as a graph of values unlocks is far more incremental builds.
Packages, files, folders and apps are a poor way to tell whether a piece of code needs to be checked again.&lt;/p&gt;
&lt;h2 id=&quot;traditional-js&quot;&gt;Traditional JS&lt;/h2&gt;
&lt;p&gt;An app might be split into packages like these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;core/&lt;/code&gt;: shared functionality for the apps&lt;/li&gt;
&lt;li&gt;&lt;code&gt;components/&lt;/code&gt;: frontend UI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apps/dashboard/&lt;/code&gt;: an internal dashboard&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apps/client/&lt;/code&gt;: the user-facing UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whenever anything in &lt;code&gt;core/&lt;/code&gt; changes, everything that depends on &lt;code&gt;core/&lt;/code&gt; has to be rebuilt, re-linted and re-tested.
Here that means validating every bit of code, even code that didn&amp;#39;t change and doesn&amp;#39;t use anything that did.&lt;/p&gt;
&lt;h2 id=&quot;values-in-tcm&quot;&gt;Values in tcm&lt;/h2&gt;
&lt;p&gt;In tcm, your code isn&amp;#39;t organized into packages, files or apps. Each value is stored individually, and the imports
between them form a graph. From that graph, we know exactly which code needs to be validated again.&lt;/p&gt;
&lt;p&gt;Represented as a graph, the example above might look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SomeType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;someArr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ArrayRenderer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UserRenderer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DashboardApp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClientApp&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we change &lt;code&gt;someArr&lt;/code&gt;, we know exactly what needs to be validated again, and everything else can be skipped.&lt;/p&gt;
&lt;h2 id=&quot;build-superpowers&quot;&gt;Build Superpowers&lt;/h2&gt;
&lt;p&gt;In traditional projects, it&amp;#39;s very easy to introduce dead code (code that isn&amp;#39;t used anywhere).
Dead code makes your project bigger and slower. With tcm, it never gets there in the first place.&lt;/p&gt;
&lt;p&gt;The graph does away with a lot of the complexity of the past:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dead code elimination:&lt;/strong&gt; code that isn&amp;#39;t connected to the graph just isn&amp;#39;t included.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tree shaking:&lt;/strong&gt; there&amp;#39;s no need to shake out what isn&amp;#39;t referenced.&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>
<item>
<title>Why Files</title>
<link>https://tcm.codes/blog/why-files.html</link>
<guid isPermaLink="true">https://tcm.codes/blog/why-files.html</guid>
<pubDate>Sun, 08 Nov 2026 00:00:00 GMT</pubDate>
<dc:creator>Andrew Lisowski</dc:creator>
<description>&lt;p&gt;The core idea behind tcm is that there are no files: your code is stored in a database (called a codebase).
But that raises the question: why do we store code in files at all?&lt;/p&gt;
&lt;p&gt;Well, the answer is kind of obvious: that&amp;#39;s just how computers work!
But that&amp;#39;s not the full story. Files are there because we put them there.
Files help us define something, organize similar things into groups, and then group those further into folders.
Seems necessary? Maybe not.&lt;/p&gt;
&lt;h2 id=&quot;the-downsides-of-files-for-code&quot;&gt;The downsides of files for code&lt;/h2&gt;
&lt;p&gt;So files and folders are (seemingly) good for organizing our code, but what baggage comes with them?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Placement comes with constraints&lt;/strong&gt; - Putting a bit of code in a package seems simple, right? Not really. That package might only work on the server, so code the client needs can&amp;#39;t go there. If you want that code on both the server and the client, you have to set up a third package to hold the shared code. You&amp;#39;ll also have to make sure that package stays isomorphic, or else your bundle might blow up or your server might crash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Files need managing&lt;/strong&gt; - Is a value really used? We can try to rely on our tools, but cruft accumulates. Is the file too long? Too short? Too spread out? You need to decide ahead of time how much or how little should go where. That might make sense in the moment, but not as a project evolves.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folders&lt;/strong&gt; - And once you have files, which folder do you put them in? Oh, that changed? Now countless files have to change. Just like with files, we have to anticipate the ideal structure, then suffer later when it inevitably turns out to be slightly the wrong shape.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;#39;s a whole lotta problems that aren&amp;#39;t code!
Files and folders are littered with hurdles that have nothing to do with shipping code, just with organizing it to appease the beast.&lt;/p&gt;
&lt;h2 id=&quot;knowledge-bases&quot;&gt;Knowledge bases&lt;/h2&gt;
&lt;p&gt;An interesting thing about knowledge bases is that the less organization, the better!
Folders and organization are choices, and those choices mean constant maintenance and having to categorize things.
If you free yourself from that, and instead keep a single flat folder of files that organizes itself through the connections between them, you start to do a lot less work.&lt;/p&gt;
&lt;p&gt;Another concept found in knowledge bases is &amp;quot;evergreen notes&amp;quot;.
These notes are bite-sized concepts that you link together to form a web of thoughts and knowledge.
With this, you don&amp;#39;t repeat yourself much, but you have very detailed context embedded in the thoughts.&lt;/p&gt;
&lt;p&gt;Could we apply these principles to code?&lt;/p&gt;
&lt;h2 id=&quot;a-graph-of-values&quot;&gt;A graph of values&lt;/h2&gt;
&lt;p&gt;When you add code to your codebase, each value (types, constants, functions, classes) is stored as a row in a database.
If you follow their imports, you get a big graph of how all the code in your program connects.&lt;/p&gt;
&lt;p&gt;You can think of it just like a knowledge base.
Instead of a directory of files with relative links to each other, it&amp;#39;s a graph of bite-sized values.&lt;/p&gt;
&lt;p&gt;Apps, packages and scripts become subsections of the graph, picked out into whatever output format we want.
Need a package?
It materializes with the things you need, and none of the things you don&amp;#39;t.
Because we work in values, we don&amp;#39;t have to worry that an array lives in a server package: we just need the value.&lt;/p&gt;
&lt;p&gt;Suddenly, all the problems of files are gone! (There are, of course, many new problems caused by this.)
We didn&amp;#39;t need to anticipate the needs of the future with how we structured our code&amp;#39;s files.
Instead, our code can transform to meet the demands of the moment.&lt;/p&gt;
&lt;p&gt;Working on a feature? Pull all the relevant values into one file, or as many as you want.
The code can meet your needs right there, no planning needed.&lt;/p&gt;
&lt;h2 id=&quot;files-are-human&quot;&gt;Files are human&lt;/h2&gt;
&lt;p&gt;Files are ultimately a tool for humans.
Managing code the way tcm does, in a codebase, is a very different way of working.
It&amp;#39;s a bit easier to remember where a file is.
After working in a codebase for a while, you get to know the lay of the land: you remember where that function is stored, like a mark on a map.&lt;/p&gt;
&lt;p&gt;But that topology comes with constraints, and with our code as a graph, we can work around many of them.&lt;/p&gt;
</description>
</item>
<item>
<title>Introducing tcm</title>
<link>https://tcm.codes/blog/introducing-tcm.html</link>
<guid isPermaLink="true">https://tcm.codes/blog/introducing-tcm.html</guid>
<pubDate>Sat, 07 Nov 2026 00:00:00 GMT</pubDate>
<dc:creator>Andrew Lisowski</dc:creator>
<description>&lt;p&gt;tcm is a new way to store TypeScript. Instead of a folder of text files, your code lives in a &lt;strong&gt;codebase&lt;/strong&gt;: a
database of code. Each function, type and constant is stored on its own, under the hash of its code. Names are kept
apart from the code, as labels that point at those hashes.&lt;/p&gt;
&lt;p&gt;Writing code doesn&amp;#39;t change. You write ordinary TypeScript in a scratch file, in the editor you already use. Once it
typechecks, you add it to the codebase, which stores each definition in it. There&amp;#39;s no new language: Node runs the
stored code directly.&lt;/p&gt;
&lt;p&gt;Keeping names apart from code makes everyday edits simpler. A rename moves a label, so no code is edited and nothing
breaks. Changing a definition doesn&amp;#39;t leave the code that uses it behind, either. tcm checks each place that uses it
against the new version. The ones that still typecheck move to it, and the rest go on a todo list, with the reason.&lt;/p&gt;
&lt;h2 id=&quot;where-it-comes-from&quot;&gt;Where it comes from&lt;/h2&gt;
&lt;p&gt;Most of the model is &lt;a href=&quot;https://www.unison-lang.org&quot;&gt;Unison&lt;/a&gt;&amp;#39;s. Unison is a language built around storing code by its
hash, and it showed what that makes possible: free renames, changes that carry through, scratch files that show
values as you write, search by type, and history that knows a rename from an edit. tcm brings that model to
TypeScript, and keeps the compiler, packages and editors people already use.&lt;/p&gt;
&lt;p&gt;Sharing works as it does on &lt;a href=&quot;https://share.unison-lang.org&quot;&gt;Unison Share&lt;/a&gt;. Codebases are pushed and pulled by hash,
and a contribution is a branch, reviewed as a diff of definitions rather than lines of text.&lt;/p&gt;
&lt;h2 id=&quot;your-code-as-a-graph&quot;&gt;Your code as a graph&lt;/h2&gt;
&lt;p&gt;Each definition is stored with the hashes of what it uses, so a codebase is an exact graph of your code. A folder of
files is a rough version of that graph: import one function from a file, and you depend on the whole file. With the
exact graph, tcm knows what any change reaches, and much of what it does follows from that.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tests run once.&lt;/strong&gt; A test&amp;#39;s result is kept with the hashes it reached. Only the tests a change reaches run again,
and results travel with the code when you push and pull.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apps ship only what they use.&lt;/strong&gt; An app is bundled from what it reaches, so there&amp;#39;s no dead code to remove.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code runs where it belongs.&lt;/strong&gt; Whether a definition runs on the server or in the browser is worked out from what
it uses. Browser code calls a server function by importing it, with no routes or fetch calls. React server
components work the same way, with no &lt;code&gt;&amp;quot;use client&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;use server&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploys don&amp;#39;t break open tabs.&lt;/strong&gt; Calls from the browser to the server go by hash, and earlier builds stay
served, so a tab keeps calling the code it loaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://tcm.codes/docs/claims.html&quot;&gt;What goes away&lt;/a&gt; goes through each of these, and where it stops.&lt;/p&gt;
&lt;h2 id=&quot;try-it&quot;&gt;Try it&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tcm.codes/docs/getting-started.html&quot;&gt;Install tcm&lt;/a&gt; and take the &lt;a href=&quot;https://tcm.codes/docs/tutorial-codebase.html&quot;&gt;tutorial&lt;/a&gt;, or browse
&lt;a href=&quot;https://tcm.codes/forge/&quot;&gt;tcm&amp;#39;s own code&lt;/a&gt;. tcm is developed in a codebase of its own, and its docs and this blog are pages in it.&lt;/p&gt;
</description>
</item>
</channel>
</rss>
