<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeet Dhandha</title>
    <description>The latest articles on DEV Community by Jeet Dhandha (@jeet_dhandha_3c9b0d80399a).</description>
    <link>https://dev.to/jeet_dhandha_3c9b0d80399a</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3927602%2F3f9c17de-2867-4d61-b21d-1acb2a2b3757.jpg</url>
      <title>DEV Community: Jeet Dhandha</title>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeet_dhandha_3c9b0d80399a"/>
    <language>en</language>
    <item>
      <title>An ML search loop that can't overfit its own eval — the gate is in code, not a prompt</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Sat, 25 Jul 2026 11:55:36 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/an-ml-search-loop-that-cant-overfit-its-own-eval-the-gate-is-in-code-not-a-prompt-243p</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/an-ml-search-loop-that-cant-overfit-its-own-eval-the-gate-is-in-code-not-a-prompt-243p</guid>
      <description>&lt;p&gt;I kept seeing agent and eval demos where the &lt;em&gt;honesty&lt;/em&gt; — held-out discipline, "no metric gaming" — lives in a prompt, or in a paper's methodology section. So I tried to build the opposite: a search loop where the anti-overfitting rules are enforced in &lt;strong&gt;executable code&lt;/strong&gt;, then ran it against a real external grader (MLE-bench) to see whether that discipline actually costs you anything.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;heldout&lt;/strong&gt; — MIT, zero runtime deps, works as a Claude Code skill: &lt;a href="https://github.com/jeet-dhandha/heldout" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/heldout&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fss9p1hyr2zzfywum6h.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fss9p1hyr2zzfywum6h.gif" alt="A Random Forest node scored 0.06227 on held-out and heldout's gate killed it as overfit; the baseline refused to move" width="760" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the engine enforces — in code, not prompts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;every node is bound to a &lt;strong&gt;real measured score&lt;/strong&gt; — a shell command that must print &lt;code&gt;SCORE=&amp;lt;n&amp;gt;&lt;/code&gt;; it refuses to bind a metric it can't execute&lt;/li&gt;
&lt;li&gt;a dev-slice win &lt;strong&gt;cannot be kept&lt;/strong&gt; unless an untouched &lt;strong&gt;held-out&lt;/strong&gt; slice confirms it by a pre-set margin — dev-up / held-out-flat is a hard kill&lt;/li&gt;
&lt;li&gt;the baseline only &lt;strong&gt;ratchets up&lt;/strong&gt; on a fresh re-measure — no keeping by vibes&lt;/li&gt;
&lt;li&gt;every killed branch must leave a &lt;strong&gt;lesson&lt;/strong&gt;, injected into future code-gen prompts, so the loop never re-proposes a dead direction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no LLM ever grades its own work&lt;/strong&gt; — scores come from a grader the generated code can't read or influence&lt;/li&gt;
&lt;li&gt;the dollar budget is a &lt;strong&gt;wall inside the tool&lt;/strong&gt;, not a guideline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Harness, not a Kaggle agent
&lt;/h2&gt;

&lt;p&gt;AIDE / AutoKaggle / AIRA bundle the &lt;em&gt;search policy&lt;/em&gt; (what to try next) and the &lt;em&gt;ML operator&lt;/em&gt; (write + run the code) into one system, and enforce eval honesty by convention. heldout splits them: the engine owns only the search policy and its invariants, domain-agnostically; the operator (LLM draft → sandboxed run → parse score) is a replaceable module. MLE-bench is the proof surface, not the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest results — including where it did NOT win
&lt;/h2&gt;

&lt;p&gt;All numbers are from the official MLE-bench grader on &lt;code&gt;nomad2018-predict-transparent-conductors&lt;/code&gt; (private test, n=240). Never self-reported.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1:&lt;/strong&gt; held-out RMSLE &lt;strong&gt;0.06117&lt;/strong&gt; → silver medal, for &lt;strong&gt;$3.13&lt;/strong&gt; (9 LLM calls, each in a network-less container).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 + 2b:&lt;/strong&gt; I pre-registered an equal-dollar A/B against a &lt;em&gt;dumb linear-retry loop&lt;/em&gt; (same model, same budget) to isolate whether the search policy itself earns its cost. &lt;strong&gt;Two nulls&lt;/strong&gt; — the held-out gap stayed under my pre-set 0.005 decision bar both times (0.00479, then 0.00216). So &lt;strong&gt;"search beats retries" is NOT established&lt;/strong&gt; — and I report that, because the whole point is a harness that can tell you when it didn't work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The sharpest moment
&lt;/h2&gt;

