<?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>Forem: HichemTech</title>
    <description>The latest articles on Forem by HichemTech (@hichemtab-tech).</description>
    <link>https://forem.com/hichemtab-tech</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%2F1146656%2F4892f156-9a42-43e5-bfa2-fb0aa057a1b4.jpeg</url>
      <title>Forem: HichemTech</title>
      <link>https://forem.com/hichemtab-tech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hichemtab-tech"/>
    <language>en</language>
    <item>
      <title>🚨If You Have an npm Package, Read This Before November 2025</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Sun, 12 Oct 2025 22:16:25 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/if-you-have-an-npm-package-read-this-before-november-2025-525h</link>
      <guid>https://forem.com/hichemtab-tech/if-you-have-an-npm-package-read-this-before-november-2025-525h</guid>
      <description>&lt;p&gt;In case you missed it, &lt;strong&gt;GitHub just announced a major security update for npm&lt;/strong&gt; that will start rolling out this October and finish by &lt;strong&gt;mid-November 2025&lt;/strong&gt;.&lt;br&gt;
If you maintain or publish npm packages (even small ones) this update &lt;strong&gt;will affect you&lt;/strong&gt;. And if you ignore it, your next CI/CD publish might &lt;strong&gt;suddenly stop working&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Happening
&lt;/h2&gt;

&lt;p&gt;GitHub (which owns npm) is tightening authentication rules to protect the ecosystem from supply-chain attacks.&lt;br&gt;
The update has &lt;strong&gt;three main parts&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Shorter token lifetimes&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;All new granular tokens will &lt;strong&gt;expire after 7 days by default&lt;/strong&gt; (down from 30).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;maximum lifetime is now 90 days&lt;/strong&gt; — previously it was unlimited :/.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Classic tokens are going away&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Over the next five weeks, &lt;strong&gt;all legacy classic tokens will be revoked&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You won’t be able to generate them anymore.&lt;/li&gt;
&lt;li&gt;They lacked granular permissions and were considered high-risk if compromised.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TOTP 2FA is being phased out&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;You won’t be able to configure new TOTP-based two-factor setups.&lt;/li&gt;
&lt;li&gt;GitHub is moving toward &lt;strong&gt;WebAuthn / passkeys&lt;/strong&gt; for stronger, phishing-resistant authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By &lt;strong&gt;mid-November 2025&lt;/strong&gt;, the old world of “one permanent token forever” will be gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why These Changes Matter
&lt;/h2&gt;

&lt;p&gt;In the past year, npm faced an uncomfortable wave of &lt;strong&gt;phishing attacks and compromised packages&lt;/strong&gt;.&lt;br&gt;
Several maintainers received realistic emails tricking them into giving up credentials, leading to &lt;strong&gt;malicious updates pushed to popular libraries&lt;/strong&gt; through post-install scripts.&lt;br&gt;
At one point, we saw &lt;strong&gt;three major incidents in a single week in past september 2025&lt;/strong&gt; — a clear sign that npm’s token system needed an upgrade.&lt;/p&gt;

&lt;p&gt;Long-lived tokens were an easy entry point for attackers.&lt;br&gt;
By forcing expiration and rotation, GitHub is limiting the damage a stolen token can cause.&lt;br&gt;
This is a necessary evolution, even if it means extra friction for us developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Need to Do (Before November 2025)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Stop using classic tokens&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Generate new &lt;strong&gt;granular access tokens&lt;/strong&gt; with scoped permissions.&lt;/li&gt;
&lt;li&gt;Update your CI/CD pipelines, GitHub Actions secrets, and local &lt;code&gt;.npmrc&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Plan for token rotation&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Tokens now live for &lt;strong&gt;a maximum of 90 days&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you publish often, schedule rotation reminders — Google Calendar, Notion, whatever keeps you sane.&lt;/li&gt;
&lt;li&gt;Don’t wait until your GitHub Actions fail mid-deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Consider trusted publishing (OIDC)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions and GitLab CI already support it.&lt;/li&gt;
&lt;li&gt;No token rotation, better provenance, simpler security.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Stay realistic about grouping tokens&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;You &lt;em&gt;can&lt;/em&gt; group related packages or an organization under one token to reduce clutter,
 but avoid sharing tokens too broadly — &lt;strong&gt;compromise in one repo means risk for all&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;This isn’t just about compliance — it’s about modernizing npm’s security foundation.&lt;br&gt;
Expect more &lt;strong&gt;short-lived credentials&lt;/strong&gt;, &lt;strong&gt;WebAuthn enforcement&lt;/strong&gt;, and &lt;strong&gt;OIDC-based publishing&lt;/strong&gt; in the future.&lt;/p&gt;

&lt;p&gt;We’re moving toward a world where &lt;strong&gt;tokens won’t live long enough to be stolen&lt;/strong&gt;, and where publishing relies on &lt;strong&gt;temporary, verifiable credentials&lt;/strong&gt; from your CI provider.&lt;br&gt;
Yes, it’s one more thing to manage — but it’s also the thing keeping millions of developers safe.&lt;/p&gt;




&lt;p&gt;These new npm rules might feel strict, but they’re long overdue — and the sooner you adapt, the smoother your builds will be when November hits.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>npm</category>
    </item>
    <item>
      <title>Shared States in React Have Never Been Easier ;)</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Fri, 29 Aug 2025 20:01:40 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/shared-states-in-react-have-never-been-easier--2m6p</link>
      <guid>https://forem.com/hichemtab-tech/shared-states-in-react-have-never-been-easier--2m6p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotd5xb9907p530b0wr5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotd5xb9907p530b0wr5i.png" alt="react-share-states" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Imagine This Situation…
&lt;/h2&gt;

&lt;p&gt;You’re building a React app. You’ve got a counter, a user object, maybe a Firebase subscription. You think, &lt;em&gt;hey I’ll just throw &lt;code&gt;useState&lt;/code&gt; in there and I’m good.&lt;/em&gt; And then it hits you: &lt;em&gt;what if I need this same state in another component?&lt;/em&gt; 😱&lt;/p&gt;

&lt;p&gt;Options today?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context → wrap your whole &lt;code&gt;App.tsx&lt;/code&gt; just to share one boolean. Boring. Annoying. Overkill.&lt;/li&gt;
&lt;li&gt;Redux → congratulations, you just added 100 lines of boilerplate for 1 variable.&lt;/li&gt;
&lt;li&gt;Zustand, Jotai, etc. → cool, but still feels like setting up a store for something that could be a line of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what if I told you that you can have a &lt;strong&gt;shared state in React&lt;/strong&gt; with almost the same simplicity as &lt;code&gt;useState&lt;/code&gt;? No providers if you don’t want them, no reducers, no context wrappers, just a tiny hook that feels natural.&lt;/p&gt;

&lt;p&gt;That’s what I built: &lt;strong&gt;&lt;code&gt;react-shared-states&lt;/code&gt;&lt;/strong&gt;. 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check it here: &lt;a href="https://github.com/HichemTab-tech/react-shared-states" rel="noopener noreferrer"&gt;react-shared-states&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Example: Two Components, One Counter 🕹️
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useSharedState&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;react-shared-states&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;A: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;B&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;B: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&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;Click on &lt;strong&gt;A&lt;/strong&gt;’s button → &lt;strong&gt;B updates instantly&lt;/strong&gt;. They’re synced because they share the same key (&lt;code&gt;"counter"&lt;/code&gt;). That’s it. No Provider, no boilerplate, no context-hating rants.&lt;/p&gt;

&lt;p&gt;This is the heart of the package: &lt;strong&gt;shared states that feel like normal states.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  How Did This Madness Start? 👀
&lt;/h3&gt;

&lt;p&gt;Story time. A friend of mine was using Firebase and came across a package called &lt;code&gt;useBetween&lt;/code&gt;. He loved it: it allowed him to call a hook once, and then reuse the data everywhere. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;useUsersData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for his Firebase use case—load users once, and then just consume them anywhere. He was happy. Until he wasn’t.&lt;/p&gt;

&lt;p&gt;He merged his branch and suddenly &lt;strong&gt;pnpm peer dependency errors everywhere&lt;/strong&gt;. His branch was on React 18, but the main repo was updated to React 19. He tried updating, but nothing worked. Why? Because &lt;code&gt;useBetween&lt;/code&gt; was abandoned. Even worse, it was &lt;strong&gt;using React internals that React 19 removed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One infamous line from the &lt;a href="https://github.com/betula/use-between/blob/ddd50040c1e97a45bd2f809f30e209b7401ab2dc/src/lib/react-shared-internals.ts#L13" rel="noopener noreferrer"&gt;source code&lt;/a&gt;:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ReactSharedInternals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, you read that right. The author literally accessed React’s private internals and based the whole package on it. 🚨&lt;/p&gt;

&lt;p&gt;My friend had no choice but to throw it away and refactor everything back to… Redux. LMAO. XDD. Can you imagine being forced back into reducers and actions after tasting freedom?&lt;/p&gt;

&lt;p&gt;That’s when I thought: &lt;em&gt;Why can’t we have something like &lt;code&gt;useBetween&lt;/code&gt;, but done properly?&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The Secret Weapon: &lt;code&gt;useSyncExternalStore&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;React actually gives us the right tool for this: &lt;strong&gt;&lt;code&gt;useSyncExternalStore&lt;/code&gt;&lt;/strong&gt;. Unlike &lt;code&gt;useBetween&lt;/code&gt;, which broke React’s rules, this hook is the official API to subscribe to external stores.&lt;/p&gt;

&lt;p&gt;Let me show you a tiny demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt; &lt;span class="o"&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;subscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&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;getSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;counter&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;setCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;l&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Counter&lt;/span&gt;&lt;span class="p"&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSyncExternalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSnapshot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking the button updates the external counter, notifies all listeners, and React re-renders. It’s just like &lt;code&gt;useState&lt;/code&gt;, but the state lives outside React. That’s the magic: &lt;strong&gt;shared state, officially supported.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Scopes: Not Everything Should Be Global 🌍
&lt;/h3&gt;

&lt;p&gt;Sometimes, you don’t want everything to be shared globally. That’s where &lt;strong&gt;scopes&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;useSharedState&lt;/code&gt; uses the &lt;strong&gt;global scope&lt;/strong&gt;. But you can isolate parts of your app using &lt;code&gt;SharedStatesProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this provider, all shared states are local to that scope. Want to get fancy? You can even give a provider a &lt;strong&gt;name&lt;/strong&gt; so multiple trees (like a modal in a portal) can share the same scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt; &lt;span class="na"&gt;scopeName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ModalContent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Portal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt; &lt;span class="na"&gt;scopeName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FloatingToolbar&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SharedStatesProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Portal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your modal and toolbar are in sync, even though they’re in different React trees without having one big context provider. Clean, simple, and powerful.&lt;/p&gt;




&lt;h3&gt;
  
  
  Async Goodness: Shared Functions ⚡
&lt;/h3&gt;

&lt;p&gt;What if your shared state comes from a fetch? Easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;current-user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="nf"&gt;trigger&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call &lt;code&gt;trigger()&lt;/code&gt; once, and the result is cached and shared everywhere. Another component using the same key (&lt;code&gt;"current-user"&lt;/code&gt;) gets the result instantly, without refetching. That’s &lt;strong&gt;automatic caching and deduplication&lt;/strong&gt; out of the box.&lt;/p&gt;




&lt;h3&gt;
  
  
  Subscriptions: Firestore, Sockets, You Name It 🔥
&lt;/h3&gt;

&lt;p&gt;Realtime data? &lt;code&gt;useSharedSubscription&lt;/code&gt; is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSharedSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-123&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="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onComplete&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&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;123&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;snap&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onComplete&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;unsub&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Connecting...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first component sets up the subscription, others just “tap in” and get updates instantly. When no component uses it anymore, it auto-unsubscribes. No duplicate sockets, no memory leaks. 🙌&lt;/p&gt;




&lt;h3&gt;
  
  
  Static API: Control from Outside React 🛠️
&lt;/h3&gt;

