<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Henrique Sasaki Yuya</title>
    <description>The latest articles on DEV Community by Henrique Sasaki Yuya (@moriturus).</description>
    <link>https://dev.to/moriturus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F91096%2Faa532131-7dbd-4113-bc83-c8cd03cc7859.jpeg</url>
      <title>DEV Community: Henrique Sasaki Yuya</title>
      <link>https://dev.to/moriturus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moriturus"/>
    <language>en</language>
    <item>
      <title>A type-safe GitHub Actions workflow library in TypeScript</title>
      <dc:creator>Henrique Sasaki Yuya</dc:creator>
      <pubDate>Sat, 16 May 2026 10:10:01 +0000</pubDate>
      <link>https://dev.to/moriturus/a-type-safe-github-actions-workflow-library-in-typescript-29l4</link>
      <guid>https://dev.to/moriturus/a-type-safe-github-actions-workflow-library-in-typescript-29l4</guid>
      <description>&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;code&gt;ghawb&lt;/code&gt;&lt;/strong&gt;, a TypeScript-first authoring library for GitHub Actions workflows and composite actions. It keeps GitHub Actions explicit, but moves workflow authoring into typed, validated, testable code. You still commit normal YAML; you just do not have to hand-author all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prologue
&lt;/h2&gt;

&lt;p&gt;I like GitHub Actions.&lt;/p&gt;

&lt;p&gt;I do not like finding out that I made a trivial mistake only after I pushed a branch, waited for CI to boot, and then watched a workflow fail because I wrote the wrong shape of &lt;code&gt;permissions&lt;/code&gt;, mixed up a trigger filter, or typoed an output reference buried in YAML.&lt;/p&gt;

