[{"data":1,"prerenderedAt":346},["ShallowReactive",2],{"blog-/es/blog/why-ai-codegen-cant-build-your-data-pipeline":3},{"id":4,"title":5,"author":6,"authorImage":7,"authorTitle":8,"body":9,"category":335,"date":336,"description":337,"extension":338,"image":7,"meta":339,"navigation":340,"path":341,"readTime":342,"seo":343,"stem":344,"__hash__":345},"content/blog/why-ai-codegen-cant-build-your-data-pipeline.md","Why AI Codegen Can't Build Your Data Pipeline","Vibedasher Team",null,"Engineering Team",{"type":10,"value":11,"toc":320},"minimark",[12,16,20,28,33,36,58,61,65,72,77,84,88,91,98,105,109,120,124,135,143,155,162,166,169,183,271,278,285,289,296,299,316],[13,14,5],"h1",{"id":15},"why-ai-codegen-cant-build-your-data-pipeline",[17,18,19],"p",{},"Ask an AI to build you a dashboard and it will. In one prompt you'll get a clean React component, a nice color palette, a responsive grid, maybe even a sensible chart type for your data. It looks done.",[17,21,22,23,27],{},"It is not done. It's the visible 10%. The other 90% — the part that makes the chart ",[24,25,26],"em",{},"true"," tomorrow, at scale, for the right user — is the data pipeline. And that's the part AI codegen can't hand you.",[29,30,32],"h2",{"id":31},"what-ai-codegen-is-genuinely-great-at","What AI codegen is genuinely great at",[17,34,35],{},"Let's be fair, because the ceiling here is high and rising:",[37,38,39,43,55],"ul",{},[40,41,42],"li",{},"Chart components — bar, line, area, the layout, the theming.",[40,44,45,46,50,51,54],{},"Glue code — a ",[47,48,49],"code",{},"fetch",", a ",[47,52,53],{},"useEffect",", wiring state to controls.",[40,56,57],{},"One-shot transforms — \"group these rows by month and sum revenue\" on data you paste in.",[17,59,60],{},"If the data already exists, is already correct, and already fits in memory, AI codegen is a superpower. That's a real and useful envelope.",[29,62,64],{"id":63},"what-it-cant-build-and-why","What it can't build (and why)",[17,66,67,68,71],{},"A \"data pipeline\" isn't one thing an AI forgot to generate. It's a set of ",[24,69,70],{},"stateful, long-lived, correctness-critical systems"," that can't be one-shot into existence. Here's the gap, concretely.",[73,74,76],"h3",{"id":75},"connectors-that-survive-reality","Connectors that survive reality",[17,78,79,80,83],{},"Pulling from Stripe, Postgres, a warehouse, a SaaS API — the naive version is a day of work. The real version handles auth refresh, rate limits, pagination, schema drift when a source adds a column, and the source being down at 3am. An AI can scaffold a connector; it can't ",[24,81,82],{},"operate"," one. Connectors are maintenance, not code.",[73,85,87],{"id":86},"incremental-etl","Incremental ETL",[17,89,90],{},"The toy version reprocesses everything on every run. That works until you have real data volume, and then it's slow and expensive. Real ETL is incremental — it knows what changed since the last run, processes only that, and stays correct when a run fails halfway and retries. That's watermarks, idempotency, and state. There is no state for an AI to generate in a stateless prompt.",[73,92,94,95],{"id":93},"a-query-engine-not-a-select","A query engine, not a ",[47,96,97],{},"SELECT",[17,99,100,101,104],{},"Writing SQL is easy; ",[24,102,103],{},"serving"," it isn't. A query engine joins across managed datasets, plans the execution, enforces limits so one query can't take down the box, and returns typed, columnar results. It's a running system with a lifecycle, not a string an AI emits.",[73,106,108],{"id":107},"freshness-and-caching","Freshness and caching",[17,110,111,112,115,116,119],{},"Two questions every dashboard has to answer: ",[24,113,114],{},"is this current?"," and ",[24,117,118],{},"why did it take four seconds?"," Freshness means tracking when each dataset was last built and invalidating downstream when a source changes. Caching means repeat queries return instantly without re-running — and knowing exactly when a cached result is stale. These are runtime concerns with no source-code representation to generate.",[73,121,123],{"id":122},"row-level-security","Row-level security",[17,125,126,127,130,131,134],{},"The one that ends careers. Dashboard shows tenant A's revenue — fine. Now make sure tenant B, hitting the same dashboard, ",[24,128,129],{},"cannot"," see it, ever, even with a crafted request. That's row-level filtering enforced server-side, on every query, with fail-closed defaults. An AI writing a ",[47,132,133],{},"WHERE tenant_id = ..."," in a browser is not security — it's a filter the browser can rewrite. Real RLS has to live where the client can't reach it.",[29,136,138,139,142],{"id":137},"the-tell-its-all-state-and-runtime-not-code","The tell: it's all ",[24,140,141],{},"state and runtime",", not code",[17,144,145,146,150,151,154],{},"Notice the pattern. Everything AI codegen nails is ",[147,148,149],"strong",{},"stateless and static"," — a component, a transform, a snippet. Everything it can't do is ",[147,152,153],{},"stateful and runtime"," — connectors that run, ETL that remembers, caches that expire, security that's enforced on every request.",[17,156,157,158,161],{},"Codegen produces artifacts. Pipelines are ",[24,159,160],{},"systems that run",". You can't generate a system into existence with a prompt any more than you can generate a database's uptime. The chart is an artifact; the freshness of the number in it is a system.",[29,163,165],{"id":164},"so-rent-the-system-generate-the-artifact","So: rent the system, generate the artifact",[17,167,168],{},"This is exactly the Vibedasher split, and it's why the split exists.",[37,170,171,177],{},[40,172,173,176],{},[147,174,175],{},"Let AI generate the artifacts."," Your dashboard's charts, layout, and filter logic are code — so let your AI (Claude Code, Cursor) build and eject them into your app. You own that code. It's the part codegen is great at.",[40,178,179,182],{},[147,180,181],{},"Rent the running system."," The connectors, incremental ETL, query engine, freshness, caching, and row-level security are what Vibedasher operates. The whole thing surfaces through one call:",[184,185,190],"pre",{"className":186,"code":187,"language":188,"meta":189,"style":189},"language-ts shiki shiki-themes github-light github-dark","const { columns, rows } = await client.query({\n  sql,      // alias-only SQL, composed from your controls\n  params,   // { column: value } filters, bound server-side\n});\n// Managed datasets are inferred from the SQL's aliases — or pinned\n// explicitly with `datasetIds: [...]`.\n","ts","",[47,191,192,234,244,253,259,265],{"__ignoreMap":189},[193,194,197,201,205,209,212,215,218,221,224,227,231],"span",{"class":195,"line":196},"line",1,[193,198,200],{"class":199},"szBVR","const",[193,202,204],{"class":203},"sVt8B"," { ",[193,206,208],{"class":207},"sj4cs","columns",[193,210,211],{"class":203},", ",[193,213,214],{"class":207},"rows",[193,216,217],{"class":203}," } ",[193,219,220],{"class":199},"=",[193,222,223],{"class":199}," await",[193,225,226],{"class":203}," client.",[193,228,230],{"class":229},"sScJk","query",[193,232,233],{"class":203},"({\n",[193,235,237,240],{"class":195,"line":236},2,[193,238,239],{"class":203},"  sql,      ",[193,241,243],{"class":242},"sJ8bj","// alias-only SQL, composed from your controls\n",[193,245,247,250],{"class":195,"line":246},3,[193,248,249],{"class":203},"  params,   ",[193,251,252],{"class":242},"// { column: value } filters, bound server-side\n",[193,254,256],{"class":195,"line":255},4,[193,257,258],{"class":203},"});\n",[193,260,262],{"class":195,"line":261},5,[193,263,264],{"class":242},"// Managed datasets are inferred from the SQL's aliases — or pinned\n",[193,266,268],{"class":195,"line":267},6,[193,269,270],{"class":242},"// explicitly with `datasetIds: [...]`.\n",[17,272,273,274,277],{},"Every hard thing above happens ",[24,275,276],{},"behind"," that call. The table aliases resolve to managed datasets and filtered subqueries under row-level security. The result comes from cache when it can. Freshness is tracked upstream. Your ejected code never reasons about any of it — it asks for rows and gets typed rows.",[17,279,280,281,284],{},"That's the point of renting an engine: not because you ",[24,282,283],{},"couldn't"," build a query planner and an incremental ETL layer and a caching tier, but because building and operating them is a team and a roadmap, not a prompt — and it's not what makes your product yours.",[29,286,288],{"id":287},"the-takeaway","The takeaway",[17,290,291,292,295],{},"AI codegen collapsed the cost of the frontend to near zero. It did ",[147,293,294],{},"not"," collapse the cost of the pipeline underneath — because that cost was never code. It was systems, state, and operations.",[17,297,298],{},"Generate the chart. Rent the engine that makes it true.",[17,300,301,302,309,310,315],{},"See how the engine surfaces to your code at ",[303,304,308],"a",{"href":305,"rel":306},"https://docs.vibedasher.com",[307],"nofollow","docs.vibedasher.com",", or ",[303,311,314],{"href":312,"rel":313},"https://cloud.vibedasher.com/register",[307],"describe your first dashboard at cloud.vibedasher.com",".",[317,318,319],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":189,"searchDepth":236,"depth":236,"links":321},[322,323,331,333,334],{"id":31,"depth":236,"text":32},{"id":63,"depth":236,"text":64,"children":324},[325,326,327,329,330],{"id":75,"depth":246,"text":76},{"id":86,"depth":246,"text":87},{"id":93,"depth":246,"text":328},"A query engine, not a SELECT",{"id":107,"depth":246,"text":108},{"id":122,"depth":246,"text":123},{"id":137,"depth":236,"text":332},"The tell: it's all state and runtime, not code",{"id":164,"depth":236,"text":165},{"id":287,"depth":236,"text":288},"Engineering","2026-07-09","AI can vibecode a beautiful chart in seconds. It cannot build the connectors, incremental ETL, query engine, freshness, caching, and row-level security underneath it. That gap is exactly what Vibedasher runs.","md",{},true,"/blog/why-ai-codegen-cant-build-your-data-pipeline",7,{"title":5,"description":337},"blog/why-ai-codegen-cant-build-your-data-pipeline","p4zUK36qVuqVN5rxuKj-NS1uqfGH79YlBKaQL93aJAI",1783694222034]