&lt;p&gt;Sometimes you want to set a value or trigger a fetch without being inside a component. That’s why the library exposes a static API:&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;sharedStatesApi&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;react-shared-states&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;sharedStatesApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&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;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sharedStatesApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great for SSR, debugging, or triggering updates from non-React code.&lt;/p&gt;




&lt;h3&gt;
  
  
  Wrapping Up 🎁
&lt;/h3&gt;

&lt;p&gt;So that’s the story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My friend tried &lt;code&gt;useBetween&lt;/code&gt;, it broke on React 19 because it hacked private internals.&lt;/li&gt;
&lt;li&gt;I thought: why not do the same idea, but &lt;em&gt;properly&lt;/em&gt;, using &lt;code&gt;useSyncExternalStore&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;After experimenting, I built &lt;code&gt;react-shared-states&lt;/code&gt; → a library that gives you shared states, scopes, async functions, subscriptions, and even static APIs, all with React’s blessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hate boilerplate, if you hate wrapping your app with context providers for tiny values, or if you just want something that &lt;em&gt;works&lt;/em&gt;—you’ll love this package.&lt;/p&gt;

&lt;p&gt;Because honestly… &lt;strong&gt;shared states in React have never been this easy, or this fun.&lt;/strong&gt; 🚀&lt;/p&gt;




&lt;p&gt;👉 Check it out: &lt;a href="https://github.com/HichemTab-tech/react-shared-states" rel="noopener noreferrer"&gt;github.com/HichemTab-tech/react-shared-states&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>programming</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>When Composer Met pnpm: The Birth of Pomposer</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Mon, 07 Jul 2025 03:03:27 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/when-composer-met-pnpm-the-birth-of-pomposer-2igj</link>
      <guid>https://forem.com/hichemtab-tech/when-composer-met-pnpm-the-birth-of-pomposer-2igj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filoegbxup06hois5l58i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filoegbxup06hois5l58i.png" alt="Pomposer logo" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was a typical Saturday night, coding away while the rest of the world was probably out having fun or sleeping. Yep, that's me, and if you're reading this, it's probably you too. I was deep into one of my side projects, building an open-source JavaScript tool (you'll probably see it soon, stay tuned! 😉), when I casually hit &lt;code&gt;pnpm install&lt;/code&gt;. And, whoosh! Installation completed instantly. No surprise there, right? That's pnpm doing its magic—sharing packages globally, optimizing storage, and making installs lightning fast.&lt;/p&gt;

&lt;p&gt;Right after that, I switched gears to a Composer project (my little side project had both backend and frontend). Out of habit, I typed &lt;code&gt;composer install&lt;/code&gt; and waited. And waited some more. Composer downloaded everything fresh, even packages I've installed dozens of times. That's when the idea hit me, why doesn't Composer have pnpm's awesome feature?&lt;/p&gt;

&lt;p&gt;Alert! 🚨 My brain had officially woken up.&lt;/p&gt;

&lt;p&gt;If you know me, you know I love building things out of pure curiosity and the moment's need. Remember my previous adventures like &lt;a href="https://dev.to/hichemtab-tech/beyond-type-safety-making-typescript-smarter-by-building-a-runtime-picker-26d5"&gt;ts-runtime-picker&lt;/a&gt;, or when &lt;a href="https://dev.to/hichemtab-tech/how-i-accidentally-found-myself-rewriting-laravel-just-to-fix-laravel-modules--44k0"&gt;I almost rewrote half of Laravel just to fix my personal issue&lt;/a&gt;? Yeah, I get carried away sometimes, but hey, it's all part of the fun (you should check out those stories sometime).&lt;/p&gt;

&lt;p&gt;Anyway, back to my Saturday night realization. I decided right then and there, why not build "pnpm for Composer"? Wait, if npm has pnpm, then Composer should have… &lt;strong&gt;Pomposer&lt;/strong&gt;! 😄 Yes, I actually loved that name and ran with it immediately.&lt;/p&gt;

&lt;p&gt;Terminal opened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir pomposer &amp;amp;&amp;amp; composer init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the staring at my blank sketchpad. How do we make this happen?&lt;/p&gt;

&lt;p&gt;First obvious idea: install packages once in a global folder, let's call it &lt;code&gt;~/.pomposer-store&lt;/code&gt;. Easy right? I quickly tried creating a dedicated folder for each dependency, running &lt;code&gt;composer require&lt;/code&gt; individually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/.pomposer-store
mkdir monolog/monolog &amp;amp;&amp;amp; cd monolog/monolog
composer require monolog/monolog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yay! Wait… no. 🤔 It installed sub-dependencies too. That's not what I wanted at all. Dependency hell was looming. And to be honest, it was frustrating. How do we get Composer to just install a single package?&lt;/p&gt;

&lt;p&gt;I tried different strategies, maybe I could parse the &lt;code&gt;composer.lock&lt;/code&gt; file manually and selectively install dependencies? Nah, we don't always have the composer.lock. What about using Composer's internal APIs? Nope, Composer stubbornly refuses to install just one package without dragging along its friends.&lt;/p&gt;

&lt;p&gt;Next idea: cloning repositories directly from GitHub? It sounded brilliant at first, but then I realized not every package was on GitHub :"), and managing different git references became messy super fast.&lt;/p&gt;

&lt;p&gt;I spent several hours, probably way too many cups of coffee deep, contemplating how Composer works under the hood. After all that caffeine-fueled brainstorming, it finally struck me.. what if we just download packages directly from Packagist as ZIP files? Direct, simple, no Composer command involved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://repo.packagist.org/p2/monolog/monolog.json"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_get_contents&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$zipUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'packages'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'monolog/monolog'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'dist'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"monolog.zip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$zipUrl&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow! It worked perfectly. Packages downloaded separately, no dependency mess. But then, another problem—how do we handle autoloading? Composer generates nice autoload files for us, but now we're doing it manually.&lt;/p&gt;

&lt;p&gt;Another round of brainstorming commenced. Regex parsing adventures, many error messages, and a lot more coffee later, the solution slowly emerged—I could read each package's &lt;code&gt;composer.json&lt;/code&gt; to generate a unified autoloader dynamically. It wasn't ideal, but it worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// autoload_psr4.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'Monolog\\'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'~/.pomposer-store/monolog/monolog/src/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// other packages&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà! It worked. 😍 Here's a working example:&lt;/p&gt;

&lt;p&gt;Create a simple project with a &lt;code&gt;composer.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"monolog/monolog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hichemtab-tech/namecrement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Pomposer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pomposer install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a quick test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="s1"&gt;'vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HichemTabTech\Namecrement\Namecrement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Monolog\Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Monolog\Handler\StreamHandler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'file (1)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'file (2)'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$newName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Namecrement&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;namecrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Next unique file name after the list of existing files:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Existing files: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"New file name: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$newName&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pomposer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php://stdout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pomposer is alive! 🚀'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;php index.php&lt;/code&gt; outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next unique file name after the list of existing files:
Existing files: file, file (1), file (2)
New file name: file (3)

[2025-07-06 20:31:22] pomposer.INFO: Pomposer is alive! 🚀 []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, Pomposer lives! But hold on, we're still in super-early beta. It has no Composer plugin support, no dev dependencies yet, no scripts or fancy features—it's just a proof-of-concept.&lt;/p&gt;

&lt;p&gt;But hey, it works—and that's pretty cool, right? 😅&lt;/p&gt;

&lt;p&gt;Building Pomposer has been a fantastic challenge filled with both frustrating and satisfying moments. It's not just another Composer wrapper—it's a completely new take on package management inspired by pnpm. And yes, it's rough, but that's the beauty of open source.&lt;/p&gt;

&lt;p&gt;If this excites you and you'd like to see Pomposer evolve, contribute, or share ideas, please join us on &lt;a href="https://github.com/HichemTab-tech/pomposer" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. I can't wait to hear your thoughts and ideas!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>php</category>
    </item>
    <item>
      <title>How I Accidentally Found Myself Rewriting Laravel (Just to Fix Laravel Modules 😅)</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Mon, 10 Mar 2025 15:15:09 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/how-i-accidentally-found-myself-rewriting-laravel-just-to-fix-laravel-modules--44k0</link>
      <guid>https://forem.com/hichemtab-tech/how-i-accidentally-found-myself-rewriting-laravel-just-to-fix-laravel-modules--44k0</guid>
      <description>&lt;p&gt;Alright, buckle up folks, because this is the tragic tale of how I &lt;em&gt;thought&lt;/em&gt; I was fixing Laravel Modules… only to realize I'd have to rewrite Laravel itself. 🙃  &lt;/p&gt;

&lt;p&gt;Did I succeed? Nope.&lt;br&gt;&lt;br&gt;
Did I at least break something? Oh, absolutely.&lt;br&gt;&lt;br&gt;
Did I still release a package out of it? You bet. 😎  &lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Beginning: A New Project 🌟&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So here I am, about to start a new project at my company—kind of a big one. And since I’m the most experienced with Laravel (not the &lt;em&gt;boss dev&lt;/em&gt; lol, just the &lt;em&gt;Laravel wizard&lt;/em&gt; 🧙‍♂️), I had the &lt;em&gt;honor&lt;/em&gt; of setting up the project structure.  &lt;/p&gt;

&lt;p&gt;Now, you might ask, "Why Laravel if the team isn't fully experienced?" Well, actually, they know it—but let's say I'm a bit ahead of them. Plus, honestly, Laravel is just amazing for quick, flexible, easy development. (If you're still thinking, "eeeh PHP nvm," no—get your ass on the computer, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project laravel/laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try it. &lt;em&gt;Try ittttt&lt;/em&gt;. You'll thank me later. 😏  &lt;/p&gt;

&lt;p&gt;Anyway, back to the story. So I start preparing our setup and, of course, &lt;strong&gt;Laravel Modules&lt;/strong&gt; comes to mind. Perfect for modular architecture, right?  &lt;/p&gt;

&lt;p&gt;Only... I hadn’t used it in a while. And I totally forgot about the &lt;strong&gt;artisan issue&lt;/strong&gt;. 😭  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Artisan Problem: Oh No, Here We Go Again&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First things first: I open my console and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravelfs new alpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Alpha is the fake project name; I can’t share the real one, so let’s just pretend it’s some next-gen AI startup or whatever.)&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Oh, and by the way, &lt;code&gt;laravelfs&lt;/code&gt; is my own Laravel installer (&lt;a href="https://github.com/HichemTab-tech/LaravelFS" rel="noopener noreferrer"&gt;LaravelFS&lt;/a&gt;)! 🚀 I open-sourced it because Laravel 12 removed Breeze, and I was like, *"Oh hell no!"&lt;/em&gt; So I made my own installer to fix that and add some cool features. You should check it out! [&lt;a href="https://github.com/HichemTab-tech/LaravelFS" rel="noopener noreferrer"&gt;LaravelFS&lt;/a&gt;]) and the &lt;a href="https://dev.to/hichemtab-tech/laravel-12-removed-breeze-and-jetstream-from-the-installer-so-i-built-laravelfs-3beo"&gt;story behind&lt;/a&gt; :  &lt;/p&gt;

&lt;p&gt;I picked "none starter," meaning just pure Blade. Why not Inertia? Well, for this scale, Blade feels safer. No big risks, just flexibility.&lt;/p&gt;

&lt;p&gt;So, with the project ready, I opened Google and searched "Laravel Modules" (yep, I forgot the vendor name, haha). Nice! The website updated since last time I used it (it was v6, now v12—mostly compatibility changes, but still cool). I quickly typed the usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require nwidart/laravel-modules
composer dump-autoload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything’s great! 🎉  &lt;/p&gt;