&lt;p&gt;That feedback loop is too late.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;code&gt;ghawb&lt;/code&gt;&lt;/strong&gt;: a TypeScript library for authoring GitHub Actions workflows and composite actions with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type-safe builders&lt;/li&gt;
&lt;li&gt;validation at construction time&lt;/li&gt;
&lt;li&gt;deterministic YAML rendering&lt;/li&gt;
&lt;li&gt;source-first distribution through JSR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if a workflow is code, then at least some workflow mistakes should be regular programming errors&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of treating &lt;code&gt;.github/workflows/*.yml&lt;/code&gt; as the source of truth, &lt;code&gt;ghawb&lt;/code&gt; treats TypeScript as the source and renders committed YAML from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I wanted to fix
&lt;/h2&gt;

&lt;p&gt;GitHub Actions YAML has an awkward failure mode.&lt;/p&gt;

&lt;p&gt;A lot of mistakes are not conceptually complicated, but they are still easy to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invalid combinations of trigger fields&lt;/li&gt;
&lt;li&gt;blank or malformed IDs&lt;/li&gt;
&lt;li&gt;step output references that point to nothing&lt;/li&gt;
&lt;li&gt;reusable workflow jobs using fields GitHub does not allow there&lt;/li&gt;
&lt;li&gt;matrix definitions that look plausible but are structurally wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are interesting problems.&lt;/p&gt;

&lt;p&gt;They are structural mistakes.&lt;/p&gt;

&lt;p&gt;And structural mistakes are exactly the kind of thing a type system and a validation layer should help with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;ghawb&lt;/code&gt; looks like
&lt;/h2&gt;

&lt;p&gt;Here is a small CI workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createWorkflowId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defineWorkflow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ghawb/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nodeCi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ghawb/job-helpers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineWorkflow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createWorkflowId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ci&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPush&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPullRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createJobId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nodeCi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;nodeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then render it with the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun x @ghawb/cli render &lt;span class="nt"&gt;--input&lt;/span&gt; workflows/ci.ts &lt;span class="nt"&gt;--output&lt;/span&gt; .github/workflows/ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is deterministic, so you can commit the generated YAML and review it normally.&lt;/p&gt;

&lt;p&gt;The important part is not that YAML is generated.&lt;/p&gt;

&lt;p&gt;The important part is that the authoring surface is now typed, validated, and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal is not to hide GitHub Actions
&lt;/h2&gt;

&lt;p&gt;I was not trying to pretend GitHub Actions is something other than GitHub Actions.&lt;/p&gt;

&lt;p&gt;The goal is narrower:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;keep the GitHub Actions model explicit&lt;/li&gt;
&lt;li&gt;catch structural mistakes earlier&lt;/li&gt;
&lt;li&gt;make large workflows easier to compose and review&lt;/li&gt;
&lt;li&gt;preserve committed YAML as a normal repository artifact&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That constraint matters.&lt;/p&gt;

&lt;p&gt;I did not want a magical DSL that hides the underlying workflow model so aggressively that users stop knowing what GitHub Actions will actually do.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;ghawb&lt;/code&gt; stays close to the platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;triggers are still triggers&lt;/li&gt;
&lt;li&gt;jobs are still jobs&lt;/li&gt;
&lt;li&gt;reusable workflows are still reusable workflows&lt;/li&gt;
&lt;li&gt;rendered output is still plain GitHub Actions YAML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not a replacement for understanding GitHub Actions.&lt;/p&gt;

&lt;p&gt;It is a better place to author GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kind of mistakes it catches
&lt;/h2&gt;

&lt;p&gt;This is where the library becomes useful in day-to-day work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ghawb&lt;/code&gt; validates things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identifier format for workflow IDs and job IDs&lt;/li&gt;
&lt;li&gt;invalid trigger/filter combinations&lt;/li&gt;
&lt;li&gt;duplicate or malformed step IDs&lt;/li&gt;
&lt;li&gt;references to undeclared step outputs in job outputs&lt;/li&gt;
&lt;li&gt;unsupported fields on reusable workflow jobs&lt;/li&gt;
&lt;li&gt;invalid matrix declarations&lt;/li&gt;
&lt;li&gt;invalid environment/config shapes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if a job output references a step output that was never declared, that should not be something you discover only after pushing a branch.&lt;/p&gt;

&lt;p&gt;If a reusable workflow job includes a field GitHub does not allow in that context, that should be rejected while the workflow is still being built.&lt;/p&gt;

&lt;p&gt;If a matrix definition has the wrong shape, that should be treated as an authoring error, not a CI surprise.&lt;/p&gt;

&lt;p&gt;So instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CI failed 3 minutes later because the workflow shape was invalid&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;this workflow definition is invalid while you are still authoring it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much better loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why TypeScript
&lt;/h2&gt;

&lt;p&gt;The value is not just type safety in the abstract.&lt;/p&gt;

&lt;p&gt;The value is that workflow definitions become regular software artifacts.&lt;/p&gt;

&lt;p&gt;That means you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;factor repeated workflow logic into named helpers&lt;/li&gt;
&lt;li&gt;test workflow construction&lt;/li&gt;
&lt;li&gt;inject render-time config&lt;/li&gt;
&lt;li&gt;use editor completion and refactoring&lt;/li&gt;
&lt;li&gt;keep reusable CI paths small and explicit&lt;/li&gt;
&lt;li&gt;review generated YAML without manually maintaining all of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A growing YAML file is hard to refactor safely.&lt;/p&gt;

&lt;p&gt;A TypeScript module can be composed, tested, and reviewed with the same tools you already use for application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it differs from heavier CI abstractions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ghawb&lt;/code&gt; is intentionally not trying to be a new CI system.&lt;/p&gt;

&lt;p&gt;It does not replace GitHub Actions with a different execution model.&lt;/p&gt;

&lt;p&gt;It does not try to hide the workflow schema behind a completely separate abstraction.&lt;/p&gt;

&lt;p&gt;It is closer to a typed authoring and rendering layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript is the source&lt;/li&gt;
&lt;li&gt;GitHub Actions YAML is the committed artifact&lt;/li&gt;
&lt;li&gt;GitHub Actions remains the runtime&lt;/li&gt;
&lt;li&gt;the rendered output stays reviewable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;Some tools give you a more powerful abstraction over builds or pipelines. That can be useful, but it also creates a larger conceptual boundary between the code you write and the workflow GitHub actually runs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ghawb&lt;/code&gt; is deliberately smaller than that.&lt;/p&gt;

&lt;p&gt;Its job is to make GitHub Actions authoring safer and more composable without making the resulting workflow feel mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package boundaries
&lt;/h2&gt;

&lt;p&gt;I wanted the core package to stay narrow.&lt;/p&gt;

&lt;p&gt;The main package, &lt;code&gt;@ghawb/sdk&lt;/code&gt;, is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workflow builders&lt;/li&gt;
&lt;li&gt;expressions&lt;/li&gt;
&lt;li&gt;rendering payloads&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More optional or opinionated parts live in separate packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@ghawb/job-helpers&lt;/code&gt; for higher-level helpers like Node CI setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ghawb/typed-actions&lt;/code&gt; for typed wrappers around common actions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ghawb/composite-actions&lt;/code&gt; for authoring &lt;code&gt;action.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ghawb/cli&lt;/code&gt; for rendering source modules into YAML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ghawb/reusable-workflow-import&lt;/code&gt; for bringing existing reusable workflow YAML into the typed world without pushing YAML parsing into the SDK core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That split was intentional.&lt;/p&gt;

&lt;p&gt;Users can start with a small, explicit core and opt into the more opinionated layers only when they help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: a reusable, typed CI path
&lt;/h2&gt;

&lt;p&gt;This is the sort of thing I wanted to make boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createWorkflowId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defineWorkflow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ghawb/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nodeCi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ghawb/job-helpers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkflow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createWorkflowId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ci&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPush&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPullRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ci-${{ github.ref }}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cancelInProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createJobId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;job&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runsOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nodeCi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;nodeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not flashy.&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;CI should not be a place where I spend attention on preventable formatting and shape mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why JSR-only distribution
&lt;/h2&gt;

&lt;p&gt;This project ships through &lt;strong&gt;JSR only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That decision comes from what the project actually is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source-first TypeScript packages&lt;/li&gt;
&lt;li&gt;Bun as the default development runtime&lt;/li&gt;
&lt;li&gt;continued Deno support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current compatibility policy is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bun 1.x&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deno 2.x&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a deliberately smaller promise surface than “every JavaScript runtime forever.”&lt;/p&gt;

&lt;p&gt;For this project, JSR fits better than carrying extra packaging complexity just to preserve compatibility expectations that were not helping the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The repository is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;https://github.com/moriturus/ghawb.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx jsr add @ghawb/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want the CLI too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx jsr add @ghawb/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today, that install path does not create a local &lt;code&gt;ghawb&lt;/code&gt; executable because JSR's npm compatibility tarballs currently drop &lt;code&gt;package.json#bin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The working invocation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun x @ghawb/cli render &lt;span class="nt"&gt;--input&lt;/span&gt; workflows/ci.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you are on Deno:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno add jsr:@ghawb/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project README includes the current package layout and examples for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workflow authoring&lt;/li&gt;
&lt;li&gt;CLI rendering&lt;/li&gt;
&lt;li&gt;typed action wrappers&lt;/li&gt;
&lt;li&gt;composite action authoring&lt;/li&gt;
&lt;li&gt;reusable workflow import&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;If you have been treating GitHub Actions YAML as “just one of those things you have to suffer through,” I think there is real value in moving the authoring experience into typed code while still keeping the final YAML explicit and reviewable.&lt;/p&gt;

&lt;p&gt;The goal is not to make GitHub Actions disappear.&lt;/p&gt;

&lt;p&gt;The goal is to make preventable workflow mistakes show up earlier, closer to where they are created.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>githubactions</category>
      <category>iac</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Built a Type-Safe SI Unit Library in Swift — And the Compiler Catches Your Physics Mistakes</title>
      <dc:creator>Henrique Sasaki Yuya</dc:creator>
      <pubDate>Tue, 24 Mar 2026 11:06:02 +0000</pubDate>
      <link>https://dev.to/moriturus/i-built-a-type-safe-si-unit-library-in-swift-and-the-compiler-catches-your-physics-mistakes-32he</link>
      <guid>https://dev.to/moriturus/i-built-a-type-safe-si-unit-library-in-swift-and-the-compiler-catches-your-physics-mistakes-32he</guid>
      <description>&lt;p&gt;You've seen this bug before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;  &lt;span class="c1"&gt;// Compiles. Runs. Produces nonsense.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or worse — the Mars Climate Orbiter kind, where pound-seconds and newton-seconds silently mix, and a $327 million spacecraft burns up in the atmosphere.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a href="https://github.com/moriturus/SystemeInternational" rel="noopener noreferrer"&gt;SystemeInternational&lt;/a&gt;&lt;/strong&gt; to make that class of bug extinct in Swift — at compile time, with zero runtime cost.&lt;/p&gt;
&lt;h2&gt;
  
  
  What If the Type System Knew Physics?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SystemeInternational&lt;/code&gt; encodes physical dimensions, units, and even the distinction between &lt;em&gt;absolute positions&lt;/em&gt; and &lt;em&gt;intervals&lt;/em&gt; into Swift's type parameters. Every quantity is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Scalar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Space&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. &lt;strong&gt;8 bytes.&lt;/strong&gt; The &lt;code&gt;Unit&lt;/code&gt; and &lt;code&gt;Space&lt;/code&gt; are phantom types — they exist only at compile time and vanish completely in the binary.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Kilometer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3_600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;speed&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;  &lt;span class="c1"&gt;// ✅ Compiles — dimension is Length/Time&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;nonsense&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ Compile error — no overload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No runtime checks. No &lt;code&gt;if unit == .meter&lt;/code&gt;. The compiler simply won't let you add meters to seconds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Five Things That Make This Different
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Hertz ≠ Becquerel (Even Though They're Both 1/s)
&lt;/h3&gt;

&lt;p&gt;The SI system has unit pairs with identical dimensions but completely different physical meanings. Most unit libraries treat them as interchangeable. SystemeInternational doesn't:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;reciprocalRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CanonicalUnit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;QuotientDimension&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Dimensionless&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;TimeDimension&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reciprocalRate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interpreted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Hertz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;activity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reciprocalRate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interpreted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Becquerel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// 50.0&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// 50.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;Hertz&lt;/code&gt; and &lt;code&gt;Becquerel&lt;/code&gt; are different public types even though they share the same exponent dimension. The same idea applies to angular frequency (&lt;code&gt;rad/s&lt;/code&gt;) versus cyclic frequency (&lt;code&gt;Hz&lt;/code&gt;), and to &lt;code&gt;Gray&lt;/code&gt;/&lt;code&gt;Sievert&lt;/code&gt; (absorbed dose vs. equivalent dose). The library respects BIPM's semantic distinctions.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. You Can't Add Two Temperatures
&lt;/h3&gt;

&lt;p&gt;Adding 20°C + 25°C is physically meaningless — you can't add two absolute positions. But subtracting them to get a temperature &lt;em&gt;difference&lt;/em&gt; is perfectly valid.&lt;/p&gt;

&lt;p&gt;SystemeInternational models this with &lt;strong&gt;affine space algebra&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;room&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;CelsiusTemperatureValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// Affine (absolute position)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;boiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;CelsiusTemperatureValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// Affine&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rise&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;boiling&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;                     &lt;span class="c1"&gt;// ✅ Linear (interval): 80°C&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shifted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rise&lt;/span&gt;                        &lt;span class="c1"&gt;// ✅ Affine: 100°C&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;oops&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;boiling&lt;/span&gt;                     &lt;span class="c1"&gt;// ❌ Compile error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Expression&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Point − Point&lt;/td&gt;
&lt;td&gt;Vector&lt;/td&gt;
&lt;td&gt;Distance between positions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Point + Vector&lt;/td&gt;
&lt;td&gt;Point&lt;/td&gt;
&lt;td&gt;Shift a position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Point + Point&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;compile error&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adding positions is meaningless&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And yes — it validates against absolute zero at runtime:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;CelsiusTemperatureValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;274&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="c1"&gt;// throws QuantityError.belowAbsoluteZero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Exact Integer Arithmetic
&lt;/h3&gt;

&lt;p&gt;Need precise timing in embedded systems? Use integer scalars:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;exactly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;seconds&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convertedIfExactly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Second&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exactValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Optional(2)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;oneMeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;exactly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;oneMeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convertedIfExactly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Kilometer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// throws — 0.001 isn't an Int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No silent truncation. No floating-point drift. It throws when the math doesn't work out exactly.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Rational Scale Factors (Not Floating-Point)
&lt;/h3&gt;

&lt;p&gt;Unit scales are stored as &lt;strong&gt;rational numbers with decimal exponents&lt;/strong&gt;, preserving precision that &lt;code&gt;Double&lt;/code&gt; arithmetic would destroy:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1° = π/180 radians, stored as:
UnitScale(numerator: 3_141_592_653_589_793, denominator: 180, decimalExponent: -15)

// 1 eV = 1.602176634 × 10⁻¹⁹ J (exact by 2019 SI redefinition), stored as:
UnitScale(numerator: 1_602_176_634, denominator: 1, decimalExponent: -28)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This means conversions like &lt;code&gt;Degree → Radian&lt;/code&gt; or &lt;code&gt;ElectronVolt → Joule&lt;/code&gt; carry the full precision of the defining constants.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Thin Abstractions the Optimizer Can See Through
&lt;/h3&gt;

&lt;p&gt;Hot-path accessors and arithmetic are annotated with &lt;code&gt;@inlinable&lt;/code&gt;, so the compiler can inline across module boundaries and apply full optimizations in Release builds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Debug&lt;/th&gt;
&lt;th&gt;Release&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;convert_kilometer_to_meter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;225 ns/op&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3 ns/op&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~75×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;semantic_lumen_operator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;291 ns/op&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83 ns/op&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~3.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;semantic_lux_operator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;435 ns/op&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;155 ns/op&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~2.8×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Same-unit operations like &lt;code&gt;linear_add_same_unit&lt;/code&gt; and &lt;code&gt;multiply_canonical_area&lt;/code&gt; measured &lt;strong&gt;0 ns/op&lt;/strong&gt; in Release — the optimizer eliminated them entirely via dead code elimination, confirming that the phantom-type abstractions add no barriers to standard compiler optimizations. Real-world code that &lt;em&gt;uses&lt;/em&gt; the results will still pay the cost of a bare &lt;code&gt;Double&lt;/code&gt; operation, but nothing more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key takeaway: &lt;strong&gt;the type-safety layer is transparent to the optimizer.&lt;/strong&gt; You get compile-time unit checking without runtime overhead beyond the underlying arithmetic.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Module Architecture
&lt;/h2&gt;

&lt;p&gt;SystemeInternational is split into focused, composable modules:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UnitesSI              ← Main facade (import this)
├── UnitesDeBaseDuSI  ← Core: Quantity, dimensions, 7 base units
├── PrefixesDuSI      ← All 20 SI prefixes (Quetta → Quecto)
└── UnitesDeriveesDuSI← Named derived units + temperature scales

UtiliseesNonSI        ← 16 BIPM-accepted non-SI units
UnitesSICompat        ← Foundation.Measurement bridge
UtiliseesNonSICompat  ← Non-SI Foundation bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For most use cases, &lt;code&gt;import UnitesSI&lt;/code&gt; gives you everything. The modular design means you only link what you use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick Tour
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prefixed Units — All 20 SI Prefixes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Kilometer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tiny&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Microgram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;huge&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Gigahertz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Mass follows SI convention: Kilogram is base, but prefixes come from Gram&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Milligram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Kilogram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// 0.0005&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Derived Units with Semantic Operators
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;intensity&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Candela&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;solidAngle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Steradian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;luminous&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;solidAngle&lt;/span&gt;  &lt;span class="c1"&gt;// → Lumen&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;area&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// → m²&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;illuminance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;luminous&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;area&lt;/span&gt;  &lt;span class="c1"&gt;// → Lux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Foundation.Measurement Interop
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UnitesSICompat&lt;/span&gt;

&lt;span class="c1"&gt;// SystemeInternational → Foundation&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;road&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Kilometer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;12.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foundationMeasurement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;road&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// "km"&lt;/span&gt;

&lt;span class="c1"&gt;// Foundation → SystemeInternational&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Measurement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UnitTemperature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;absoluteTemperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DegreeCelsius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Kelvin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// 298.15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Non-SI Accepted Units
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UtiliseesNonSI&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;water&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Milliliter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rightAngle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Degree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;gain&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Quantity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Decibel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Linear&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;water&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Liter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// 0.5&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rightAngle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;converted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Radian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1.5707963267948966&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Why "Systeme International"?
&lt;/h2&gt;

&lt;p&gt;The name comes from the French &lt;em&gt;Système International d'Unités&lt;/em&gt; — the official name of the SI system, maintained by the &lt;a href="https://www.bipm.org/en/measurement-units/si" rel="noopener noreferrer"&gt;Bureau International des Poids et Mesures (BIPM)&lt;/a&gt;. The module names follow the same convention: &lt;em&gt;Unités de base du SI&lt;/em&gt;, &lt;em&gt;Préfixes du SI&lt;/em&gt;, &lt;em&gt;Utilisées non-SI&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements &amp;amp; Installation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Swift 6.2+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dependencies&lt;/strong&gt; — pure Swift, no external packages
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Package.swift&lt;/span&gt;
&lt;span class="nv"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/moriturus/SystemeInternational.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;306 tests. 100% coverage target. Apache 2.0 licensed.&lt;/p&gt;



&lt;p&gt;If you work with physical quantities in Swift — whether it's scientific computing, IoT sensor data, robotics, game physics, or just making sure your app doesn't confuse kilometers with miles — give &lt;strong&gt;&lt;a href="https://github.com/moriturus/SystemeInternational" rel="noopener noreferrer"&gt;SystemeInternational&lt;/a&gt;&lt;/strong&gt; a look.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/moriturus/SystemeInternational" rel="noopener noreferrer"&gt;Star the repo on GitHub&lt;/a&gt;&lt;/strong&gt; if you think the type system should catch your physics bugs.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/moriturus" rel="noopener noreferrer"&gt;
        moriturus
      &lt;/a&gt; / &lt;a href="https://github.com/moriturus/SystemeInternational" rel="noopener noreferrer"&gt;
        SystemeInternational
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      SI Units for Swift: Strongly typed, static type safe, (almost) zero-cost abstruction
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SystemeInternational&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A type-safe SI unit system for Swift — catch unit misuse at compile time, not at runtime.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ee65a0aadb5741c45f50418a38e188ab06c09df9a9ce286de587806934c83f3c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53776966742d362e322d4630353133383f6c6f676f3d7377696674266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/ee65a0aadb5741c45f50418a38e188ab06c09df9a9ce286de587806934c83f3c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53776966742d362e322d4630353133383f6c6f676f3d7377696674266c6f676f436f6c6f723d7768697465" alt="Swift 6.2"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/53aec1ae7394521e2af38df6c1560d97fcb8152f5edd45d87563432eed72bf7a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/53aec1ae7394521e2af38df6c1560d97fcb8152f5edd45d87563432eed72bf7a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c7565" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;SystemeInternational&lt;/code&gt; models physical quantities, units, and dimensions using Swift's strong type system. Units are marker types — you never instantiate &lt;code&gt;Meter()&lt;/code&gt; directly; instead you write &lt;code&gt;Quantity&amp;lt;Double, Meter, Linear&amp;gt;&lt;/code&gt; or refer to &lt;code&gt;Meter.self&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Key characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different physical meanings are different types — no raw &lt;code&gt;Double&lt;/code&gt; or &lt;code&gt;typealias&lt;/code&gt; ambiguity&lt;/li&gt;
&lt;li&gt;Unit conversions are type-checked and use exact rational scale metadata&lt;/li&gt;
&lt;li&gt;Named SI derived units with distinct semantics (&lt;code&gt;Hertz&lt;/code&gt; vs &lt;code&gt;Becquerel&lt;/code&gt;) are never implicitly convertible&lt;/li&gt;
&lt;li&gt;Affine-space algebra distinguishes absolute positions (points) from intervals (vectors) at compile time&lt;/li&gt;
&lt;li&gt;Temperature uses the same unified &lt;code&gt;Quantity&lt;/code&gt; type with a &lt;code&gt;Space&lt;/code&gt; parameter, not a separate type hierarchy&lt;/li&gt;
&lt;li&gt;Foundation &lt;code&gt;Measurement&lt;/code&gt; interoperability is available through dedicated compatibility modules&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Swift 6.2+&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Add the package to your &lt;code&gt;Package.swift&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-swift notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;dependencies:&lt;/span&gt; &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;url&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;https://github.com/moriturus/SystemeInternational.git&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/moriturus/SystemeInternational" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; To our friends still measuring things in feet, inches, pounds, ounces, fluid ounces (US), fluid ounces (UK), short tons, long tons, nautical miles, statute miles, furlongs, and—my personal favorite—&lt;em&gt;slugs&lt;/em&gt;: the year is 2026. The rest of the world moved on. Even the UK went metric (mostly). Even NASA went metric (after &lt;em&gt;that&lt;/em&gt; incident). This library does not, and will never, include &lt;code&gt;Foot&lt;/code&gt;, &lt;code&gt;Slug&lt;/code&gt;, or &lt;code&gt;Hogshead&lt;/code&gt;. You know where to find &lt;code&gt;Foundation.Measurement&lt;/code&gt; — it's right there, waiting, with all 14 of your competing gallon definitions. 🫡&lt;/p&gt;

</description>
      <category>swift</category>
      <category>programming</category>
      <category>opensource</category>
      <category>types</category>
    </item>
    <item>
      <title>Ktra: Your Little Cargo Registry</title>
      <dc:creator>Henrique Sasaki Yuya</dc:creator>
      <pubDate>Thu, 22 Oct 2020 16:38:11 +0000</pubDate>
      <link>https://dev.to/moriturus/ktra-your-little-cargo-registry-bo2</link>
      <guid>https://dev.to/moriturus/ktra-your-little-cargo-registry-bo2</guid>
      <description>&lt;p&gt;Today, I introduce an application to provide a private cargo registry, &lt;a href="https://github.com/moriturus/ktra"&gt;&lt;code&gt;Ktra&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ktra&lt;/code&gt; is a minimum implementation of an &lt;a href="https://doc.rust-lang.org/cargo/reference/registries.html"&gt;&lt;em&gt;Alternate Registry&lt;/em&gt;&lt;/a&gt; that is introduced for non-public crates in Rust/Cargo 1.34.  &lt;/p&gt;

&lt;p&gt;This app is under heavy development so any contributions and/or feature requests are welcome!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cargo</category>
      <category>registry</category>
    </item>
  </channel>
</rss>
