<?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: Autonnel</title>
    <description>The latest articles on DEV Community by Autonnel (@autonnel).</description>
    <link>https://dev.to/autonnel</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%2F4206571%2F029bccb9-f4c1-411a-a856-5abd03d4f94d.png</url>
      <title>DEV Community: Autonnel</title>
      <link>https://dev.to/autonnel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/autonnel"/>
    <language>en</language>
    <item>
      <title>Prisma on Cloudflare Workers: what actually breaks</title>
      <dc:creator>Autonnel</dc:creator>
      <pubDate>Sun, 25 Oct 2026 12:29:20 +0000</pubDate>
      <link>https://dev.to/autonnel/prisma-on-cloudflare-workers-what-actually-breaks-5ej2</link>
      <guid>https://dev.to/autonnel/prisma-on-cloudflare-workers-what-actually-breaks-5ej2</guid>
      <description>&lt;p&gt;Four failures from running Prisma 7 and Astro on Cloudflare Workers: hanging connections, and a compatibility date that corrupts every SSR page.&lt;/p&gt;

&lt;p&gt;Aug 27, 2026 · 7 min read&lt;/p&gt;

&lt;p&gt;Autonnel’s website and platform backend run on Cloudflare Workers: Astro in &lt;code&gt;output: 'server'&lt;/code&gt; mode, Prisma 7 against Postgres through Hyperdrive, all inside workerd. It works well now. Getting here cost several days that I would like to give back to whoever reads this.&lt;/p&gt;

&lt;p&gt;None of these were subtle bugs in anyone’s code. They were all cases where a pattern that is correct on Node is quietly wrong on Workers, and the failure mode was far away from the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke#1-a-module-level-prisma-client-hangs-the-second-request" rel="noopener noreferrer"&gt;1. A module-level Prisma client hangs the second request&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is the one that cost the most time, because on Node it is not just fine, it is the recommended pattern.&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;// Correct on Node. Wrong on Workers.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&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;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a long-lived Node server, a module-cached pool is exactly what you want. On Workers, an opened connection cannot be reused across request I/O contexts. A module-cached pool holding idle connections means the next request that picks up one of those connections hangs, and it hangs at a point in your code with no obvious relationship to the pool.&lt;/p&gt;

&lt;p&gt;The symptom is worse than an error: intermittent, load-dependent timeouts that never reproduce locally, because your dev server only ever handles one request at a time.&lt;/p&gt;

&lt;p&gt;Cloudflare’s guidance is one connection per request, with Hyperdrive doing the pooling server-side. What we ended up with keeps a single client per request in &lt;code&gt;AsyncLocalStorage&lt;/code&gt; and disconnects it after the response settles:&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;requestScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AsyncLocalStorage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DbEnv&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&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;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStore&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;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&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;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;adapter&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;PrismaPg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;adapterConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;max&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="na"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4000&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;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Node (long-lived server): a module-cached pool is correct and efficient.&lt;/span&gt;
  &lt;span class="nx"&gt;nodeCached&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;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;adapter&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;PrismaPg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;adapterConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;idleTimeoutMillis&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;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nodeCached&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRequestDb&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&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="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="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="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;requestScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&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;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$disconnect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&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="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;Two details that matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;max: 1&lt;/code&gt; on the Workers path. One request, one connection. Hyperdrive is the pool.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;waitUntil&lt;/code&gt; for the disconnect. Cleanup must not block the response, but it also must not be dropped on the floor when the isolate is about to go away. &lt;code&gt;waitUntil&lt;/code&gt; is the only thing that gets both.&lt;/p&gt;

&lt;p&gt;The middleware wraps every request in &lt;code&gt;withRequestDb&lt;/code&gt;, so every module can call &lt;code&gt;getDb(getRuntimeEnv())&lt;/code&gt; locally and they all share one connection. The rule we wrote into the repo’s agent guide, because it is the kind of thing that gets reintroduced by accident: &lt;strong&gt;never introduce a module-level Prisma singleton.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke#2-a-compatibility-date-that-turns-every-ssr-page-into-object-object" rel="noopener noreferrer"&gt;2. A compatibility date that turns every SSR page into “[object Object]”&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is my favourite failure of the year, in the sense that I never want to see it again.&lt;/p&gt;

&lt;p&gt;Symptom: every server-rendered page returns HTML whose body is the literal string &lt;code&gt;[object Object]&lt;/code&gt;. Not an error. Not a stack trace. A 200 response containing the stringification of an object where your page should be.&lt;/p&gt;