&lt;p&gt;Time to create our first module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan module:make IOT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom, module created! So easy, right? Next step: creating a model called Device. Without thinking, I went:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:model Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❌ ERROR? No. But... &lt;strong&gt;wrong path.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Instead of going into &lt;code&gt;Modules/IOT/Models/Device.php&lt;/code&gt;, it gets created in &lt;strong&gt;&lt;code&gt;App\Models\Device.php&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Wait... hold on, oh damn! 🤦‍♂️ It hit me immediately. This command places the model in \App\Models, not the module I created. How did I forget this?! Right, Artisan doesn’t care about Laravel Modules.* &lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Annoyance: Long Commands &amp;amp; No Third-Party Support&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The right command was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan module:make-model IOT Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yeah, instead of just using Laravel’s built-in &lt;code&gt;make:model&lt;/code&gt;, I now have to &lt;strong&gt;specify the module name every single time&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Which is already annoying, &lt;em&gt;but wait, there’s more!&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Not all Artisan commands have a "module" version.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;Third-party package commands won’t work inside modules.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;Spatie packages? Say goodbye to easy module integration.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;At this moment, my genius brain (remember that &lt;a href="https://dev.to/hichemtab-tech/beyond-type-safety-making-typescript-smarter-by-building-a-runtime-picker-26d5"&gt;ts-runtime-picker&lt;/a&gt; story? Yeah, genius mode activated again 😅) woke up and said, "Hichem, why can't you fix this? No one ever thought of it before, right?"&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;spoiler: no, you won’t, Hichem.&lt;/em&gt; 😂).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(&lt;strong&gt;Hichem&lt;/strong&gt; is my name btw, no shit sharlok :D).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;The Overconfident Attempt: Let’s Hack Artisan! 😎&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So here's the deal: the real issue is Artisan commands run only from Laravel's root, So I had this &lt;em&gt;brilliant&lt;/em&gt; idea:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;"What if I make Artisan work everywhere, dynamically detecting the module?"&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;💡 Genius, right?! (Totally never been done before, right? Haha. Ha.)  &lt;/p&gt;

&lt;p&gt;I didn’t even test the theory—I just dove straight in and started coding.  &lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Introducing… Artisan Everywhere!&lt;/strong&gt; 🚀 &lt;em&gt;(Yes, another package. Because why not? 😂)&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Opened my console, typed &lt;code&gt;composer init&lt;/code&gt; and created the pacakge (hichemtab-tech/artisan-everywhere—epic naming skills, I know 😎).&lt;/p&gt;

&lt;p&gt;Opened a new PhpStorm window, created a &lt;code&gt;bin&lt;/code&gt; folder, and inside it, an &lt;code&gt;artisan.php&lt;/code&gt;. At first, I tried to make a batch file (don't ask why, haha), but PHP made more sense for Composer.&lt;/p&gt;

&lt;p&gt;What my script did was genius:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It caught any "artisan" command you typed.&lt;/li&gt;
&lt;li&gt;Then, it searched upward through your directories, found the root artisan file, and executed commands from anywhere inside the project—no more annoying &lt;code&gt;cd ../../../..&lt;/code&gt;. Genius, right?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the script I wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Global Artisan Wrapper - Artisan Everywhere
 *
 * This script searches upward from the current working directory
 * for a local "artisan" file. When found, it delegates all command-line
 * arguments to that artisan file.
 */&lt;/span&gt;

&lt;span class="c1"&gt;// Get the current working directory.&lt;/span&gt;
&lt;span class="nv"&gt;$cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$foundArtisan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$artisanPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Traverse up the directory hierarchy looking for a local artisan file.&lt;/span&gt;
&lt;span class="k"&gt;while&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="nv"&gt;$possibleArtisan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cwd&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="no"&gt;DIRECTORY_SEPARATOR&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'artisan'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$possibleArtisan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$possibleArtisan&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$foundArtisan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$artisanPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$possibleArtisan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If we've reached the root directory, stop.&lt;/span&gt;
    &lt;span class="nv"&gt;$parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cwd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$parent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$cwd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$parent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$foundArtisan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"No local artisan file found in the directory hierarchy.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;exit&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Capture command-line arguments (excluding the script name).&lt;/span&gt;
&lt;span class="nv"&gt;$arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$argv&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="c1"&gt;// Escape each argument to safely pass them to the shell.&lt;/span&gt;
&lt;span class="nv"&gt;$escapedArguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'escapeshellarg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$argumentsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$escapedArguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Prepare the command.&lt;/span&gt;
&lt;span class="c1"&gt;// If the artisan file is executable, run it directly; otherwise, use PHP.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_executable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$artisanPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;escapeshellcmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$artisanPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$argumentsString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'php '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;escapeshellarg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$artisanPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$argumentsString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Optionally, you can enable a debug output to see the final command.&lt;/span&gt;
&lt;span class="c1"&gt;// fwrite(STDERR, "Executing command: $command\n");&lt;/span&gt;

&lt;span class="c1"&gt;// Execute the command, relaying its output and exit status.&lt;/span&gt;
&lt;span class="nb"&gt;passthru&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$returnVar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$returnVar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🥳 🎉 &lt;strong&gt;IT WORKED!&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Now I could run Artisan commands from anywhere in my project. &lt;strong&gt;No more &lt;code&gt;cd ../../../&lt;/code&gt; nonsense!&lt;/strong&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The “Oh No” Moment: Laravel’s Internal Paths :))))&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Super hyped, I went to my project and ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Modules/IOT
artisan make:model Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  Model [C:\xampp\htdocs\alpha\app\Models\Device.php] created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Wait...&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;WHAT?!! 😳  &lt;/p&gt;

&lt;p&gt;Why did it go into &lt;code&gt;app/Models/&lt;/code&gt; instead of &lt;code&gt;Modules/IOT/Models/&lt;/code&gt;?!  &lt;/p&gt;

&lt;p&gt;And then it hit me.  &lt;/p&gt;

&lt;p&gt;💀 &lt;strong&gt;Laravel doesn’t let you change the base path for Artisan commands.&lt;/strong&gt; 💀  &lt;/p&gt;

&lt;p&gt;Turns out, when you run &lt;code&gt;make:model&lt;/code&gt;, Laravel internally &lt;strong&gt;hardcodes&lt;/strong&gt; the &lt;code&gt;app/&lt;/code&gt; path. It &lt;strong&gt;does not&lt;/strong&gt; dynamically detect modules.  &lt;/p&gt;

&lt;p&gt;Here’s the problem code inside &lt;code&gt;Illuminate\Console\GeneratorCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;replaceFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rootNamespace&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'path'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\\'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.php'&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;So even though I could now run &lt;code&gt;artisan&lt;/code&gt; commands from anywhere…&lt;br&gt;&lt;br&gt;
🥲 &lt;strong&gt;Artisan would still default to &lt;code&gt;app/Models/&lt;/code&gt;.&lt;/strong&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Conclusion: I Gave Up. 😂&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I had two choices:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rewrite &lt;strong&gt;a ton&lt;/strong&gt; of Laravel’s internal command logic.
&lt;/li&gt;
&lt;li&gt;Accept defeat and keep using &lt;code&gt;php artisan module:make-model&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;😂 I took &lt;strong&gt;Option 2&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;No way Taylor Otwell is accepting a PR that modifies the entire Artisan command structure.  &lt;/p&gt;

&lt;p&gt;So yeah, I &lt;strong&gt;failed&lt;/strong&gt;. BUT! At least I made &lt;strong&gt;Artisan Everywhere&lt;/strong&gt;, which is still useful. 🎉  &lt;/p&gt;

&lt;p&gt;And hey, if anyone actually &lt;strong&gt;solves&lt;/strong&gt; this issue, &lt;strong&gt;please&lt;/strong&gt; let me know. I’d love to see it work. But for now… I’ll just keep manually moving files. 😭  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;That’s it. My failed attempt at rewriting Laravel.&lt;/strong&gt; Hope you enjoyed my suffering. 😂  &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Laravel 12 Removed Breeze and Jetstream from the Installer—So I Built LaravelFS 🚀</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Fri, 07 Mar 2025 19:27:08 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/laravel-12-removed-breeze-and-jetstream-from-the-installer-so-i-built-laravelfs-3beo</link>
      <guid>https://forem.com/hichemtab-tech/laravel-12-removed-breeze-and-jetstream-from-the-installer-so-i-built-laravelfs-3beo</guid>
      <description>&lt;h2&gt;
  
  
  The Laravel 12 Starter Kit Shift 🤔
&lt;/h2&gt;

&lt;p&gt;Picture this: You open your terminal, run &lt;code&gt;laravel new my-project&lt;/code&gt;, and… surprise! 🎭 The usual starter kits you relied on—&lt;strong&gt;Breeze and Jetstream&lt;/strong&gt;—are nowhere to be found. Instead, Laravel 12 now offers a different approach: &lt;strong&gt;full project templates&lt;/strong&gt; hosted on Packagist.&lt;/p&gt;

&lt;p&gt;Before Laravel 12, the official installer would set up a blank Laravel project and then install Breeze or Jetstream as a dependency, running &lt;code&gt;breeze:install&lt;/code&gt; or &lt;code&gt;jetstream:install&lt;/code&gt;. But in Laravel 12, the new starter kits aren’t installed as dependencies anymore—they’re &lt;strong&gt;predefined repositories&lt;/strong&gt;. Now, when you select a starter kit, the installer runs &lt;code&gt;composer create-project&lt;/code&gt;, fetching a full Laravel template project instead.&lt;/p&gt;

&lt;p&gt;And the kicker? 🥁 When using &lt;code&gt;laravel new&lt;/code&gt;, the suggested starter kits are &lt;strong&gt;React, Vue, and Livewire&lt;/strong&gt;. &lt;strong&gt;Breeze and Jetstream were completely removed.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Now, don't get me wrong—the new starter kits are &lt;strong&gt;awesome&lt;/strong&gt;! They come pre-configured with &lt;strong&gt;ShadCN, modern UI components, and a polished setup&lt;/strong&gt; that makes building apps even smoother. But at the same time, it &lt;strong&gt;sucks&lt;/strong&gt; that we lost Breeze and Jetstream as official options. Many developers were still using them, and now they had no direct way to install them like before. Naturally, this upset a lot of people. 😡&lt;/p&gt;




&lt;h2&gt;
  
  
  My Attempt to Fix It (And the Closed PR) 🙃
&lt;/h2&gt;

&lt;p&gt;Like many, I found this change frustrating. So, I thought: &lt;em&gt;Why not just add an option to allow users to provide their own starter templates?&lt;/em&gt; That way, people who wanted Breeze, Jetstream, or custom setups could still use them seamlessly.&lt;/p&gt;

&lt;p&gt;I opened a &lt;strong&gt;&lt;a href="https://github.com/laravel/installer/pull/399" rel="noopener noreferrer"&gt;PR&lt;/a&gt;&lt;/strong&gt; in the official Laravel installer to introduce a &lt;strong&gt;custom starter option&lt;/strong&gt;, allowing developers to specify their own Packagist-hosted templates. Simple, right? 🤷&lt;/p&gt;

&lt;p&gt;Unfortunately, my PR was &lt;strong&gt;closed by Taylor&lt;/strong&gt;. The reason? Laravel already had enough starter kits, and they couldn’t maintain more options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48prmjndjt5syamlxmoc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48prmjndjt5syamlxmoc.png" alt="Taylor's comment" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was a bit disappointed, to be honest. 😕 But hey, he’s the maintainer. He knows best, and I understood that he had a lot on his plate. 🍽️&lt;/p&gt;

&lt;p&gt;Still, I wasn't ready to drop the idea just yet. 💡&lt;/p&gt;




&lt;h2&gt;
  
  
  Building LaravelFS: A Better Laravel Installer 🏗️
&lt;/h2&gt;