&lt;p&gt;In Phase 2b, a Random Forest node scored &lt;strong&gt;0.06227&lt;/strong&gt; on held-out — silver-tier, and better than what the retry loop shipped. The gate &lt;strong&gt;killed it&lt;/strong&gt;: its edge over the baseline was 0.00288, below the noise-calibrated 0.0036 margin, and its dev→held-out gap (0.0055) flagged overfit. A naive "ship the best held-out score among all nodes" rule would have declared a win — by 0.00004 over the bar. The honest last-kept rule refused it → null.&lt;/p&gt;

&lt;p&gt;That refusal is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;n=1 run per arm, one task — a controlled A/B, not a statistical claim; the Phase 2b direction flip is inside single-draw noise (the retry arm drew weak, 6 of 10 attempts empty-drafted). This is a harness plus a methodology, not a SOTA result.&lt;/p&gt;




&lt;p&gt;Repo, pre-registrations, full trees, and grader ledgers: &lt;a href="https://github.com/jeet-dhandha/heldout" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/heldout&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to be told the comparison is still too generous to the search arm, or where the enforcement has a hole.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>qs-fast: a drop-in faster qs (2.3 parse, 3.8 stringify, identical output)</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:30:35 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/qs-fast-a-drop-in-faster-qs-23x-parse-38x-stringify-identical-output-e9i</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/qs-fast-a-drop-in-faster-qs-23x-parse-38x-stringify-identical-output-e9i</guid>
      <description>&lt;p&gt;&lt;a href="https://www.npmjs.com/package/qs" rel="noopener noreferrer"&gt;qs&lt;/a&gt; is the query-string parser behind Express's extended query parsing and a huge amount of HTTP-handling code. It's careful, spec-faithful, and security-hardened — it has to be, because it parses untrusted input. That carefulness also means it does a lot of work on every call, even for the boring, common case of &lt;code&gt;a=1&amp;amp;b=2&amp;amp;c=3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;qs-fast&lt;/code&gt; is a drop-in replacement that fast-paths that common case — &lt;strong&gt;2.3× faster parse, 3.8× faster stringify&lt;/strong&gt; — with &lt;strong&gt;byte-for-byte identical output&lt;/strong&gt;, and no loss of qs's safety behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast-path the easy case, fall back for everything else
&lt;/h2&gt;