&lt;p&gt;The chain, once we found it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;nodejs_compat&lt;/code&gt; with a compatibility date before 2026-04-01 makes workerd’s &lt;code&gt;process&lt;/code&gt; shim report &lt;code&gt;"[object process]"&lt;/code&gt; when stringified.&lt;/li&gt;
&lt;li&gt;Astro checks that value to decide whether it is running on Node.&lt;/li&gt;
&lt;li&gt;It concludes yes, and takes the Node async-iterable render path.&lt;/li&gt;
&lt;li&gt;That path is wrong on workerd, and the page serialises to &lt;code&gt;[object Object]&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is one line, and the comment above it is now the longest comment in our &lt;code&gt;wrangler.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Keep &amp;gt;= 2026-04-01: earlier dates make workerd's nodejs_compat `process` report&lt;/span&gt;
&lt;span class="c"&gt;# "[object process]", which trips Astro's isNode check into the Node async-iterable&lt;/span&gt;
&lt;span class="c"&gt;# render path, so SSR pages serialize to literal "[object Object]".&lt;/span&gt;
&lt;span class="n"&gt;compatibility_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2026-04-01"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generalisable lesson: on Workers, &lt;code&gt;compatibility_date&lt;/code&gt; is not metadata. It changes runtime semantics, including semantics that frameworks sniff to decide what environment they are in. When something behaves as though your framework has misidentified the runtime, check the date before you check anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke#3-the-bindings-api-moved-and-the-old-one-throws" rel="noopener noreferrer"&gt;3. The bindings API moved, and the old one throws&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Astro 6 with &lt;code&gt;@astrojs/cloudflare&lt;/code&gt; v13 removed &lt;code&gt;locals.runtime.env&lt;/code&gt; and &lt;code&gt;locals.runtime.ctx&lt;/code&gt;. They do not return &lt;code&gt;undefined&lt;/code&gt;, which would be easy to spot. They throw.&lt;/p&gt;

&lt;p&gt;Environment and bindings now come from &lt;code&gt;cloudflare:workers&lt;/code&gt;, and the ExecutionContext (the thing with &lt;code&gt;waitUntil&lt;/code&gt; on it, which point 1 needs) is &lt;code&gt;locals.cfContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because we still want the code to run under plain Node for tests and local development, resolution has to be conditional:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveCfEnv&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;mod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudflare:workers&lt;/span&gt;&lt;span class="dl"&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;mod&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;env&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;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Not on the workerd runtime (plain Node). Fall through to process.env.&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&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;That gets bound once per request in the middleware, and everything else reads through one helper rather than reaching for bindings directly. It is worth the indirection: when this API moves again, and it will, there is a single place to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke#4-workerd-validates-your-static-import-graph-at-deploy-time" rel="noopener noreferrer"&gt;4. workerd validates your static import graph at deploy time&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This one bites late, which is the worst time.&lt;/p&gt;

&lt;p&gt;Cloudflare validates the eager static import graph when you deploy. A Node-only dependency that never executes at runtime, but is statically imported somewhere in the graph, will still fail the deploy. Your build passed. Your tests passed. The deploy does not.&lt;/p&gt;

&lt;p&gt;For us the relevant facts were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pg&lt;/code&gt; is fine, because it bundles through &lt;code&gt;@prisma/adapter-pg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jose&lt;/code&gt; and &lt;code&gt;aws4fetch&lt;/code&gt; are edge-safe, which is why they are the JWT and S3 clients here rather than the more obvious choices.&lt;/li&gt;
&lt;li&gt;Anything reaching for &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt; in its Node form, or a native binding is not fine, no matter how conditionally it is called.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical habit: when adding a dependency to a Workers project, check its import graph before you check its API. &lt;code&gt;[unenv] fs.readFile is not implemented yet!&lt;/code&gt; at deploy time is the same lesson delivered later and more expensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke#what-actually-generalises" rel="noopener noreferrer"&gt;What actually generalises&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Strip out the specifics and the same shape appears four times: &lt;strong&gt;a pattern that is correct on a long-lived Node process is wrong on a per-request isolate, and the failure surfaces somewhere unrelated to the cause.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Connection pooling, module-level caching, environment access, and dependency loading all have that property. If you are porting a Node app to Workers, those four are where I would look first, in that order.&lt;/p&gt;

&lt;p&gt;The upside is real, which is why we stayed. Static assets are unmetered, a typical funnel sits inside the free tier, and the whole thing is one &lt;code&gt;wrangler deploy&lt;/code&gt;. The site you are reading this on is the same stack, and it serves pages in about half a second from a cold cache with zero bundled JavaScript on most routes. The full bill, worked from Cloudflare’s published rates, is in &lt;a href="https://autonnel.com/blog/funnel-cost-on-cloudflare-workers" rel="noopener noreferrer"&gt;what a funnel costs on Cloudflare Workers&lt;/a&gt;; the licence and plan side is on &lt;a href="https://autonnel.com/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to see the working version rather than the description, the whole thing is Apache-2.0 at &lt;a href="https://github.com/autonnel/autonnel" rel="noopener noreferrer"&gt;github.com/autonnel/autonnel&lt;/a&gt;. The database code above is &lt;code&gt;src/lib/db.ts&lt;/code&gt;. The &lt;a href="https://autonnel.com/docs/getting-started/installation" rel="noopener noreferrer"&gt;installation docs&lt;/a&gt; cover the Workers deploy path end to end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://autonnel.com/blog/self-hosted-checkout-what-you-run" rel="noopener noreferrer"&gt;Read next: Self-hosting a checkout: what you are actually signing up to run →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://autonnel.com/blog/prisma-on-workerd-what-broke" rel="noopener noreferrer"&gt;autonnel.com/blog/prisma-on-workerd-what-broke&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>prisma</category>
      <category>webdev</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