&lt;p&gt;Since Laravel's official installer wasn’t going to support custom starters, I decided to &lt;strong&gt;build my own alternative&lt;/strong&gt;: &lt;strong&gt;&lt;a href="https://github.com/HichemTab-tech/LaravelFS" rel="noopener noreferrer"&gt;LaravelFS&lt;/a&gt;&lt;/strong&gt;. 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What LaravelFS Does Differently&lt;/strong&gt; 🔥
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Brings back Breeze and Jetstream&lt;/strong&gt;: You can now select them just like before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adds a custom starter option&lt;/strong&gt;: You can provide your own Packagist-hosted template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introduces a "Templates" feature&lt;/strong&gt;: Save and reuse your project setup preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How It Works&lt;/strong&gt; 🛠️
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Installing &lt;a href="https://github.com/HichemTab-tech/LaravelFS" rel="noopener noreferrer"&gt;LaravelFS&lt;/a&gt;:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer global require hichemtab-tech/laravelfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Creating a new project (with all the missing options restored!):
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravelfs new my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted to choose your starter kit, database, test library (Pest or PHPUnit), and additional options like SSR, TypeScript, and dark mode. 🌙&lt;/p&gt;

&lt;p&gt;-using The official Laravel installer : &lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forzld542nl5lmjh8kqal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forzld542nl5lmjh8kqal.png" alt="Laravel installer output" width="537" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using My installer:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx9bzw8j4sgy8jqkbk8v.png" alt="LaravelFS installer output" width="601" height="411"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Saving a setup as a template:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravelfs template:new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the same prompts as &lt;code&gt;laravelfs new&lt;/code&gt;, but instead of creating a project, it &lt;strong&gt;saves your selections as a reusable template&lt;/strong&gt;. 📝&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Using a saved template:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;laravelfs use my-template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more repeating the same prompts every time—just use your template and get started instantly! 🚀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6dfmo9fztd8r8nr2sna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6dfmo9fztd8r8nr2sna.png" alt="templates preview" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Curious Coincidence… 🤨
&lt;/h2&gt;

&lt;p&gt;Now here’s where things get interesting. &lt;/p&gt;

&lt;p&gt;Just &lt;strong&gt;a few days&lt;/strong&gt; after my PR was closed and I released LaravelFS, another contributor opened a &lt;strong&gt;&lt;a href="https://github.com/laravel/installer/pull/407" rel="noopener noreferrer"&gt;new PR&lt;/a&gt;&lt;/strong&gt; with almost the exact same idea: allowing &lt;strong&gt;custom starter kits&lt;/strong&gt; in the Laravel installer.&lt;/p&gt;

&lt;p&gt;This time, however, &lt;strong&gt;the PR was accepted&lt;/strong&gt;. Taylor even tweeted excitedly about it. 😃&lt;/p&gt;

&lt;p&gt;Now, I’m not saying anything… but it does make me wonder: &lt;strong&gt;What was different?&lt;/strong&gt; Same purpose, same need—but a different outcome. 🤷‍♂️&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: LaravelFS and Community-Driven Solutions 💡
&lt;/h2&gt;

&lt;p&gt;At the end of the day, LaravelFS exists because &lt;strong&gt;developers wanted more control&lt;/strong&gt; over how they set up their projects. 💪&lt;/p&gt;

&lt;p&gt;I don’t hold any hard feelings—maintaining an ecosystem like Laravel isn't easy, and tough decisions have to be made. But this experience was a great reminder that &lt;strong&gt;if something gets rejected, that doesn’t mean it’s a bad idea&lt;/strong&gt;. Sometimes, you just have to build it yourself. 🏗️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/HichemTab-tech/LaravelFS" rel="noopener noreferrer"&gt;LaravelFS &lt;/a&gt;is open-source, and I’d love for more people to try it out and contribute! What do you think? Have you felt the impact of Laravel 12’s starter kit changes? Let’s talk about it! 🎤🔥&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>The Day I Truly Realized I Was a Junior Developer 🤡</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Mon, 10 Feb 2025 11:43:38 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/the-day-i-truly-realized-i-was-a-junior-developer-kgp</link>
      <guid>https://forem.com/hichemtab-tech/the-day-i-truly-realized-i-was-a-junior-developer-kgp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This story is focused on the storytelling aspect of my experience. For the sake of flow and engagement, I’ve skipped detailed code explanations and some technical nuances. The goal here is to share the journey, not to provide a deep technical breakdown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction: A Lesson in Humility and TypeScript Wizardry
&lt;/h2&gt;

&lt;p&gt;Every developer has &lt;em&gt;that&lt;/em&gt; moment—the one where they think they've mastered something, only to be completely humbled by a colleague. This is the story of &lt;em&gt;my&lt;/em&gt; moment. It started with a simple goal: improving routing in a React project. It ended with me questioning my entire existence as a developer. 😅&lt;/p&gt;

&lt;p&gt;What follows is a journey filled with excitement, frustration, moments of pure dopamine-fueled breakthroughs, and ultimately, an eye-opening realization about experience, skill, and &lt;strong&gt;what it really means to be a senior dev&lt;/strong&gt;. Buckle up. 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup 🎬
&lt;/h2&gt;