&lt;p&gt;The core idea: qs-fast handles the default-options path with a hand-rolled parser and stringifier, and &lt;strong&gt;transparently bails to real qs&lt;/strong&gt; the moment anything non-trivial shows up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;non-default options (custom delimiter, &lt;code&gt;allowDots&lt;/code&gt;, custom charset, …)&lt;/li&gt;
&lt;li&gt;deep nesting (&lt;code&gt;depth &amp;gt; 5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__proto__&lt;/code&gt; / prototype-pollution vectors&lt;/li&gt;
&lt;li&gt;array index overflow&lt;/li&gt;
&lt;li&gt;cyclic object input&lt;/li&gt;
&lt;li&gt;anything else outside the fast lane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you never trade away qs's correctness or its security hardening — you just skip its overhead when it's provably safe to. &lt;code&gt;qs&lt;/code&gt; is a runtime dependency, and encoding goes through qs's own &lt;code&gt;qs/lib/utils&lt;/code&gt; so the bytes match exactly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;qs&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;qs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// after&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;qs&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;@libs-jd/qs-fast&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @libs-jd/qs-fast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;Measured on Node v24 / V8, best-of-7, against &lt;code&gt;qs@6&lt;/code&gt; — every case checked for identical output first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;op&lt;/th&gt;
&lt;th&gt;qs&lt;/th&gt;
&lt;th&gt;qs-fast&lt;/th&gt;
&lt;th&gt;speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;stringify&lt;/code&gt; (typical objects)&lt;/td&gt;
&lt;td&gt;0.0277 ms&lt;/td&gt;
&lt;td&gt;0.0072 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;parse&lt;/code&gt; (typical query strings)&lt;/td&gt;
&lt;td&gt;0.0634 ms&lt;/td&gt;
&lt;td&gt;0.0275 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.3×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the correctness gate, the fast path covered &lt;strong&gt;96% of parse&lt;/strong&gt; and &lt;strong&gt;100% of stringify&lt;/strong&gt; workloads; the rest fell through to real qs and still returned identical results.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that matters: identical output
&lt;/h2&gt;

&lt;p&gt;Same rule as any honest optimization — prove it before you trust it. &lt;code&gt;qs-fast&lt;/code&gt; ships a &lt;strong&gt;142-case gate&lt;/strong&gt; (nested objects, arrays, encoded characters, edge cases, and the fallback triggers themselves) asserting the output is identical to real qs. &lt;strong&gt;0 mismatches&lt;/strong&gt;, run in CI on every change. When in doubt, it defers to qs, so the failure mode is "as fast as qs," never "wrong."&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@libs-jd/qs-fast" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@libs-jd/qs-fast&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/jeet-dhandha/qs-fast" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/qs-fast&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. If you parse a lot of query strings, swap the import and see.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>diff-fast: a drop-in faster jsdiff (2.6–4.8 , identical output)</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:29:29 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/diff-fast-a-drop-in-faster-jsdiff-26-48x-identical-output-43km</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/diff-fast-a-drop-in-faster-jsdiff-26-48x-identical-output-43km</guid>
      <description>&lt;p&gt;If you render diffs, do three-way merges, or build review tooling in JavaScript, there's a good chance &lt;a href="https://www.npmjs.com/package/diff" rel="noopener noreferrer"&gt;jsdiff&lt;/a&gt; (the &lt;code&gt;diff&lt;/code&gt; package) is already in your dependency tree — it's one of the most-depended-on packages on npm. Its Myers-diff core is correct and battle-tested. But the hot loop leaves real speed on the table.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;diff-fast&lt;/code&gt; is a drop-in replacement that reclaims it — &lt;strong&gt;2.6×–4.8× faster&lt;/strong&gt; on the common &lt;code&gt;diffChars&lt;/code&gt; / &lt;code&gt;diffLines&lt;/code&gt; paths, with &lt;strong&gt;byte-for-byte identical output&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drop-in means drop-in
&lt;/h2&gt;

&lt;p&gt;It re-exports the entire &lt;code&gt;diff&lt;/code&gt; API unchanged and only shadows &lt;code&gt;diffChars&lt;/code&gt; and &lt;code&gt;diffLines&lt;/code&gt; with a faster implementation. Same function signatures, same options, same return objects. The whole migration is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;diffChars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;diffLines&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;diff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// after&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;diffChars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;diffLines&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;@libs-jd/diff-fast&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;Everything else in the API (&lt;code&gt;diffWords&lt;/code&gt;, &lt;code&gt;applyPatch&lt;/code&gt;, &lt;code&gt;structuredPatch&lt;/code&gt;, …) is passed straight through to the real &lt;code&gt;diff&lt;/code&gt;, which is a runtime dependency — so you're never running an incomplete fork.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @libs-jd/diff-fast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;Measured on Node v24 / V8, best-of-7, against &lt;code&gt;diff@9&lt;/code&gt; — comparing element-for-element identical output on every case:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;workload&lt;/th&gt;
&lt;th&gt;jsdiff&lt;/th&gt;
&lt;th&gt;diff-fast&lt;/th&gt;
&lt;th&gt;speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;diffChars&lt;/code&gt;, 600 chars, ~15% edits&lt;/td&gt;
&lt;td&gt;3.06 ms&lt;/td&gt;
&lt;td&gt;0.63 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;diffChars&lt;/code&gt;, 1.2k chars, ~8% edits&lt;/td&gt;
&lt;td&gt;3.55 ms&lt;/td&gt;
&lt;td&gt;0.97 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.7×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;diffLines&lt;/code&gt;, 400 lines, ~10% edits&lt;/td&gt;
&lt;td&gt;1.08 ms&lt;/td&gt;
&lt;td&gt;0.34 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.2×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;diffLines&lt;/code&gt;, 150 lines, ~20% edits&lt;/td&gt;
&lt;td&gt;0.51 ms&lt;/td&gt;
&lt;td&gt;0.19 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.6×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;No new algorithm — same Myers diff, same result. The wins are all in how the inner loop touches memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dense diagonal in a typed array.&lt;/strong&gt; jsdiff keys its furthest-reaching paths in a plain array indexed by a &lt;em&gt;signed&lt;/em&gt; diagonal, so negative indices push it into dictionary (hash) mode. &lt;code&gt;diff-fast&lt;/code&gt; offsets into an &lt;code&gt;Int32Array&lt;/code&gt; instead, keeping the whole search in packed, unboxed elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A flat component pool&lt;/strong&gt; instead of allocating a fresh &lt;code&gt;{count, value, added, removed}&lt;/code&gt; object on every edit step, which turns O(d²) object churn into index bumps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropped per-iteration bookkeeping&lt;/strong&gt; (including a &lt;code&gt;Date.now()&lt;/code&gt; call jsdiff makes on the default path).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part that matters: identical output
&lt;/h2&gt;

&lt;p&gt;A speedup you can't trust is worthless. &lt;code&gt;diff-fast&lt;/code&gt; ships with a &lt;strong&gt;200-case gate&lt;/strong&gt; (character and line diffs, random edit rates, identical inputs, insert/delete/replace mixes) that asserts the fast output is &lt;em&gt;element-for-element identical&lt;/em&gt; to jsdiff via &lt;code&gt;JSON.stringify&lt;/code&gt; equality — including the exact key order of the component objects. &lt;strong&gt;0 mismatches.&lt;/strong&gt; That gate runs in CI on every change.&lt;/p&gt;

&lt;p&gt;One honest caveat: on tiny or identical inputs the diff early-exits, so there &lt;code&gt;diff-fast&lt;/code&gt; is roughly neutral (~1×). The win shows up on real-sized diffs, which is where it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@libs-jd/diff-fast" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@libs-jd/diff-fast&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/jeet-dhandha/diff-fast" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/diff-fast&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. If you diff big things, swap the import and let me know how it goes.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop re-parsing your font 100,000 times: faster bulk PDF generation with pdf-lib</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Thu, 16 Jul 2026 21:23:37 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/stop-re-parsing-your-font-100000-times-faster-bulk-pdf-generation-with-pdf-lib-3dc</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/stop-re-parsing-your-font-100000-times-faster-bulk-pdf-generation-with-pdf-lib-3dc</guid>
      <description>&lt;h2&gt;
  
  
  The one line hiding in every pdf-lib batch job
&lt;/h2&gt;

&lt;p&gt;If you generate PDFs at scale with &lt;a href="https://github.com/Hopding/pdf-lib" rel="noopener noreferrer"&gt;pdf-lib&lt;/a&gt; — invoices, tickets, certificates, statements — you have almost certainly written a loop like this:&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;for &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;invoice&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;invoices&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerFontkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fontkit&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;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embedFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fontBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;subset&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invoice #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&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;It looks fine. It is not fine. &lt;code&gt;embedFont()&lt;/code&gt; calls &lt;code&gt;fontkit.create(bytes)&lt;/code&gt; &lt;strong&gt;every single time&lt;/strong&gt; it runs. Parsing a TrueType font is not free — it walks the glyph tables, the cmap, the hmtx metrics, the whole thing. So if you render 100,000 invoices from the same TTF, you parse that identical font file 100,000 times. You are doing the exact same expensive work, throwing the result away, and doing it again on the next iteration.&lt;/p&gt;

&lt;p&gt;I hit this while generating a few hundred thousand documents from one corporate font, watched the profiler light up inside fontkit, and realized the fix is almost embarrassingly small. So I packaged it: &lt;strong&gt;pdf-lib-bulk&lt;/strong&gt; — a tiny, zero-dependency helper that parses your font once and reuses it across the whole batch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @libs-jd/pdf-lib-bulk pdf-lib @pdf-lib/fontkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;100 one-page PDFs, one custom TTF, Apple M1. Reproduce with &lt;code&gt;bun run bench/bench.ts&lt;/code&gt; in the repo.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload (100 PDFs, custom TTF)&lt;/th&gt;
&lt;th&gt;pdf-lib naive&lt;/th&gt;
&lt;th&gt;pdf-lib-bulk&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Variable text per document (&lt;code&gt;bulkGenerate&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1,399 ms&lt;/td&gt;
&lt;td&gt;347 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.0×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identical documents (&lt;code&gt;makeTemplateCloner&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1,515 ms&lt;/td&gt;
&lt;td&gt;48 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;31×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two very different wins, because there are two very different situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every document is different&lt;/strong&gt; (invoices, letters, tickets — same font, different text). The font parse is the redundant part. Cache it and you get ~&lt;strong&gt;4×&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every document is identical&lt;/strong&gt; (blank forms, certificates you fill in later, a fixed cover page). Here the &lt;em&gt;entire&lt;/em&gt; render is redundant, not just the font. Build the document once and clone the bytes, and you get ~&lt;strong&gt;31×&lt;/strong&gt; — and it climbs higher the simpler each document is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The variable-content number is stable; the template-clone number scales with how much work you're skipping, so on very simple documents I've seen it well past 31×. The table above uses the conservative published figure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tools, pick by your situation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Variable content per document — &lt;code&gt;bulkGenerate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the common case: a batch where each PDF has different text but shares fonts. &lt;code&gt;bulkGenerate&lt;/code&gt; is a generation loop with the font cache pre-wired. You provide your fonts and a &lt;code&gt;draw&lt;/code&gt; callback; it hands you a fresh document and the already-embedded fonts for each item.&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="nx"&gt;fontkit&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;@pdf-lib/fontkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bulkGenerate&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;@libs-jd/pdf-lib-bulk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fontBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inter-Regular.ttf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;bulkGenerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fontkit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                       &lt;span class="c1"&gt;// parsed once, reused for every document&lt;/span&gt;
  &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fontBytes&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invoice #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="c1"&gt;// pdfs: Uint8Array[] — one standalone PDF per invoice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every output is a complete, standalone PDF with the font properly embedded and subset. Nothing is shared between the files themselves — only the &lt;em&gt;parse step&lt;/em&gt; is shared, in memory, during generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Identical documents — &lt;code&gt;makeTemplateCloner&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When every output is the same bytes — a blank form, a certificate template, a fixed handout — you shouldn't be re-running layout, font embedding, or drawing at all. Build the document once, however you like, then clone it:&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;makeTemplateCloner&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;@libs-jd/pdf-lib-bulk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// build the document once (fonts embedded once, layout once)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;templateBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildCertificateTemplate&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;cloner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;makeTemplateCloner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateBytes&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;pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cloner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood this uses &lt;code&gt;copyPages&lt;/code&gt; to duplicate the finished pages, skipping layout, font parsing, and font embedding entirely. That's why it's an order of magnitude faster than the naive rebuild-per-document loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Just the font cache — &lt;code&gt;cachingFontkit&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Maybe you have your own carefully tuned generation loop and don't want to hand it over. Fine — take just the cache and drop it in. It's the same fontkit API, so nothing else in your code changes:&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="nx"&gt;fontkit&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;@pdf-lib/fontkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cachingFontkit&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;@libs-jd/pdf-lib-bulk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cachingFontkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fontkit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerFontkit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                     &lt;span class="c1"&gt;// same API, parse happens once&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embedFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fontBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;subset&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works (it really is this simple)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cachingFontkit&lt;/code&gt;&lt;/strong&gt; memoizes &lt;code&gt;fontkit.create()&lt;/code&gt; per font buffer. It's a &lt;code&gt;WeakMap&lt;/code&gt; keyed by the &lt;code&gt;Uint8Array&lt;/code&gt; you pass in, so the expensive TTF/OTF parse happens once per font instead of once per document. Because it's a &lt;code&gt;WeakMap&lt;/code&gt;, there's no leak to manage — when your font buffer is gone, so is the cache entry. Fonts are still embedded and subset per document, so every output PDF stays standalone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bulkGenerate&lt;/code&gt;&lt;/strong&gt; is the loop with that cache pre-wired: create doc → embed cached fonts → your &lt;code&gt;draw&lt;/code&gt; callback → save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;makeTemplateCloner&lt;/code&gt;&lt;/strong&gt; loads your finished template once and produces copies via &lt;code&gt;copyPages&lt;/code&gt;, skipping the redundant work entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No magic, no clever tricks — just not throwing away the font parse on every iteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a separate package instead of a PR to pdf-lib?
&lt;/h2&gt;

&lt;p&gt;Caching inside &lt;code&gt;embedFont&lt;/code&gt; by default would change pdf-lib's memory profile for everyone, including people who embed a font exactly once. The redundant-parse problem only exists in &lt;em&gt;bulk&lt;/em&gt; loops, so the right place to fix it is a thin bulk-oriented layer on top — opt-in, no behavior change for single-document users. pdf-lib stays exactly as it is; you reach for this only when you're generating many documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The details that matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies.&lt;/strong&gt; It wraps pdf-lib and fontkit, which you already have. Nothing else comes along for the ride.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop-in.&lt;/strong&gt; &lt;code&gt;cachingFontkit&lt;/code&gt; is the same fontkit interface. &lt;code&gt;bulkGenerate&lt;/code&gt;/&lt;code&gt;makeTemplateCloner&lt;/code&gt; are small, focused functions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript, fully typed.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MIT licensed.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your service renders PDFs in batches, this is close to free performance — install it, swap your loop for &lt;code&gt;bulkGenerate&lt;/code&gt; (or take just &lt;code&gt;cachingFontkit&lt;/code&gt;), and stop paying for the same font parse a hundred thousand times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo (benchmark + source):&lt;/strong&gt; &lt;a href="https://github.com/jeet-dhandha/pdf-lib-bulk" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/pdf-lib-bulk&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;code&gt;npm install @libs-jd/pdf-lib-bulk&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If it saves your batch job some hours, a ⭐ on the repo helps other people find it. Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Revwy is live: paste a Google Maps link, get a business website</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Thu, 04 Jun 2026 06:31:06 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/revwy-is-live-paste-a-google-maps-link-get-a-business-website-2dc4</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/revwy-is-live-paste-a-google-maps-link-get-a-business-website-2dc4</guid>
      <description>&lt;p&gt;Revwy is now live 🚀&lt;/p&gt;

&lt;p&gt;Paste any Google Maps business listing and Revwy auto-builds a unique, themed website from its existing photos, reviews, and info — in seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free to start&lt;/strong&gt; — hosted on a revwy.com address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic / Plus / Pro&lt;/strong&gt; tiers as the business grows&lt;/li&gt;
&lt;li&gt;Built from real Google Maps data, not generic templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it 👉 &lt;a href="https://revwy.com" rel="noopener noreferrer"&gt;https://revwy.com&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I stopped Claude from cloning entire GitHub repos for a 10-line snippet</title>
      <dc:creator>Jeet Dhandha</dc:creator>
      <pubDate>Tue, 12 May 2026 16:43:35 +0000</pubDate>
      <link>https://dev.to/jeet_dhandha_3c9b0d80399a/how-i-stopped-claude-from-cloning-entire-github-repos-for-a-10-line-snippet-54aj</link>
      <guid>https://dev.to/jeet_dhandha_3c9b0d80399a/how-i-stopped-claude-from-cloning-entire-github-repos-for-a-10-line-snippet-54aj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR — I wrote a &lt;a href="https://github.com/jeet-dhandha/jd-skills/tree/main/skills/surgical-github-extraction" rel="noopener noreferrer"&gt;Claude Code skill&lt;/a&gt; that prevents Claude from cloning or &lt;code&gt;npm install&lt;/code&gt;-ing a repo when I only wanted &lt;em&gt;one function&lt;/em&gt; or &lt;em&gt;one idea&lt;/em&gt; from it. Raw files only, into &lt;code&gt;/tmp&lt;/code&gt;, smallest useful unit, adapted to my project's style. MIT, 70 lines of markdown. Part of &lt;a href="https://github.com/jeet-dhandha/jd-skills" rel="noopener noreferrer"&gt;&lt;code&gt;jd-skills&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The pattern that finally annoyed me enough
&lt;/h2&gt;

&lt;p&gt;You've done this. I've done this. You're in a Claude Code session, you paste a GitHub URL, and you say something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Look at how this repo handles agent handoffs — can we do something similar?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then Claude goes: &lt;code&gt;git clone https://github.com/...&lt;/code&gt;, reads 47 files, asks you which &lt;code&gt;__init__.py&lt;/code&gt; is interesting, and 90 seconds later you're three levels deep in someone else's repo scaffolding for what should have been a 12-line concept.&lt;/p&gt;

&lt;p&gt;Or worse — it adds the whole library to your &lt;code&gt;package.json&lt;/code&gt; as a dependency. For one function. You now own its transitive deps, its CVE notifications, and a version pin you'll never upgrade.&lt;/p&gt;

&lt;p&gt;The problem isn't Claude being lazy or sloppy. The problem is that &lt;strong&gt;"use this library" and "borrow an idea from this library" deserve completely different workflows&lt;/strong&gt;, and there was no rule telling Claude which one I meant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;The fix is dumb-simple as a rule and surprisingly effective in practice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the user references a GitHub repo for &lt;em&gt;inspiration&lt;/em&gt; (not as a dependency), never clone it and never install it. Read the README first. If the README answers the question, stop there. If code is needed, fetch raw files via &lt;code&gt;raw.githubusercontent.com&lt;/code&gt; into the OS temp dir, lift the minimum useful unit, adapt it to the user's style, and cite the source commit SHA.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I wrote this up as a Claude Code skill — a &lt;code&gt;SKILL.md&lt;/code&gt; with a description that triggers auto-invocation when a GitHub URL is dropped in as inspiration. Claude reads the skill on session start, and it just… does the right thing now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walkthrough — the concept-only case
&lt;/h2&gt;

&lt;p&gt;I was working on a YC job-applier and someone pointed me at &lt;a href="https://github.com/TauricResearch/TradingAgents" rel="noopener noreferrer"&gt;TauricResearch/TradingAgents&lt;/a&gt;. "Can we use this multi-agent pattern?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without the skill:&lt;/strong&gt; Claude would have cloned the whole trading repo, then I'd have spent 20 minutes pruning irrelevant files. The repo is for &lt;em&gt;trading&lt;/em&gt;. Almost none of it transfers to job applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With the skill:&lt;/strong&gt; Claude started with &lt;code&gt;raw.githubusercontent.com/TauricResearch/TradingAgents/main/README.md&lt;/code&gt; to map the rough shape — specialised role agents (analysts → bull/bear researchers debating → judge → trader → risk team) coordinated by a graph. Then it pulled a handful of source files via raw URLs to see how the pattern was actually wired: the agent prompt templates, the JSON schema each role hands back, and the graph node that routes between them. Four files into &lt;code&gt;/tmp&lt;/code&gt;, not a clone.&lt;/p&gt;

&lt;p&gt;That was enough to propose an analogue for my use case: &lt;code&gt;JobFitAnalyst&lt;/code&gt; + &lt;code&gt;RecruiterPersonaWriter&lt;/code&gt; + &lt;code&gt;Critic&lt;/code&gt; (a role that &lt;em&gt;argues against&lt;/em&gt; applying), orchestrated by my existing extractor pipeline — with the prompt and schema shapes borrowed from the trading agents but rewritten for jobs. We discussed the design before any code got written.&lt;/p&gt;

&lt;p&gt;Result: the pattern was useful. The framework around it wasn't. Cloning would have been pure friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walkthrough — the surgical lift case
&lt;/h2&gt;

&lt;p&gt;Different prompt: "There's a clean exponential backoff in &lt;a href="https://github.com/litl/backoff" rel="noopener noreferrer"&gt;litl/backoff&lt;/a&gt; — can we just steal it?"&lt;/p&gt;

&lt;p&gt;With the skill, Claude:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pinned the SHA&lt;/strong&gt; so the source is reproducible:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   gh api repos/litl/backoff/commits/master &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.sha'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-c1-7&lt;/span&gt;
   &lt;span class="c"&gt;# → abc1234&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Listed the tree&lt;/strong&gt; via the API instead of cloning:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   gh api repos/litl/backoff/contents/backoff &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[].path'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identified &lt;code&gt;_wait_gen.py&lt;/code&gt;&lt;/strong&gt; as the target — ~40 lines of generator logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetched just that file&lt;/strong&gt; to &lt;code&gt;/tmp/sge-backoff-abc1234/&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="s2"&gt;"https://raw.githubusercontent.com/litl/backoff/abc1234/backoff/_wait_gen.py"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/sge-backoff-abc1234/_wait_gen.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extracted the 8-line &lt;code&gt;expo&lt;/code&gt; generator&lt;/strong&gt;, rewrote it as a plain function matching my scraper's style (no Pythonic generators, just a list).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pasted it into &lt;code&gt;utils/retry.py&lt;/code&gt;&lt;/strong&gt; with a provenance comment:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# adapted from github.com/litl/backoff@abc1234:backoff/_wait_gen.py
&lt;/span&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exponential_backoff&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;
       &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;pip install backoff&lt;/code&gt;. No git submodule. &lt;strong&gt;Eight lines, owned by me, traceable to the source.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Skill and not an MCP
&lt;/h2&gt;

&lt;p&gt;Quick aside in case you're considering writing your own. People sometimes ask: should this be a Skill or an MCP server?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; ships new &lt;em&gt;tools&lt;/em&gt; to Claude. You'd build a &lt;code&gt;github_raw_fetch&lt;/code&gt; server, host it, version it, wire it into clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill&lt;/strong&gt; ships &lt;em&gt;instructions&lt;/em&gt; that shape how Claude uses tools it already has — &lt;code&gt;WebFetch&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt; via &lt;code&gt;Bash&lt;/code&gt;, &lt;code&gt;gh&lt;/code&gt;, &lt;code&gt;Read&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is purely a discipline layered on existing tools. Skill. Anthropic's own &lt;a href="https://github.com/anthropics/skills/blob/main/skills/mcp-builder/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-builder&lt;/code&gt;&lt;/a&gt; is itself a Skill, which is a hint.&lt;/p&gt;

&lt;p&gt;Rule of thumb: if you can write your idea as a paragraph of instructions, it's a Skill. If you need to add a new verb to Claude's vocabulary, it's an MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.claude/skills/surgical-github-extraction
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/jeet-dhandha/jd-skills/main/skills/surgical-github-extraction/SKILL.md &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.claude/skills/surgical-github-extraction/SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or project-scoped under &lt;code&gt;.claude/skills/&lt;/code&gt; in your repo.&lt;/p&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/jeet-dhandha/jd-skills" rel="noopener noreferrer"&gt;https://github.com/jeet-dhandha/jd-skills&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's part of &lt;a href="https://github.com/jeet-dhandha/jd-skills" rel="noopener noreferrer"&gt;&lt;code&gt;jd-skills&lt;/code&gt;&lt;/a&gt;, a small collection of Claude Code skills I'm building. The sibling skill, &lt;code&gt;code-graft&lt;/code&gt;, handles the case where a one-off snippet isn't enough but a runtime dep is too much — vendor only the slice of a library you actually use into your project, trim the rest, re-sync selectively from upstream. Useful for things like "I want one tokenizer out of HuggingFace transformers without the 2GB."&lt;/p&gt;

&lt;p&gt;Issues, prompts that misfired, and "this should also handle X" reports very welcome — skills only get sharper with concrete failure cases.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you write Claude Code skills, I'd love to see them. Drop them in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>github</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
