Lying to TypeScript for Fun and Profit
Tags: typescript, programminglanguages
Published: 2023-03-23 19:36:39

---
title: Lying to TypeScript for Fun and Profit
published: true
date: 2023-03-21 21:10:00 UTC
tags: TypeScript,ProgrammingLanguages
canonical_url: https://kevincox.ca/2023/03/21/lying-to-typescript/
---

<main style="--width:min(36em,100%);display:grid;grid-template-columns:1fr var(--width) 1fr;overflow:auto;padding:0;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">I’ve been writing a lot more <a href="https://www.typescriptlang.org/" style="box-sizing:border-box;margin:0;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray">TypeScript</a> recently and have been pleasantly surprised. It mostly negates the downsides of JavaScript and creates a fairly pleasant programming environment.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">One interesting thing about TypeScript is that it feels that there is a weaker bond between the compile-time types and run-time types than in most other programming languages. For example TypeScript considers the following code perfectly legitimate.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span> <span style="box-sizing:border-box;margin:0;padding:0">data</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;margin:0;padding:0">JSON</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">parse</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">{}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">);</span>
<span style="box-sizing:border-box;margin:0;padding:0">data</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">achieveWorldPeace</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">();</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">This is because <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">JSON</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">parse</span></code> returns <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">any</span></code>. This is a magical type that causes TypeScript to assume that whatever you do is correct. So TypeScript assumes that <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">data</span></code> has a method <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">achieveWorldPeace</span></code> that can be called with no arguments to return <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">any</span></code>.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">You will commonly see code such as:</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span> <span style="box-sizing:border-box;margin:0;padding:0">res</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">await</span> <span style="box-sizing:border-box;margin:0;padding:0">fetch</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">/api.json</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">);</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span> <span style="box-sizing:border-box;margin:0;padding:0">data</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">ApiResponse</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">await</span> <span style="box-sizing:border-box;margin:0;padding:0">res</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">json</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">();</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">This is better. Because now you have “diffused” the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">any</span></code> and TypeScript will no longer let you call <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">data</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">achieveWorldPeace</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">()</span></code>. However, nothing actually checked that <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">data</span></code> implements <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">ApiResponse</span></code>. The following code is perfectly fine.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span> <span style="box-sizing:border-box;margin:0;padding:0">data</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span><span style="box-sizing:border-box;margin:0;padding:0">bar</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;margin:0;padding:0">JSON</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">parse</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">`{"foo": 7}`</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">);</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Of course this isn’t specific to TypeScript. Many languages have backdoors to their type system. The explicit way to do this in TypeScript is <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">v</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">ApiResponse</span></code>. Other languages have similar escapes; Rust has <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">unsafe</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">std</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">::</span><span style="box-sizing:border-box;margin:0;padding:0">mem</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">::</span><span style="box-sizing:border-box;margin:0;padding:0">transmute</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">::</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&lt;</span><span style="box-sizing:border-box;margin:0;padding:0">ApiResponse</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&gt;</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">v</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span></code> and C++ has <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">reinterpret_cast</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&lt;</span><span style="box-sizing:border-box;margin:0;padding:0">ApiResponse</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&gt;</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">v</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span></code>. The difference is that in those two languages casting is very restricted, most casts will cause undefined behaviour. In TypeScript the types don’t affect the runtime code at all. This means that as long as the code happens to be correct subverting the type system can be relatively safe and useful.</p>
<h2 id="new-types" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#new-types" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">New Types</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">The most common use I see is implementing “new type” aliases. Since TypeScript is structurally typed it can be difficult to ensure validation via the type system. For example in Rust I will often use the “New Type Struct” pattern for validated values.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">pub</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">struct</span> <span style="box-sizing:border-box;margin:0;padding:0">HtmlEscaped</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">String</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">);</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">pub</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">fn</span> <span style="box-sizing:border-box;margin:0;padding:0">escape</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span><span style="box-sizing:border-box;margin:0;padding:0">str</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">HtmlEscaped</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;margin:0;padding:0">HtmlEscaped</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">escape_html</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">))</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>

<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// Warning! Unsafe! Can cause XSS vulnerabilities</span>
<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// if `text` is not in fact trustworthy.</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">pub</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">fn</span> <span style="box-sizing:border-box;margin:0;padding:0">write_trusted_raw_html</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span><span style="box-sizing:border-box;margin:0;padding:0">str</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;margin:0;padding:0">println!</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"{text}"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">);</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">pub</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">fn</span> <span style="box-sizing:border-box;margin:0;padding:0">write_text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">HtmlEscaped</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// We trust this text because it was escaped.</span>
	<span style="box-sizing:border-box;margin:0;padding:0">write_trusted_raw_html</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">.0</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">You can see that this makes it much harder for the user to accidentally write unescaped HTML and cause a XSS vulnerability. They are forced to either escape their HTML so that they can call <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">write_text</span></code> or use the raw function with a scary name that will make them (and their code reviewer) double check that the value is indeed trustworthy.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Doing something similar in TypeScript is not straightforward because in a structural typed language types are just aliases.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">ohno</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// These are the same type.</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<h2 id="being-honest" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#being-honest" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Being Honest</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">The natural way to do this is add some sort of marker that the text has been escaped.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">extends</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">__properlyEscapedMarker</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">true</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">conversionBlocked</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// ERROR: Property '__properlyEscapedMarker' is missing</span>
	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap">// in type 'RawText' but required in type 'EscapedHtml'</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Then you can provide a small helper to do the escaping.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">escapeText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">textToHtml</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">),</span>
		<span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">__properlyEscapedMarker</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">true</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">,</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Now it will be very difficult to accidentally pass a <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">RawText</span></code> where an <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span></code> is expected.</p>