&lt;p&gt;It was another day of development, another day of feeling &lt;strong&gt;like a pro&lt;/strong&gt;. I was working on a React project, using &lt;code&gt;createBrowserRouter&lt;/code&gt; to handle the routing in &lt;code&gt;Router.tsx&lt;/code&gt;. Notice the &lt;strong&gt;.tsx&lt;/strong&gt; extension? Yeah, that’s because &lt;code&gt;createBrowserRouter&lt;/code&gt; requires &lt;strong&gt;actual JSX components&lt;/strong&gt; inside &lt;code&gt;element&lt;/code&gt;. Let me show you an example of what the router looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AppLayout&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
        &lt;span class="na"&gt;children&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DashboardLayout&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ProtectedRoute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
                &lt;span class="na"&gt;children&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="na"&gt;index&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/all-projects"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;projects/:projectId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProjectGuard&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;children&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;groups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProjectGroups&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="p"&gt;},&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project-stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;groups/:groupId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GroupGuard&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;children&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GroupStats&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                                    &lt;span class="p"&gt;},&lt;/span&gt;
                                    &lt;span class="p"&gt;{&lt;/span&gt;
                                        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;consents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Consents&lt;/span&gt;&lt;span class="p"&gt;/&amp;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="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProjectStats&lt;/span&gt;&lt;span class="p"&gt;/&amp;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;fallbackRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/projects&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="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NoMatch&lt;/span&gt;&lt;span class="p"&gt;/&amp;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="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;v7_startTransition&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I &lt;strong&gt;loved&lt;/strong&gt; using &lt;code&gt;createBrowserRouter&lt;/code&gt;. It was clean, it allowed nested routes, and I highly recommend it if you’re working with React Router. But &lt;strong&gt;then the problem hit&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem 🤯
&lt;/h2&gt;

&lt;p&gt;Across the project, we were using the &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; component to navigate. Pretty standard, right? Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/projects/123/groups/456"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Go to Group&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, here’s where &lt;strong&gt;the chaos began&lt;/strong&gt;. The client requested a change in the &lt;strong&gt;routing structure&lt;/strong&gt;. Just some &lt;strong&gt;small tweaks&lt;/strong&gt; to paths, nothing major… &lt;em&gt;right?&lt;/em&gt; WRONG.&lt;/p&gt;

&lt;p&gt;Because now, &lt;strong&gt;every single manually written URL&lt;/strong&gt; in the project had to be updated. 🥲&lt;/p&gt;

&lt;p&gt;I was staring at the codebase, thinking: &lt;em&gt;Okay, this is bad. This is really bad.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There had to be a &lt;strong&gt;better way&lt;/strong&gt;—a way to validate links across the app &lt;strong&gt;without relying on hardcoded strings&lt;/strong&gt;, a way to &lt;strong&gt;autocomplete&lt;/strong&gt; routes when writing them, a way to &lt;strong&gt;ensure consistency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My &lt;strong&gt;brainstorming mode&lt;/strong&gt; activated.&lt;/p&gt;

&lt;p&gt;🔍 &lt;strong&gt;How could I make the routes smarter?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
💡 &lt;strong&gt;Could TypeScript help enforce route validation?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
🤔 &lt;strong&gt;How do I make WebStorm autocomplete route names for me?&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Then, the &lt;strong&gt;stupidest&lt;/strong&gt; but funniest idea hit me: &lt;em&gt;Should I just make a JetBrains plugin for this?&lt;/em&gt; 😂&lt;/p&gt;

&lt;p&gt;For a &lt;strong&gt;solid five minutes&lt;/strong&gt;, I was &lt;strong&gt;seriously&lt;/strong&gt; considering writing an entire JetBrains plugin &lt;strong&gt;just to solve my routing issue&lt;/strong&gt;. But then, I shook my head. &lt;em&gt;Hichem, stop. The company is not paying you to build useless plugins.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The "Brilliant" Plan 🚀
&lt;/h2&gt;

&lt;p&gt;Instead, I thought: &lt;strong&gt;let’s use TypeScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I could somehow convert route paths into &lt;strong&gt;TypeScript-enforced IDs&lt;/strong&gt;, I could:&lt;/p&gt;

&lt;p&gt;✔ Validate links.&lt;br&gt;
✔ Enable autocomplete.&lt;br&gt;
✔ Ensure routes are &lt;strong&gt;always correct&lt;/strong&gt;, no more broken links.&lt;/p&gt;

&lt;p&gt;I modified the router to &lt;strong&gt;include unique IDs&lt;/strong&gt; for each route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeObjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AppLayout&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;children&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnlyAdmin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin-projects-list&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was &lt;strong&gt;feeling like a genius.&lt;/strong&gt; But &lt;strong&gt;TypeScript wasn’t cooperating&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I needed TypeScript to &lt;strong&gt;dynamically recognize these IDs&lt;/strong&gt; as valid values. I tried using &lt;code&gt;typeof routesObject&lt;/code&gt;, but &lt;strong&gt;nope&lt;/strong&gt;, it didn’t work. After &lt;strong&gt;hours&lt;/strong&gt; of searching the TypeScript docs (not great docs, by the way), &lt;strong&gt;tons&lt;/strong&gt; of Stack Overflow pages, and &lt;strong&gt;even brainstorming with ChatGPT&lt;/strong&gt;, I was getting nowhere.&lt;/p&gt;

&lt;p&gt;That’s when the &lt;strong&gt;new idea hit&lt;/strong&gt;: What if I just &lt;strong&gt;generate a TypeScript type file dynamically&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Boom. New plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract all routes into an object.&lt;/li&gt;
&lt;li&gt;Generate a TypeScript type file dynamically.&lt;/li&gt;
&lt;li&gt;Run a script whenever routes change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run generate-router-types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which would output:&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-projects-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💥 &lt;strong&gt;Mission accomplished!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I pushed the PR, took a coffee break, and came back expecting a &lt;strong&gt;quick approval&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Joe" Moment 😐
&lt;/h2&gt;

&lt;p&gt;Then I saw &lt;strong&gt;Joe’s comment&lt;/strong&gt;. Let’s call him Joe (not his real name, but let’s roll with it).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I have a better to do this, using only typescript"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjroisg1p55xmz58ciz92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjroisg1p55xmz58ciz92.png" alt="screenshot of github PR comment" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My &lt;strong&gt;soul left my body.&lt;/strong&gt; 💀&lt;/p&gt;

&lt;p&gt;Bro, &lt;strong&gt;do you think I didn’t try that first?!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Then he messaged me directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"As-tu 15min pour discuter ta PR ?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great. A meeting. With &lt;strong&gt;Joe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Joe was &lt;strong&gt;new&lt;/strong&gt; at the company, but he had a crazy resume—Rust, Go, Node, DevOps, everything. But he also had &lt;strong&gt;that energy&lt;/strong&gt;—the kind of dev who &lt;strong&gt;always has a counterpoint to everything&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Still, I joined the call with a &lt;em&gt;whatever&lt;/em&gt; attitude. &lt;em&gt;Let’s hear this genius out.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Humiliation 🤡
&lt;/h2&gt;

&lt;p&gt;Joe shared his screen. &lt;/p&gt;

&lt;p&gt;He started explaining. &lt;/p&gt;

&lt;p&gt;I saw his code. &lt;/p&gt;

&lt;p&gt;And in that moment, I realized… &lt;strong&gt;I am, in fact, a junior.&lt;/strong&gt; 😂&lt;/p&gt;

&lt;p&gt;His solution? Pure &lt;strong&gt;TypeScript magic.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No build scripts.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No external tools.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No route extraction function.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just a few lines of TypeScript.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MergePath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractRouteType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;paramsKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExtractPathParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;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;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoute&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt; &lt;span class="nx"&gt;ExtractRouteType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;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="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoutes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;children&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoutes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteObject&lt;/span&gt;&lt;span class="p"&gt;[]]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoute&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MergePath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoutes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RouteById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtractRoutes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routeObjects&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;U&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="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&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="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;😭😭😭&lt;/p&gt;

&lt;p&gt;I had spent &lt;strong&gt;hours&lt;/strong&gt; on a solution that &lt;strong&gt;wasn’t even needed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Joe laughed and said, &lt;em&gt;"Yeah, TypeScript is really powerful when you know how to use it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yeah, &lt;strong&gt;I got schooled.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Redemption Arc 🦸‍♂️
&lt;/h2&gt;

&lt;p&gt;But instead of sulking, I &lt;strong&gt;fought back&lt;/strong&gt;. I took Joe’s TypeScript wizardry and &lt;strong&gt;improved on it&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;I thought: &lt;em&gt;If he can dynamically extract routes, why can't I also dynamically validate parameters?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wanted &lt;strong&gt;TypeScript to enforce route parameters&lt;/strong&gt;, meaning:&lt;/p&gt;

&lt;p&gt;✔ If a route requires &lt;code&gt;:projectId&lt;/code&gt;, I should be forced to pass &lt;code&gt;{ projectId: string }&lt;/code&gt; as params.&lt;br&gt;&lt;br&gt;
✔ If I forgot a param, TypeScript should &lt;strong&gt;throw an error&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✔ If I added extra params that weren’t needed, &lt;strong&gt;TypeScript should complain&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
✔ And of course, &lt;strong&gt;autocomplete should still work flawlessly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I went back to my &lt;strong&gt;battle station&lt;/strong&gt;. 💻☕&lt;/p&gt;

&lt;p&gt;I introduced &lt;code&gt;ExtractPathParams&lt;/code&gt;, a TypeScript utility to extract &lt;strong&gt;only the required parameters&lt;/strong&gt; from route paths:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractPathParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;ExtractPathParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Param&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I rewrote &lt;code&gt;buildRoute&lt;/code&gt; so that it &lt;strong&gt;refused to compile&lt;/strong&gt; if required parameters weren’t passed:&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildRoute&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RouteId&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;RouteById&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;routeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ParamsArg&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RouteId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ParamsArg&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RouteId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Route &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;params_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Route &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; contains an unresolved parameter`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;route&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;&lt;strong&gt;Boom.&lt;/strong&gt; Now TypeScript would &lt;strong&gt;scream at me&lt;/strong&gt; if I passed the wrong params! 🎯&lt;/p&gt;

&lt;p&gt;I also &lt;strong&gt;refactored &lt;code&gt;AppLink&lt;/code&gt;&lt;/strong&gt; to integrate this new validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RouteId&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;RouteById&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;: RouteBuilderProps&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RouteId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; Omit&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; "to"&amp;gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; React.RefAttributes&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HTMLAnchorElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;buildRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
export default AppLink;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran the tests. &lt;strong&gt;Everything worked.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I opened another PR. &lt;strong&gt;This time, Joe actually liked it.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;"Nice, I like this. Makes sure routes are truly type-safe. Good job."&lt;/p&gt;

&lt;p&gt;😭 &lt;strong&gt;Sweet validation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, instead of writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/projects/123/groups/456"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;View Group&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AppLink&lt;/span&gt; &lt;span class="na"&gt;routeId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"admin-project-group-consents"&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    View Group
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AppLink&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔ &lt;strong&gt;No more incorrect URLs.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✔ &lt;strong&gt;TypeScript-enforced validation.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✔ &lt;strong&gt;Autocomplete for route names &amp;amp; parameters.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✔ &lt;strong&gt;Less headache when changing paths in the future.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And just like that, the &lt;strong&gt;routing system was finally bulletproof.&lt;/strong&gt; 🔥&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lesson 🎓
&lt;/h2&gt;

&lt;p&gt;That day, I learned: &lt;strong&gt;experience actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I could’ve held a grudge. I could’ve been bitter. But instead, I &lt;strong&gt;learned, improved, and built upon the knowledge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The truth is, &lt;strong&gt;experience isn’t just about time spent coding.&lt;/strong&gt; It’s about &lt;strong&gt;how deeply you challenge yourself, how far you push your skills, and how much you truly understand your tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That PR was a &lt;strong&gt;huge lesson&lt;/strong&gt; for me. It wasn’t just about routing, or TypeScript, or Joe being a TypeScript wizard. It was about &lt;strong&gt;growth&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;So if you ever find yourself &lt;strong&gt;humbled by a better solution&lt;/strong&gt;, don’t resist it. &lt;strong&gt;Study it, build upon it, and make it your own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s what makes a truly great developer. 😎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The End.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Tailwind Without Components? You’re Doing It Wrong 🚀</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Sat, 08 Feb 2025 11:12:13 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/tailwind-without-components-youre-doing-it-wrong-51na</link>
      <guid>https://forem.com/hichemtab-tech/tailwind-without-components-youre-doing-it-wrong-51na</guid>
      <description>&lt;p&gt;💡 &lt;em&gt;Tailwind is everywhere, but are we using it the right way?&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;A lot of us have heard about Tailwind by now and are using it in every project. It’s like a trend—when something becomes popular, we all jump on it, making it a default choice for every new project.&lt;/p&gt;

&lt;p&gt;Maybe in your estimation meetings with your boss, you want to impress project managers. Or during story-point discussions, you confidently suggest Tailwind.&lt;/p&gt;

&lt;p&gt;Especially when your team starts working on an old project with legacy code, you might think, &lt;em&gt;Boom, I'll tell them to use Tailwind!&lt;/em&gt; It's fast, it's modern, and hey, &lt;em&gt;we don’t even need to write CSS files anymore, just use predefined classes and get a nice style!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But… is it really that simple? 🤔&lt;/p&gt;

&lt;p&gt;Wait a minute. Ever noticed that Tailwind is mostly introduced alongside &lt;strong&gt;React, Angular, or Vue&lt;/strong&gt;? That’s no coincidence.&lt;/p&gt;

&lt;p&gt;Ever wondered &lt;strong&gt;why&lt;/strong&gt;? It’s because these frameworks use a &lt;strong&gt;component-based architecture&lt;/strong&gt;. 🏗️&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why Tailwind Works Best with Components 🛠️
&lt;/h2&gt;

&lt;p&gt;Let’s break it down. Say you have a button that’s used throughout your project. In a &lt;strong&gt;component-based framework&lt;/strong&gt;, you would create a reusable &lt;strong&gt;Button component&lt;/strong&gt; and use it everywhere. Simple, right? Now let’s say your boss suddenly tells you, &lt;em&gt;“Hey, remove the rounded effect from all buttons. We have a client demo in 5 minutes.”&lt;/em&gt; 😨&lt;/p&gt;

&lt;p&gt;No problem! You go to your &lt;strong&gt;Button component&lt;/strong&gt;, remove the &lt;code&gt;rounded&lt;/code&gt; class, push the change, get a quick review, merge, and deploy. Maybe the entire process takes &lt;strong&gt;2 minutes&lt;/strong&gt;, easy! ✅&lt;/p&gt;

&lt;p&gt;But now, imagine a different scenario...&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Nightmare: Using Tailwind in Legacy HTML/JQuery Projects 😵‍💫
&lt;/h2&gt;

&lt;p&gt;Let’s say you're working on an old PHP project with &lt;strong&gt;no structure,&lt;/strong&gt; drowning in legacy code. Maybe it's still using &lt;strong&gt;Bootstrap 3&lt;/strong&gt; (&lt;em&gt;ouch&lt;/em&gt;). This is the kind of project where if you open it in PHPStorm, JetBrains might call you personally, saying their IDE is down because you've hit the error/warning inspection limit. 😂&lt;/p&gt;

&lt;p&gt;Now, in this &lt;em&gt;exciting&lt;/em&gt; scenario, you suggest: &lt;em&gt;“Let’s use Tailwind!”&lt;/em&gt; You bravely convince the team, everything works fine, and you even manage to &lt;strong&gt;remove all CSS files&lt;/strong&gt; while maintaining the theme. Amazing, right? 🎉&lt;/p&gt;

&lt;p&gt;Then your boss hits you with the same request: &lt;em&gt;“Remove the rounded effect from all buttons. Client demo in 5 minutes.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And that’s when you realize…&lt;/p&gt;

&lt;p&gt;🔴 &lt;strong&gt;Step 1: PANIC.&lt;/strong&gt;&lt;br&gt;
🔵 &lt;strong&gt;Step 2: Hope there’s a single button file.&lt;/strong&gt; (&lt;em&gt;There isn’t.&lt;/em&gt;) 😬&lt;br&gt;
🟡 &lt;strong&gt;Step 3: Start manually editing every button.&lt;/strong&gt; (&lt;em&gt;Cry internally.&lt;/em&gt;) 😭&lt;br&gt;
🟢 &lt;strong&gt;Step 4: Search Everywhere in JetBrains.&lt;/strong&gt; (&lt;em&gt;Why are there so many results?!&lt;/em&gt;) 🤯&lt;br&gt;
🟣 &lt;strong&gt;Step 5: Discover some buttons are dynamically created in jQuery.&lt;/strong&gt; (&lt;em&gt;Great.&lt;/em&gt;) ⏳&lt;br&gt;
🔴 &lt;strong&gt;Step 6: Accept your fate. The demo will happen with rounded buttons.&lt;/strong&gt; 😵&lt;/p&gt;

&lt;p&gt;At this point, you realize: &lt;strong&gt;There is NO way you can do this in 5 minutes.&lt;/strong&gt; Heck, even 8 hours might not be enough! 😵&lt;/p&gt;

&lt;p&gt;And that was just removing one simple &lt;code&gt;rounded&lt;/code&gt; class. Of course, it's not like the whole demo is ruined because of a rounded button—this is just an example. Now, imagine something actually important, like having to &lt;strong&gt;change an entire card layout&lt;/strong&gt; from &lt;code&gt;grid&lt;/code&gt; to &lt;code&gt;flex&lt;/code&gt;, or replacing &lt;strong&gt;all spacing values&lt;/strong&gt; across the project to match new design guidelines. Suddenly, you’re digging through countless files, manually updating class names, and hoping you didn’t miss one. Yeah... &lt;em&gt;Yikes.&lt;/em&gt; 🫠&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Takeaway: Tailwind is for Components 🎯
&lt;/h2&gt;

&lt;p&gt;So now you get it, right? &lt;strong&gt;Tailwind is meant for projects that use components.&lt;/strong&gt; If you’re working in a framework like &lt;strong&gt;React, Vue, or Svelte&lt;/strong&gt;, Tailwind is a dream because styles are encapsulated within components, making updates easy and centralized. ✨&lt;/p&gt;

&lt;p&gt;But if you’re dealing with a legacy codebase full of &lt;strong&gt;plain HTML and jQuery&lt;/strong&gt;, using Tailwind can become a &lt;strong&gt;maintenance nightmare&lt;/strong&gt;. In such cases, traditional CSS (or even a framework like Bootstrap) might be a &lt;strong&gt;better choice&lt;/strong&gt;. 👀&lt;/p&gt;

&lt;p&gt;Before using Tailwind, ask yourself: &lt;strong&gt;“Do I have a component-based architecture?”&lt;/strong&gt; If not, you might just be &lt;strong&gt;doing it wrong.&lt;/strong&gt; 😉&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Lazy Devs, Rejoice! Automate Updates with Dependabot (and My Secret Sauce) 🍹📱</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Mon, 30 Dec 2024 23:56:49 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/lazy-devs-rejoice-automate-updates-with-dependabot-and-my-secret-sauce-3i41</link>
      <guid>https://forem.com/hichemtab-tech/lazy-devs-rejoice-automate-updates-with-dependabot-and-my-secret-sauce-3i41</guid>
      <description>&lt;p&gt;Hey there, busy devs! Are you tired of endlessly scrolling through dependency update emails, manually checking for package updates, or even (&lt;em&gt;gasp&lt;/em&gt;) opening your IDE for minor tweaks? 😩 Well, fret no more because &lt;strong&gt;&lt;a href="https://github.com/dependabot" rel="noopener noreferrer"&gt;Dependabot&lt;/a&gt;&lt;/strong&gt; is here to save the day (and your sanity)! 🚀&lt;/p&gt;

&lt;p&gt;Let’s dive into why you should start using Dependabot &lt;em&gt;right now&lt;/em&gt;, how it works its magic, and how my &lt;strong&gt;&lt;a href="https://github.com/marketplace/actions/packbumppr" rel="noopener noreferrer"&gt;PackBumpPR&lt;/a&gt;&lt;/strong&gt; action complements it perfectly for packages with &lt;code&gt;package.json&lt;/code&gt;. Ready? Let’s go! 🛠️✨&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Dependabot is a Game-Changer 🦾
&lt;/h2&gt;

&lt;p&gt;Imagine this: you’re sipping your favorite drink 🍹, scrolling through your phone, and your app’s dependencies are magically kept up-to-date. Sounds like a dream, right? Well, &lt;strong&gt;Dependabot&lt;/strong&gt; makes it reality. Here’s why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stay Secure&lt;/strong&gt;: Outdated dependencies can leave your project vulnerable. Dependabot automates updates, ensuring your project stays secure with the latest patches. 🔒&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effortless Updates&lt;/strong&gt;: It regularly checks your dependencies and opens pull requests for any updates. You just review, test, and merge. Easy peasy! 🛡️&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saves Time&lt;/strong&gt;: Forget manually combing through version lists or changelogs. Dependabot handles it for you while you focus on the &lt;em&gt;fun&lt;/em&gt; stuff. 🎉&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Tests Automatically&lt;/strong&gt;: PRs from Dependabot can trigger your CI/CD pipelines, so you know if the update breaks anything &lt;em&gt;before&lt;/em&gt; merging. 👌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, it’s your invisible coding assistant, keeping your project lean, mean, and up-to-date. 🤖&lt;/p&gt;




&lt;h2&gt;
  
  
  How Dependabot Works 🛠️
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated Dependency Checks&lt;/strong&gt;:&lt;br&gt;
Dependabot scans your &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;composer.json&lt;/code&gt;, or other dependency files for updates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pull Request Creation&lt;/strong&gt;:&lt;br&gt;
Whenever there’s a new version available, it creates a pull request, including details about the update.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Your Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review the PR.&lt;/li&gt;
&lt;li&gt;Optionally run tests to ensure compatibility.&lt;/li&gt;
&lt;li&gt;Merge it with confidence.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dependabot even handles &lt;strong&gt;semver&lt;/strong&gt; ranges and will never break your project by jumping across major versions (unless you want it to). How cool is that? 😎&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with Dependabot 🚀
&lt;/h2&gt;

&lt;p&gt;It’s ridiculously easy to set up. Here’s how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add Dependabot to Your Repo&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;.github/dependabot.yml&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Add configuration like this:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push the File&lt;/strong&gt;:&lt;br&gt;
Dependabot will start checking your dependencies based on the schedule you set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review and Merge PRs&lt;/strong&gt;:&lt;br&gt;
Dependabot will create pull requests for any updates it finds. Review, test, and merge them.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s it! Your project is now in safe, automated hands. 🧙‍♂️✨&lt;/p&gt;




&lt;h2&gt;
  
  
  The Perfect Partner: PackBumpPR 🛠️📦
&lt;/h2&gt;

&lt;p&gt;Let’s say Dependabot has done its job. You’ve merged a bunch of PRs, and your dependencies are sparkling clean. Now what? If your project uses &lt;code&gt;package.json&lt;/code&gt;, you likely need to bump your version number to reflect these changes. 📈&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;PackBumpPR&lt;/strong&gt; comes in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version Management Made Easy&lt;/strong&gt;: It automatically updates your &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Request Creation&lt;/strong&gt;: Creates a branch and a PR for the new version bump.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatchable Workflow&lt;/strong&gt;: Trigger it manually from GitHub, no IDE required. Update your package from your phone while waiting in line for coffee! ☕&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to Use PackBumpPR 🚀
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add It to Your Workflow&lt;/strong&gt;:
Use the following snippet in your workflow file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dispatchable Version Bump&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;version_increment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;increment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(patch,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minor,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;major,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;custom)"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch"&lt;/span&gt;
      &lt;span class="na"&gt;custom_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Custom&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version_increment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;custom)"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bump-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use PackBumpPR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-username/PackBumpPR@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;version_increment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.version_increment }}&lt;/span&gt;
          &lt;span class="na"&gt;custom_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.custom_version }}&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;main_branche&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trigger the Workflow&lt;/strong&gt;:&lt;br&gt;
Go to the &lt;strong&gt;Actions&lt;/strong&gt; tab in your repo, select the workflow, and click &lt;strong&gt;Run workflow&lt;/strong&gt;. Choose your version increment type (&lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;minor&lt;/code&gt;, &lt;code&gt;major&lt;/code&gt;, or custom).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review the PR&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://github.com/marketplace/actions/packbumppr" rel="noopener noreferrer"&gt;PackBumpPR&lt;/a&gt; will create a PR for the version bump. Review it, and merge. Done!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Why Combine Dependabot + PackBumpPR? 🤝
&lt;/h2&gt;

&lt;p&gt;Together, Dependabot and &lt;a href="https://github.com/marketplace/actions/packbumppr" rel="noopener noreferrer"&gt;PackBumpPR&lt;/a&gt; make an unbeatable team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependabot keeps your dependencies fresh and secure. 🌱&lt;/li&gt;
&lt;li&gt;PackBumpPR handles version management without breaking a sweat. 💪&lt;/li&gt;
&lt;li&gt;You stay productive, even when you’re away from your desk. 📱💻&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, what are you waiting for? Automate your workflow today and say goodbye to tedious updates. Your future self will thank you. 🙌&lt;/p&gt;




&lt;p&gt;Lazy devs, rejoice! Automation isn’t just a luxury—it’s the smarter way to work. 🌟&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>github</category>
      <category>npm</category>
    </item>
    <item>
      <title>Laravel's 2024: Progress at Lightning Speed, But Are We Biting Off More Than We Can Chew?</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Sun, 29 Dec 2024 13:32:43 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/laravels-2024-progress-at-lightning-speed-but-are-we-biting-off-more-than-we-can-chew-c7g</link>
      <guid>https://forem.com/hichemtab-tech/laravels-2024-progress-at-lightning-speed-but-are-we-biting-off-more-than-we-can-chew-c7g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0t647vjft9s37c00mudm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0t647vjft9s37c00mudm.png" alt="Progress at Lightning Speed, But Are We Biting Off More Than We Can Chew" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎉 &lt;em&gt;“What a year for Laravel!”&lt;/em&gt; If there’s one framework that’s continuously redefining web development, it’s Laravel. 2024 has been no exception—if anything, it’s been extraordinary. From groundbreaking releases to exciting new tools, the Laravel team, led by Taylor Otwell, has been unstoppable. 🚀&lt;/p&gt;

&lt;p&gt;But as the saying goes, &lt;em&gt;“With great power comes great responsibility.”&lt;/em&gt; With so much rapid progress, I can’t help but wonder: &lt;strong&gt;Are we biting off more than we can chew?&lt;/strong&gt; 🤔&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Achievements Galore: What Laravel Delivered in 2024&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, let’s acknowledge the remarkable progress this year. Here’s a quick rundown of Laravel’s 2024 highlights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Laravel 11&lt;/strong&gt;: A revolutionary update with cutting-edge features and concepts. 🎊&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inertia v2&lt;/strong&gt;: A significant upgrade to the frontend scaffolding tool we’ve all come to love. 🖥️&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Cloud&lt;/strong&gt;: A cloud platform now in early access, offering exciting possibilities for hosting and deployment. ☁️&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Nightwatch&lt;/strong&gt;: A smart, professional monitoring tool set to outshine Telescope. 👀 &lt;em&gt;(you can take a look at this tool's details &lt;a href="https://dev.to/hichemtab-tech/introducing-laravel-nightwatch-a-new-age-of-monitoring-your-laravel-apps-7kl"&gt;here&lt;/a&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter Kit Overhaul&lt;/strong&gt;: The team has begun transitioning to template repositories for different app types, a bold move to simplify and specialize starter kits. 🛠️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s no wonder the Laravel community is buzzing with excitement. These developments represent bold steps forward, keeping Laravel ahead of the curve in an ever-evolving ecosystem. ❤️&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;But Are We Moving Too Fast?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;While the rapid innovation is exciting, it’s hard to ignore some growing pains. From unresolved bugs to overlooked community contributions, the speed of development may be creating cracks in the foundation. 😟&lt;/p&gt;

&lt;p&gt;Here are some examples that highlight the tension between progress and stability:&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. Inertia v2: Stability and Merging Bugs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The release of &lt;strong&gt;Inertia v2&lt;/strong&gt; has been a mixed bag for developers. While the new features are impressive, some bugs have left us scratching our heads.  &lt;/p&gt;

&lt;p&gt;Take &lt;strong&gt;issue &lt;a href="https://github.com/inertiajs/inertia/issues/2122" rel="noopener noreferrer"&gt;#2122&lt;/a&gt;&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem:&lt;/strong&gt; On mobile devices, the browser’s "Back" button doesn’t behave as expected when prefetching is enabled.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community Feedback:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;“Looks like the fix from the 1.3 beta did not make it into the 2.x beta yet.”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Why was v2 marked as stable without bug fixes from version 1.3?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This kind of instability raises questions about whether the release process is leaving some edge cases behind. 🤷‍♂️  &lt;/p&gt;




&lt;p&gt;Another example is &lt;strong&gt;issue &lt;a href="https://github.com/inertiajs/inertia/issues/2068" rel="noopener noreferrer"&gt;#2068&lt;/a&gt;&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Problem:&lt;/strong&gt; The &lt;code&gt;Inertia::merge()&lt;/code&gt; feature doesn’t handle nested arrays properly. For instance, in a pagination object:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;links&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of appending new items to the &lt;code&gt;data&lt;/code&gt; array, the entire array is replaced—leading to data loss. 😨  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proposed Fix:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Two community-driven PRs (&lt;a href="https://github.com/inertiajs/inertia/pull/2069" rel="noopener noreferrer"&gt;#2069&lt;/a&gt; and &lt;a href="https://github.com/inertiajs/inertia-laravel/pull/679" rel="noopener noreferrer"&gt;#679&lt;/a&gt;) introduced a &lt;code&gt;deepMerge&lt;/code&gt; feature to handle such cases. This would allow incremental updates to nested structures while preserving the consistency of unrelated fields.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Problem with Docs:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
To make matters worse, the &lt;a href="https://inertiajs.com/merging-props#server-side" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; suggests that merging already works as expected, creating confusion for developers relying on outdated guidance.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite these contributions from the community, the PRs remain unreviewed, leaving developers stuck. 🙃  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Laravel Cloud: Bugs in Beta&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Laravel Cloud, currently in early access, is an exciting new addition to the Laravel ecosystem. As with any early-stage product, it’s natural for some bugs to surface. The community is eager to see how this platform evolves and reaches its full potential. It’s clear that Laravel Cloud has the promise to simplify hosting and deployment for Laravel applications, and we’re optimistic about its future. ☁️✨&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Constructive Feedback: What We Hope For&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We’re not here to complain—we’re here to collaborate. Here’s what we believe could make Laravel even better moving forward:  &lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;1. Prioritize Community Contributions:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The Laravel community is incredibly talented and eager to help. By reviewing and merging PRs more efficiently, the team could tap into this collective expertise and lighten their own workload.  &lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;2. Ensure Stability Before Release:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Labeling something as “&lt;u&gt;stable&lt;/u&gt;” sets expectations. Taking extra time to address critical bugs and edge cases would help maintain trust and reliability.  &lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;3. Communicate More Openly:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Transparency is key. Whether it’s issue backlogs, priority updates, or timelines, keeping the community in the loop fosters collaboration and enthusiasm.  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion: A Message from the Community&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Laravel’s achievements in 2024 have been extraordinary. 🎆 We’re incredibly grateful to Taylor Otwell and his team for their vision, hard work, and commitment to excellence.  &lt;/p&gt;

&lt;p&gt;But with great progress comes great responsibility. As users and collaborators, we want to ensure the ecosystem remains as strong and reliable as it is innovative. After all:  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We’re not just users; we’re collaborators. Please don’t leave us blocked."&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Here’s to another amazing year of Laravel—let’s keep building greatness together! ✨  &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>programming</category>
      <category>php</category>
    </item>
    <item>
      <title>How I Destroyed Half of My Full Stack Developer Career Before It Even Got Started, Because of Google</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Fri, 20 Dec 2024 22:55:41 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/how-i-destroyed-half-of-my-full-stack-developer-career-before-it-even-got-started-because-of-google-3on0</link>
      <guid>https://forem.com/hichemtab-tech/how-i-destroyed-half-of-my-full-stack-developer-career-before-it-even-got-started-because-of-google-3on0</guid>
      <description>&lt;p&gt;It was about five years ago. Back then, I was a developer… well, when I say "developer," I mean just a boy who had just stepped into the world of programming. 😅 I’d been coding for about a year, focusing on building websites and Java Android apps. But I wanted more. I dreamed of creating games—the kind that would light up the Google Play Store. ✨&lt;/p&gt;

&lt;p&gt;So, I started learning Unity. I was programming with my friend Younes (shoutout to us! &lt;a href="https://github.com/HichemTab-tech" rel="noopener noreferrer"&gt;Hichem&lt;/a&gt; and &lt;a href="https://github.com/YounesAmalou" rel="noopener noreferrer"&gt;Younes&lt;/a&gt; 🙌), we were total newbies in the ecosystem, but even thought we created some surprisingly good games: &lt;strong&gt;Code Breaking&lt;/strong&gt;, &lt;strong&gt;Keep It Up&lt;/strong&gt;, &lt;strong&gt;Count It&lt;/strong&gt;, &lt;strong&gt;Chess Games&lt;/strong&gt;, &lt;strong&gt;Color Guessing Games&lt;/strong&gt;, &lt;strong&gt;Avoid &amp;amp; Run&lt;/strong&gt;, and more. Honestly, for two amateurs, our games were solid—worthy of competing alongside Ketchapp and Voodoo titles (at least, that’s what we told ourselves 😂).&lt;/p&gt;

&lt;p&gt;We decided to take the leap and buy a Google Play Developer account. With $25 pooled together, we bought the account. What a moment! We could actually publish apps! 😍 Being young and inexperienced, we faced a lot of rejections from Google at first (terms and policy violations and all that blah blah blah 🙄). But eventually, we managed to publish our games. &lt;/p&gt;

&lt;p&gt;You can’t imagine the dopamine rush we felt seeing our games live in the Google Play Store. 🥳 I’d tell my friends and family, “Hey! Go to Google Play and search ‘Keep It Up’” (our game name) or “Look up ‘Studio Lab tech’” (the name we chose as a brand LOL). The joy was unreal. &lt;/p&gt;

&lt;h3&gt;
  
  
  Some of the screenshots when we were building our games
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd8jra5cxcsv2nty3xtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd8jra5cxcsv2nty3xtj.png" alt="Building the game Avoid and run" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e9kxpa1s8a20cw8n9t6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e9kxpa1s8a20cw8n9t6.png" alt="Building the game Keep it up" width="800" height="690"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Detour to Disaster
&lt;/h2&gt;

&lt;p&gt;After about a year, life got busy with studies. We didn’t have time to build new games. One day, while scrolling through Upwork and Fiverr, hunting for tasks to make a little extra cash, I stumbled across an offer: $5 for someone with a Google Play Developer account to publish an app. 🤔&lt;/p&gt;

&lt;p&gt;"Wow, that’s me! I have a developer account,” I thought. Quick $5? Why not? 💸&lt;/p&gt;

&lt;p&gt;So, I did it. I uploaded the app, fixed a few errors flagged by Google, and got my $5. Nice! I started actively seeking out these publishing gigs. But something puzzled me—why didn’t these developers just buy their own accounts for $25? It seemed illogical. 🤷‍♂️&lt;/p&gt;

&lt;p&gt;It wasn’t long before I discovered the answer… the hard way. 😔&lt;/p&gt;

&lt;h2&gt;
  
  
  The Beginning of the End
&lt;/h2&gt;

&lt;p&gt;After publishing a few apps for $5 each, Google began sending alerts. One after another. Violations. Terms and policies. The worst part? I couldn’t delete the apps I uploaded. I tried archiving them but hesitated. Archiving would mean refunding the $5, right? So, I left them. Big mistake. 😓&lt;/p&gt;

&lt;p&gt;Then came the dark day. I woke up, checked my email as usual, and saw &lt;em&gt;the&lt;/em&gt; message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hi Developers at Studio Lab Tech, this is a notification that your Google Play Developer account has been terminated.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbgaojnfd3us93v3lv13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbgaojnfd3us93v3lv13.png" alt="Termination message" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like that, we were terminated. I immediately called my friend. At first, we weren’t too worried. Surely, there must be a way to restore the account, right? Wrong. 😭 After some research, we found the devastating truth:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can no longer be a Google Play Developer. Ever.&lt;/strong&gt; 😱&lt;/p&gt;

&lt;p&gt;Even creating a new account wouldn’t work. Google has ways to detect it. Our career in Android development was over. 💔&lt;/p&gt;

&lt;h2&gt;
  
  
  Growing But Shackled
&lt;/h2&gt;

&lt;p&gt;Fast forward four years, and my friend and I are now seasoned developers. We’ve nailed it in web and app development: Java, React Native, Unity C#, Kotlin, Ionic, Flutter… you name it. But for what? We can build incredible apps, but we can’t publish them. The joy of seeing our creations live on Google Play? Gone. 😢&lt;/p&gt;

&lt;p&gt;Google’s decision feels incredibly harsh and unjust. People change. Even criminals get a chance to redeem themselves. But for us, it’s a life sentence. We were young, inexperienced, and ignorant of the rules. We’ve learned so much since then, but the ban remains.&lt;/p&gt;

&lt;p&gt;Now, I’m a full stack developer working with React, MongoDB, Laravel, Symfony, Java, and Python. But when it comes to Android apps? I’m locked out. Some of the best apps I’ve built sit idle, unpublished, breaking my heart every time I remember that I can’t share them with the world. 💔&lt;/p&gt;

&lt;h2&gt;
  
  
  Google, Why?
&lt;/h2&gt;

&lt;p&gt;Google’s policy is understandable from a business perspective. They want to save time and money by eliminating risky accounts. But a lifetime ban? It’s extreme. Why not charge a higher fee for account restoration or allow for probationary periods? Why not give second chances? 😕&lt;/p&gt;

&lt;p&gt;I’ve sent multiple appeals over the years, explaining my situation. “I was just a kid,” I wrote. But every time, the answer was the same: “No.”&lt;/p&gt;

&lt;p&gt;My career—half of it, at least—is dead. I know I’m not alone. Many developers are in the same situation. We’re not asking for much—just a chance to prove we’ve changed, to redeem ourselves. 🙏&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google, please reconsider.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To anyone reading this: What can we do? Is there any hope? My wish, my dream, is for Google to give second chances. Until then, the apps I build will remain unpublished, and a part of my career will remain unfinished. 😔&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>android</category>
      <category>google</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Beyond Type Safety: making TypeScript smarter by Building a Runtime Picker</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Sat, 14 Dec 2024 00:23:35 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/beyond-type-safety-making-typescript-smarter-by-building-a-runtime-picker-26d5</link>
      <guid>https://forem.com/hichemtab-tech/beyond-type-safety-making-typescript-smarter-by-building-a-runtime-picker-26d5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hey, before we get started, let me clarify something: while I’ll be talking a lot about my package, &lt;strong&gt;ts-runtime-picker&lt;/strong&gt;, this isn’t a promotional article. I’m just sharing my experience and the journey I took before building it. (&lt;strong&gt;But hey&lt;/strong&gt;, if you're curious, &lt;a href="https://github.com/HichemTab-tech/ts-runtime-picker" rel="noopener noreferrer"&gt;here's the link&lt;/a&gt; to the package 😉).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How TypeScript Led Me to A New Idea (And a Package)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s rewind a bit. So, I’ve been working with TypeScript for a while now. I wouldn’t call myself a TypeScript pro, but I’ve built a few big projects and worked with it at my company. You know, the usual—some “hello world” projects, some slightly more complex ones, and of course, a fair share of trips to Google to figure out “what the heck does this error mean?” or “How do I pick fields from an interface again?” (You get the point. 🙃)&lt;/p&gt;

&lt;p&gt;One day, I ran into an issue while working with Firebase cloud functions. I was on the &lt;strong&gt;createUser&lt;/strong&gt; endpoint, writing my validation logic, trimming data, and handling the usual CRUD request madness. Everything was moving smoothly until I ran into this piece of code from a previous developer:&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="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and my inner TypeScript pro was screaming. 🚨&lt;/p&gt;

&lt;p&gt;I mean, &lt;strong&gt;come on&lt;/strong&gt;, this was a massive red flag. Right? Inserting unfiltered user data directly like that was risky—what if the request data was missing some validation and we ended up inserting unwanted fields into the database? Not great.&lt;/p&gt;

&lt;p&gt;I quickly removed the code, but then, I froze for a second. 🤔 I stared at my screen thinking: "Hold on, if I just assign &lt;code&gt;request.data&lt;/code&gt; to the &lt;code&gt;User&lt;/code&gt; interface type, wouldn’t TypeScript stop me from doing something like this? Shouldn’t this fix the issue?" (Cue a hopeful glance at my IDE, waiting for the red squiggly lines to appear.)&lt;/p&gt;

&lt;p&gt;But wait… 🤦‍♂️ TypeScript is &lt;strong&gt;not&lt;/strong&gt; magic. It's only a compile-time check, right? It doesn’t exist in runtime. TypeScript is a mask for type safety, but it doesn’t actually enforce anything when the code’s running. It doesn’t &lt;em&gt;really&lt;/em&gt; stop extra fields from being inserted at runtime.&lt;/p&gt;

&lt;p&gt;So, I called up one of my teammates and asked, “Hey bro, if we have an object named &lt;code&gt;alphabets&lt;/code&gt; with all the letters in the alphabet, and we create an interface &lt;code&gt;OnlyTwoLetters&lt;/code&gt; that only allows the letters 'a' and 'b', what happens when we cast the &lt;code&gt;alphabets&lt;/code&gt; object to that interface?”&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="c1"&gt;// Object with all alphabet letters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alphabets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Apple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Banana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cherry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Eggplant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... all the way to z&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Interface that only allows 'a' and 'b'&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OnlyTwoLetters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Casting alphabets to OnlyTwoLetters&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredAlphabets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;alphabets&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;OnlyTwoLetters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredAlphabets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without missing a beat, my teammate said, “Haha, well, you’d still get all the alphabet letters because TypeScript can’t really stop that in runtime.”&lt;/p&gt;

&lt;p&gt;Damn. I knew it. I was under the effect of hope—&lt;em&gt;hoping&lt;/em&gt; that TypeScript could magically prevent me from making mistakes at runtime. 🙄&lt;/p&gt;

&lt;p&gt;But then, it hit me: What if TypeScript could enforce this at runtime? What if we could cast an object to a specific interface and have TypeScript &lt;strong&gt;automatically strip out&lt;/strong&gt; any properties that weren’t defined in the interface? &lt;/p&gt;

&lt;p&gt;&lt;em&gt;That&lt;/em&gt; would solve my problem. &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Birth of &lt;code&gt;ts-runtime-picker&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So, with this lightbulb moment, I thought: “Why not make this a reality?” If I could cast &lt;code&gt;request.data&lt;/code&gt; to the &lt;code&gt;User&lt;/code&gt; interface, TypeScript could help me &lt;em&gt;automatically&lt;/em&gt; remove any extra properties, making the object safe to insert into Firebase. 🎉&lt;/p&gt;

&lt;p&gt;And just like that, the idea for &lt;strong&gt;&lt;a href="https://github.com/HichemTab-tech/ts-runtime-picker" rel="noopener noreferrer"&gt;ts-runtime-picker&lt;/a&gt;&lt;/strong&gt; was born. The goal was simple: create a package that would allow TypeScript users to filter out unwanted properties from an object, based on the fields defined in a specific interface.&lt;/p&gt;

&lt;p&gt;The best part? It would save me from the manual validation and filtering of fields. Gone were the days of:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredData&lt;/span&gt; &lt;span class="o"&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="nx"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// More work, less fun.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;How It Works: Let TypeScript Do Its Job&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With &lt;strong&gt;ts-runtime-picker&lt;/strong&gt;, the whole process is automated. You can cast an object to an interface, and the package will ensure that only the properties defined in the interface are kept. Here's how it works in action:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Before: Manual Validation&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&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;requestData&lt;/span&gt; &lt;span class="o"&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="s1"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123 Street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Manually check and remove unwanted fields:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredData&lt;/span&gt; &lt;span class="o"&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="nx"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Not very elegant.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;After: With &lt;code&gt;ts-runtime-picker&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&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;createPicker&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;ts-runtime-picker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&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;requestData&lt;/span&gt; &lt;span class="o"&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="s1"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123 Street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Automatically filters out non-existent properties:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;picker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createPicker&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;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;safeData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;firebase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;safeData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Much cleaner!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best part? This code is &lt;strong&gt;safe&lt;/strong&gt; by default. No need for manual checks or object manipulation. &lt;code&gt;ts-runtime-picker&lt;/code&gt; automatically handles it for you by filtering out all fields that don’t exist in the &lt;code&gt;User&lt;/code&gt; interface. You can just focus on your core logic without worrying about accidental field insertion. 🙌&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The Power of Laziness (and How It Can Lead to Innovation)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, you might be wondering: "Did this come out of sheer laziness?" And to that, I say: &lt;strong&gt;Yes, but also, no.&lt;/strong&gt; 😅&lt;/p&gt;

&lt;p&gt;Sure, the initial spark of the idea came from me being a little lazy—I didn’t want to manually filter fields every time I needed to insert data. But hey, sometimes laziness leads to brilliance! The desire to &lt;em&gt;make things easier&lt;/em&gt; can be a driving force for innovation.&lt;/p&gt;

&lt;p&gt;In fact, despite the initial “laziness,” I spent &lt;strong&gt;8 hours&lt;/strong&gt; building the package. Yeah, that’s right. 😆&lt;/p&gt;

&lt;p&gt;But that’s how it goes sometimes. "The need gives birth to invention," and this need to avoid tedious repetitive checks led to a new solution that ultimately made my life (and hopefully many others' lives) a lot easier.&lt;/p&gt;

&lt;p&gt;So, while I can &lt;em&gt;blame&lt;/em&gt; laziness for getting the ball rolling, it was the need to solve the problem that gave rise to &lt;strong&gt;&lt;a href="https://github.com/HichemTab-tech/ts-runtime-picker" rel="noopener noreferrer"&gt;ts-runtime-picker&lt;/a&gt;&lt;/strong&gt;. And that’s how sometimes, &lt;strong&gt;being stuck or lazy&lt;/strong&gt; isn’t necessarily a bad thing—it’s the birthplace of something new and useful!&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;And that’s the story behind the &lt;strong&gt;&lt;a href="https://github.com/HichemTab-tech/ts-runtime-picker" rel="noopener noreferrer"&gt;ts-runtime-picker&lt;/a&gt;&lt;/strong&gt; package. A journey from TypeScript frustration to creating a tool that solves a real problem. This package is my way of helping TypeScript users take full advantage of type safety — not just during development but also in runtime.&lt;/p&gt;

&lt;p&gt;If you’re tired of manually filtering fields or worried about unwanted data sneaking in, give &lt;strong&gt;&lt;a href="https://github.com/HichemTab-tech/ts-runtime-picker" rel="noopener noreferrer"&gt;ts-runtime-picker&lt;/a&gt;&lt;/strong&gt; a shot. It’s one less thing to worry about, and it makes working with TypeScript just a little bit smarter. 😄&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing Laravel Nightwatch: A New Age of Monitoring Your Laravel Apps ✨</title>
      <dc:creator>HichemTech</dc:creator>
      <pubDate>Wed, 27 Nov 2024 23:37:41 +0000</pubDate>
      <link>https://forem.com/hichemtab-tech/introducing-laravel-nightwatch-a-new-age-of-monitoring-your-laravel-apps-7kl</link>
      <guid>https://forem.com/hichemtab-tech/introducing-laravel-nightwatch-a-new-age-of-monitoring-your-laravel-apps-7kl</guid>
      <description>&lt;p&gt;Hey there, &lt;strong&gt;Laravel enthusiasts&lt;/strong&gt;! 👋 If you thought deploying a Laravel application was as good as it gets, think again—because &lt;strong&gt;Laravel Nightwatch&lt;/strong&gt; is here to take your development journey to a whole new level! Say goodbye to staring into the black box of performance issues and hello to crystal-clear insights 🕶. &lt;a href="https://github.com/taylorotwell" rel="noopener noreferrer"&gt;Taylor Otwell&lt;/a&gt; and &lt;a href="https://github.com/jessarcher" rel="noopener noreferrer"&gt;Jess Archer&lt;/a&gt; introduced this exciting new tool at &lt;a href="https://youtu.be/gzujJSPR2mI?si=Jt-PCvDk5efVJ0H3" rel="noopener noreferrer"&gt;LaraconAU 2024&lt;/a&gt;, and it promises to make monitoring your Laravel applications not only informative but also, dare I say… fun! Let's dive in 💌.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknrvkllh8q30gnr3f4ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknrvkllh8q30gnr3f4ne.png" alt="Preview of Nightwatch" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What is Laravel Nightwatch?
&lt;/h2&gt;

&lt;p&gt;Laravel Nightwatch is a hosted, fully managed observability platform designed specifically for Laravel applications. Think of it like the ultimate co-pilot for your Laravel app—always watching, always ready to provide you with the deep insights you need, and 100% tailored for Laravel. No hours of configuration, no generic dashboards, just real actionable metrics that matter to your app. It’s all about leveling up the way we understand and respond to our app's health and performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Laravel Nightwatch just works, and it looks amazing. - Taylor Otwell&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🛡 Nightwatch vs. The "Others"
&lt;/h2&gt;

&lt;p&gt;Unlike traditional monitoring tools that try to cater to every tech stack (which is often why they’re frustrating to use), Nightwatch is obsessively optimized for Laravel. Here’s why that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☆ &lt;strong&gt;Out-of-the-box configuration&lt;/strong&gt;: You just composer install the Nightwatch agent, ship your Laravel app, and boom—you've got yourself a beautiful dashboard 📊.&lt;/li&gt;
&lt;li&gt;☆ &lt;strong&gt;Deep Integration&lt;/strong&gt;: Laravel-specific metrics are displayed in a way that &lt;em&gt;makes sense&lt;/em&gt; to Laravel devs. It’s not about reinventing the wheel—it's about putting the right wheels on the right car 🏎.&lt;/li&gt;
&lt;li&gt;☆ &lt;strong&gt;Easily see what matters&lt;/strong&gt;: From N+1 query issues ❌ to detailed request timelines ⌚, Nightwatch makes it simple to dig deep without getting lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Dive into the Features
&lt;/h2&gt;

&lt;p&gt;Nightwatch isn't just about pretty charts (though, let's admit, the UI is gorgeous 💃). It offers a plethora of practical and insightful features to help you master your app's health:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dashboard Overview 🔹
&lt;/h3&gt;

&lt;p&gt;From the moment you log in, Nightwatch presents you with a clean and effective overview. The top graph lets you see your web requests: successful ones in green and issues popping in red and orange. You can see spikes, track errors, and get a sense of how your app is faring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dntzkqtcldaao2tl2cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dntzkqtcldaao2tl2cy.png" alt="Dashboard screenshot" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Performance Insights ⚡
&lt;/h3&gt;

&lt;p&gt;Wondering why that one page in your app is running slower than a sleepy snail? 🐌 No worries, Nightwatch lets you drill down into individual requests. You can check out every lifecycle stage of a request—from middleware to queries—giving you the power to figure out &lt;em&gt;exactly&lt;/em&gt; what went wrong.&lt;/p&gt;

&lt;p&gt;Nightwatch provides insights like P95 (95th percentile) for requests and jobs, so you can focus on bottlenecks without being distracted by outliers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqubx7rqv7y72qhew21sx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqubx7rqv7y72qhew21sx.png" alt="Timeline by level" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. N+1 Queries? Not on My Watch! 🔎
&lt;/h3&gt;

&lt;p&gt;No Laravel dev loves stumbling across an N+1 query. Nightwatch not only detects these situations for you but also provides all the context so you can squash them before they eat your database alive 🤖. Clicking on an offending query shows its exact origin and the routes that called it, so you’ll be back in the green in no time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i04gnd6nhnsmjjrxcdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i04gnd6nhnsmjjrxcdf.png" alt="N+1 queries" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎉 Full Traceability - From Users to Exceptions
&lt;/h2&gt;

&lt;p&gt;Ever wanted to follow a user's journey through your application and see exactly where they tripped? Now you can 🤷‍♂️. With Nightwatch, each request, each job, and even the exceptions are tied together, allowing you to trace back the root cause of the problems in a few clicks. It’s all about getting the "big picture" view with the ability to zoom in on those tiny little things that make a big difference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzmpi5bh7fot6zxznl0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzmpi5bh7fot6zxznl0s.png" alt="Exceptions report preview" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚪 Real-User Insights &amp;amp; Logging
&lt;/h2&gt;

&lt;p&gt;We all know that apps aren't just about backend performance, but about user experience 👤. Nightwatch lets you filter and track actions by individual users, meaning you can dig into the requests made by Tim, see where he experienced exceptions, and even identify if he’s having a rough day with your app. 😬&lt;/p&gt;

&lt;p&gt;Logging is integrated seamlessly into Nightwatch—no more jumping between a million different tools to find that one pesky bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Alerts That Care About You 🙌
&lt;/h2&gt;

&lt;p&gt;No one wants to babysit a dashboard 24/7. Nightwatch provides alerts for the things that matter—you can set conditions like "notify me if requests take longer than 500ms for more than 5 minutes." It’s all about meaningful notifications (not spam 📬), so you’ll always know when your app needs your love and care.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Setup &amp;amp; Availability
&lt;/h2&gt;

&lt;p&gt;The good news? Laravel Nightwatch is coming soon, with early access opening up towards the end of this year, and beta availability in early 2025. And don’t worry—you won’t need Laravel Cloud to use it, though it integrates beautifully if you do 🥳.&lt;/p&gt;

&lt;p&gt;Getting started is as easy as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;install nightwatch&lt;/li&gt;
&lt;li&gt;Ship your Laravel app ✈&lt;/li&gt;
&lt;li&gt;Enjoy the insights 📈&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🎉 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Laravel Nightwatch is set to redefine how we think about app observability. It’s built with the same care and philosophy that we love from Laravel—simplicity, developer happiness, and making tough things easier. It’s not just a monitoring tool; it’s the sidekick your Laravel app deserves.&lt;/p&gt;

&lt;p&gt;So whether you’re building for a startup, or a product used by millions… it’s time to keep an eye out for Laravel Nightwatch 🌍⚡.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get on the waitlist now, and let Nightwatch have your back!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>laravel</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
