<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>SSOT · djuleayo</title>
    <link>https://316c0611.personalpage-ahl.pages.dev/tags/ssot/</link>
    <description></description>
    <generator>Hugo</generator>
    <language>en</language>
    <atom:link href="https://316c0611.personalpage-ahl.pages.dev/tags/ssot/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>TS API Spec</title>
      <link>https://316c0611.personalpage-ahl.pages.dev/posts/ts-api-spec/</link>
      <guid>https://316c0611.personalpage-ahl.pages.dev/posts/ts-api-spec/</guid>
      <pubDate>Sat, 20 Dec 2025 11:13:48 &#43;0200</pubDate>
      <description>&amp;lt;h1 id=&amp;#34;ts-api-spec&amp;#34;&amp;gt;
  TS API Spec
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#ts-api-spec&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h1&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;npm: &amp;lt;a href=&amp;#34;https://www.npmjs.com/package/@apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;https://www.npmjs.com/package/@apispects/core&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;repo: &amp;lt;a href=&amp;#34;https://github.com/apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;https://github.com/apispects/core&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;ts-first-api-spec-with-superb-dx&amp;#34;&amp;gt;
  TS-first API spec with superb DX
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#ts-first-api-spec-with-superb-dx&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;figure&amp;gt;&amp;lt;img src=&amp;#34;/tsapispec.demo.gif&amp;#34;&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Everything is autocompleted and type-checked.&amp;lt;br&amp;gt;
As soon as the schema is satisfied, the error disappears.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;No separate spec file.&amp;lt;br&amp;gt;
No generation step.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;the-problem-this-targets&amp;#34;&amp;gt;
  The problem this targets
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#the-problem-this-targets&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Most teams still pay the same tax:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;duplicated contracts (controllers, OpenAPI, generated clients)&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;contract drift between backend and frontend&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;stale generated code&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;runtime bugs shipped by “green” builds&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;The root cause is structural:&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;the contract is treated as an artifact, not as the boundary.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;benefits&amp;#34;&amp;gt;
  Benefits
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#benefits&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Maximum code sharing between nodes&amp;lt;/strong&amp;gt; (assuming JS/TS)&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Fully typed end-to-end&amp;lt;/strong&amp;gt;, including error cases&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Uniform error handling&amp;lt;/strong&amp;gt; across server and client&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Single source of truth&amp;lt;/strong&amp;gt;: router and apiClient are generated from the same spec&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;Optional runtime validation can be enabled in non-prod without changing the contract.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;overlap-with-existing-approaches&amp;#34;&amp;gt;
  Overlap with existing approaches
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#overlap-with-existing-approaches&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;This overlaps with:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;OpenAPI &#43; codegen&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;RPC frameworks&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;shared schema repos&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Difference:&amp;lt;/strong&amp;gt; those generate code &amp;lt;em&amp;gt;from&amp;lt;/em&amp;gt; a spec.&amp;lt;br&amp;gt;
Here, the spec &amp;lt;em&amp;gt;is&amp;lt;/em&amp;gt; the code boundary.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;why-this-is-especially-leveraged-in-jsts&amp;#34;&amp;gt;
  Why this is especially leveraged in JS/TS
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#why-this-is-especially-leveraged-in-jsts&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;JS has an unfair advantage:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;same language on backend and frontend&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;TypeScript as the shared type system&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;That enables:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;zero drift&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;zero regeneration&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;no stale specs&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;example&amp;#34;&amp;gt;
  Example
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#example&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Declare your spec once:&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; authRouter &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  register_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;registerRequestSchema&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;registrationError&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  login_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;loginRequestSchema&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;loginErrors&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  refreshToken_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    headerSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;authHeader&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.null&amp;lt;/span&amp;gt;(),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; apiSpec &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  api&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    auth: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;authRouter&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    macro: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;macroRouter&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec; &amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// key line (TS 5)
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//Generate both router and client from the same spec:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { router: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;apiRouter&amp;lt;/span&amp;gt; } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(apiSpec, {}, express.Router);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//Controller implementation is fully typed by construction:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;makeController(
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  authRouter.register_post,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;async&amp;lt;/span&amp;gt; ({ body&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; { email, name, password } }) &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; user &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; registerUser(email, name, password);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;typeof&amp;lt;/span&amp;gt; user &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;===&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#39;string&amp;amp;#39;&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; user; &amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// typed error case
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; { token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;jwtSign&amp;lt;/span&amp;gt;({ userId: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;user.id&amp;lt;/span&amp;gt; }) };
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//And the client derives from the same contract:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { apiClient } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(apiSpec, {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  baseUrl&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#39;http://localhost:3033&amp;amp;#39;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;If you violate the contract on either side, TypeScript complains immediately.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;how-its-built&amp;#34;&amp;gt;
  How it’s built
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#how-its-built&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Parser types&amp;lt;/strong&amp;gt; (the core mechanism):&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;#34;http://djuleayo.com/posts/parser-types/&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;http://djuleayo.com/posts/parser-types/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;TypeScript 5 &#43; Vite&amp;lt;/strong&amp;gt; for clean cross-module sharing&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;what-this-optimizes-for&amp;#34;&amp;gt;
  What this optimizes for
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#what-this-optimizes-for&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;correctness over ceremony&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;DX over documentation&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;contracts that cannot drift by construction&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;If you can break the contract without TypeScript complaining, the setup is wrong.&amp;lt;/p&amp;gt;
</description>
      <content:encoded>&amp;lt;h1 id=&amp;#34;ts-api-spec&amp;#34;&amp;gt;
  TS API Spec
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#ts-api-spec&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h1&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;npm: &amp;lt;a href=&amp;#34;https://www.npmjs.com/package/@apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;https://www.npmjs.com/package/@apispects/core&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;repo: &amp;lt;a href=&amp;#34;https://github.com/apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;https://github.com/apispects/core&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;ts-first-api-spec-with-superb-dx&amp;#34;&amp;gt;
  TS-first API spec with superb DX
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#ts-first-api-spec-with-superb-dx&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;figure&amp;gt;&amp;lt;img src=&amp;#34;/tsapispec.demo.gif&amp;#34;&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Everything is autocompleted and type-checked.&amp;lt;br&amp;gt;
As soon as the schema is satisfied, the error disappears.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;No separate spec file.&amp;lt;br&amp;gt;
No generation step.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;the-problem-this-targets&amp;#34;&amp;gt;
  The problem this targets
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#the-problem-this-targets&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Most teams still pay the same tax:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;duplicated contracts (controllers, OpenAPI, generated clients)&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;contract drift between backend and frontend&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;stale generated code&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;runtime bugs shipped by “green” builds&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;The root cause is structural:&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;the contract is treated as an artifact, not as the boundary.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;benefits&amp;#34;&amp;gt;
  Benefits
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#benefits&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Maximum code sharing between nodes&amp;lt;/strong&amp;gt; (assuming JS/TS)&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Fully typed end-to-end&amp;lt;/strong&amp;gt;, including error cases&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Uniform error handling&amp;lt;/strong&amp;gt; across server and client&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Single source of truth&amp;lt;/strong&amp;gt;: router and apiClient are generated from the same spec&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;Optional runtime validation can be enabled in non-prod without changing the contract.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;overlap-with-existing-approaches&amp;#34;&amp;gt;
  Overlap with existing approaches
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#overlap-with-existing-approaches&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;This overlaps with:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;OpenAPI &#43; codegen&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;RPC frameworks&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;shared schema repos&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Difference:&amp;lt;/strong&amp;gt; those generate code &amp;lt;em&amp;gt;from&amp;lt;/em&amp;gt; a spec.&amp;lt;br&amp;gt;
Here, the spec &amp;lt;em&amp;gt;is&amp;lt;/em&amp;gt; the code boundary.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;why-this-is-especially-leveraged-in-jsts&amp;#34;&amp;gt;
  Why this is especially leveraged in JS/TS
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#why-this-is-especially-leveraged-in-jsts&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;JS has an unfair advantage:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;same language on backend and frontend&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;TypeScript as the shared type system&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;That enables:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;zero drift&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;zero regeneration&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;no stale specs&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;example&amp;#34;&amp;gt;
  Example
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#example&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Declare your spec once:&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; authRouter &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  register_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;registerRequestSchema&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;registrationError&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  login_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;loginRequestSchema&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;loginErrors&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  refreshToken_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    headerSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;authHeader&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.null&amp;lt;/span&amp;gt;(),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; apiSpec &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  api&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    auth: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;authRouter&amp;lt;/span&amp;gt;,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    macro: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;macroRouter&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec; &amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// key line (TS 5)
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//Generate both router and client from the same spec:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { router: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;apiRouter&amp;lt;/span&amp;gt; } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(apiSpec, {}, express.Router);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//Controller implementation is fully typed by construction:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;makeController(
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  authRouter.register_post,
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;async&amp;lt;/span&amp;gt; ({ body&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; { email, name, password } }) &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; user &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; registerUser(email, name, password);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;typeof&amp;lt;/span&amp;gt; user &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;===&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#39;string&amp;amp;#39;&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; user; &amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// typed error case
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; { token: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;jwtSign&amp;lt;/span&amp;gt;({ userId: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;user.id&amp;lt;/span&amp;gt; }) };
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;//And the client derives from the same contract:
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;export&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { apiClient } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(apiSpec, {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  baseUrl&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#39;http://localhost:3033&amp;amp;#39;&amp;lt;/span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;If you violate the contract on either side, TypeScript complains immediately.&amp;lt;/p&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;how-its-built&amp;#34;&amp;gt;
  How it’s built
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#how-its-built&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Parser types&amp;lt;/strong&amp;gt; (the core mechanism):&amp;lt;br&amp;gt;
&amp;lt;a href=&amp;#34;http://djuleayo.com/posts/parser-types/&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;http://djuleayo.com/posts/parser-types/&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;TypeScript 5 &#43; Vite&amp;lt;/strong&amp;gt; for clean cross-module sharing&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;h2 id=&amp;#34;what-this-optimizes-for&amp;#34;&amp;gt;
  What this optimizes for
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#what-this-optimizes-for&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;correctness over ceremony&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;DX over documentation&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;contracts that cannot drift by construction&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;If you can break the contract without TypeScript complaining, the setup is wrong.&amp;lt;/p&amp;gt;
</content:encoded>
    </item>
  </channel>
</rss>