<h2 id="small-fibs" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#small-fibs" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Small Fibs</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">But what if you didn’t want to actually update the type. Maybe the code is performance-sensitive or you don’t want the value to be visible at runtime. You can work around this by casting the value. You can think of this as “blessing” the object into the verified/sanitized type.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">extends</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;margin:0;padding:0">__properlyEscapedMarker</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">never</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">escapeText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span> <span style="box-sizing:border-box;margin:0;padding:0">escaped</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">textToHtml</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">text</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">),</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">escaped</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Now the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">__properlyEscapedMarker</span></code> property is never actually added to the object at runtime. It exists only in the type system to avoid accidental conversions.</p>
<h2 id="symbols" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#symbols" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Symbols</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">You can make this slightly more robust by using a <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span></code> which allows creating a unique property. This allows controlling the ability to create these objects with regular visibility rules.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">extends</span> <span style="box-sizing:border-box;margin:0;padding:0">RawText</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtmlTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Of course this has limited benefit as anyone anywhere can still do <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">foo</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span></code>. It only prevents accidental collisions which are unlikely to be a problem in practice.</p>
<h2 id="optimization" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#optimization" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Optimization</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">In some cases it is desirable to avoid the extra object wrapper. Just use a union type if your “base” type isn’t an <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">object</span></code>.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">type</span> <span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">EscapedHtmlTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Now at runtime you are just passing around a string, the checks only exist at compile time.</p>
<h2 id="hiding-the-implementation" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#hiding-the-implementation" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Hiding the Implementation</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">For the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">EscapedHtml</span></code> type it is probably fine for the user to use the value as a <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span></code>. However imagine that you wanted to hide this implementation. This can create a unique type that can only be used in the “correct” places (and places that accept <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">any</span></code> or <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">unknown</span></code>).</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemIdTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">wrapItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">id</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">unknown</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">unwrapItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">id</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;margin:0;padding:0">unknown</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">string</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">listIems</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">():</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">[]</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">listItemsRaw</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">().</span><span style="box-sizing:border-box;margin:0;padding:0">map</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=&gt;</span> <span style="box-sizing:border-box;margin:0;padding:0">wrapItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">));</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>

<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">function</span> <span style="box-sizing:border-box;margin:0;padding:0">getItem</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">):</span> <span style="box-sizing:border-box;margin:0;padding:0">Item</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">return</span> <span style="box-sizing:border-box;margin:0;padding:0">getItemRaw</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">unwrapItemId</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">));</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">This pattern completely hides the details of <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">ItemId</span></code> from the type system. So users external to this module are unable to (safely) create or to use them in many ways. (Again types that take <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">any</span></code> or <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">unknown</span></code> like <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">console</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">.</span><span style="box-sizing:border-box;margin:0;padding:0">log</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">()</span></code> will still be able to accept them). For example this could be useful to help control how the ID is presented to the user or embedded in URLs.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">I highly recommend creating a pair of functions like <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">wrapItemId</span></code> and <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">unwrapItemId</span></code> to keep the unsafety isolated. But don’t export them so that only the relevant module can create instances of this type without <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">as</span></code>.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">Unfortunately many operations are allowed on all types, so <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">`</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">${</span><span style="box-sizing:border-box;margin:0;padding:0">id</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">`</span></code> will still work. But it at last prevents passing to any functions or having non-universal methods called on it.</p>
<aside style="background:#333;box-shadow:4px 4px 8px #000;box-sizing:border-box;color:#fff;grid-column:2;margin:.2em 1em .6em"><p style="box-sizing:border-box;margin:.4em"><strong style="box-sizing:border-box;margin:0">Note</strong>: If you really want to prevent method calls you can union with <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">undefined</span></code> or <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">null</span></code>. However I wouldn’t recommend this as it produces bad error messages.</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em .8em"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">type</span> <span style="box-sizing:border-box;margin:0;padding:0">ItemId</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">ItemIdTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span> <span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">undefined</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
</code></pre></div>
<p style="box-sizing:border-box;margin:.4em">In most cases typescript calls this type <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">undefined</span></code> rather than <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">ItemId</span></code>.</p></aside><h2 id="summary" style="box-sizing:border-box;grid-column:2;margin:.4em 1rem .8em"><a href="https://kevincox.ca/2023/03/21/lying-to-typescript/#summary" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Summary</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">If you want to create a unique type use the following:</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">interface</span> <span style="box-sizing:border-box;margin:0;padding:0">MyType</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;margin:0;padding:0">MyTypeTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
</code></pre></div>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">I have found this to give consistently good error messages and provide solid protection against errors.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0 1rem .8em">If you want to “extend” an existing type <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:.1em;white-space:pre-wrap"><span style="box-sizing:border-box;margin:0;padding:0">BaseType</span></code> with a tag do this:</p>
<div style="background:#222;border-bottom:1px solid #444;border-top:1px solid #444;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:1fr var(--width) 1fr;margin:0 0 .8em;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;margin:.4em 1rem"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">export</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">type</span> <span style="box-sizing:border-box;margin:0;padding:0">MyType</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;margin:0;padding:0">BaseType</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">&amp;</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">readonly</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">MyTypeTag</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;margin:0;padding:0">unique</span> <span style="box-sizing:border-box;margin:0;padding:0">symbol</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
</code></pre></div></main>