[{"data":1,"prerenderedAt":6457},["ShallowReactive",2],{"\u002Farticles\u002Fperformance-budgets-for-web-components":3,"articles:related":328},{"id":4,"title":5,"author":6,"body":7,"date":310,"description":311,"draft":312,"extension":313,"image":6,"meta":314,"navigation":315,"path":316,"related":317,"seo":318,"sitemap":319,"stem":320,"tags":321,"__hash__":327},"articles\u002Farticles\u002Fperformance-budgets-for-web-components.md","How We Added Performance Budgets to 117 Web Components",null,{"type":8,"value":9,"toc":298},"minimark",[10,14,17,20,23,26,31,34,37,60,63,66,75,78,82,85,88,91,95,98,101,104,128,131,135,138,141,144,147,183,186,189,192,196,199,202,210,213,216,219,223,226,229,232,236,239,277,288,292,295],[11,12,13],"p",{},"Visual regression testing can tell you that a component still looks right. Unit tests can tell you that it still behaves correctly. Neither tells you that opening a combobox, resizing a panel, or expanding navigation has quietly become slower.",[11,15,16],{},"That gap matters in a design system. A small regression in a shared component is not isolated to one page: it is multiplied across every product that consumes it.",[11,18,19],{},"At Nord Design System, we already had broad visual coverage. What we did not have was a complete inventory of interactive components or a repeatable way to detect interaction regressions. We needed performance to become a testable contract rather than something we investigated after a product felt slow.",[11,21,22],{},"This is how we built that contract across 117 production custom elements.",[24,25],"performance-pipeline-figure",{},[27,28,30],"h2",{"id":29},"start-with-an-inventory-not-a-benchmark","Start With an Inventory, Not a Benchmark",[11,32,33],{},"It is tempting to begin by measuring the most complicated component. That produces numbers quickly, but it does not tell you whether the test suite represents the system.",[11,35,36],{},"We first classified all 117 production custom elements as one of three types:",[38,39,40,48,54],"ul",{},[41,42,43,47],"li",{},[44,45,46],"strong",{},"Interactive"," components respond directly to trusted user input.",[41,49,50,53],{},[44,51,52],{},"Composite"," components coordinate several elements or a larger interaction.",[41,55,56,59],{},[44,57,58],{},"Passive"," components render content but do not perform meaningful interaction work.",[11,61,62],{},"That classification was checked into the repository and validated by tests. Adding a new production component without deciding how it should be measured now fails the inventory contract.",[11,64,65],{},"The important result was not simply a spreadsheet of components. It gave us an explicit answer to two questions:",[67,68,69,72],"ol",{},[41,70,71],{},"Which components require an interaction scenario?",[41,73,74],{},"Which components are intentionally excluded, and why?",[11,76,77],{},"Without that inventory, performance coverage would slowly become a collection of whichever examples somebody happened to remember.",[27,79,81],{"id":80},"build-deterministic-workloads","Build Deterministic Workloads",[11,83,84],{},"We created 42 Storybook scenarios covering the interactive and composite elements. Each scenario uses trusted browser input and provides deterministic normal or dense workloads.",[11,86,87],{},"The distinction between normal and dense is useful. A Combobox with five options and a Data Table with ten rows can both look fast while avoiding the work that makes them interesting. Dense fixtures give expensive code paths enough data to become visible without turning the benchmark into an unrealistic stress test.",[11,89,90],{},"Determinism is more important than realism here. Performance tests need stable inputs, stable interaction sequences, and a pinned browser environment. If fixture data, animations, network requests, or fonts change between runs, the resulting number is not a useful signal.",[27,92,94],{"id":93},"measure-the-interaction-not-the-test-runner","Measure the Interaction, Not the Test Runner",[11,96,97],{},"A Playwright test duration includes setup, selector resolution, assertions, and communication with the browser. That makes it a poor approximation of what the user experienced.",[11,99,100],{},"Instead, we collect Chromium Event Timing entries around the trusted interaction. This lets us examine the browser's input-to-render path and calculate a p75 interaction value across repeated runs.",[11,102,103],{},"The suite also records rendering work that a single duration can hide:",[38,105,106,109,112,119,122,125],{},[41,107,108],{},"layout shifts",[41,110,111],{},"long animation frames",[41,113,114,118],{},[115,116,117],"code",{},"UpdateLayoutTree"," work",[41,120,121],{},"layout duration",[41,123,124],{},"paint duration",[41,126,127],{},"the total painted viewport area",[11,129,130],{},"That last measurement proved particularly helpful. An interaction can complete within its timing budget while repainting far more of the page than intended. The user might not notice it in one component fixture, but the same invalidation pattern can become expensive once the component is composed into an application.",[27,132,134],{"id":133},"test-components-and-composition-separately","Test Components and Composition Separately",[11,136,137],{},"Isolated component scenarios are good at locating regressions. They tell us that a specific Combobox workload or Drawer interaction changed.",[11,139,140],{},"They cannot show the full cost of composition.",[11,142,143],{},"We therefore added a second layer: a production-built documentation App Shell scenario. It exercises opening and closing navigation inside a real page, with the same layout relationships and paint surface as the application.",[11,145,146],{},"The two layers answer different questions:",[148,149,150,163],"table",{},[151,152,153],"thead",{},[154,155,156,160],"tr",{},[157,158,159],"th",{},"Layer",[157,161,162],{},"Question",[164,165,166,175],"tbody",{},[154,167,168,172],{},[169,170,171],"td",{},"Component fixture",[169,173,174],{},"Which interaction became slower?",[154,176,177,180],{},[169,178,179],{},"Application scenario",[169,181,182],{},"Does composing the system create expensive layout or paint work?",[11,184,185],{},"In our initial pinned calibration environment, 37 of the 42 component scenarios recorded a p75 at or below 32 milliseconds. Dense Combobox filtering was the slowest component scenario at 64 milliseconds.",[11,187,188],{},"The App Shell recorded an INP-style p75 of 56 milliseconds. Its layout and paint durations were small, but the trace exposed broad repaint behaviour: approximately 7.6 viewport-equivalents of paint per interaction and one full-viewport paint.",[11,190,191],{},"That is exactly the kind of result we wanted. It did not justify weakening a timing budget; it identified an invalidation problem worth investigating separately.",[27,193,195],{"id":194},"a-baseline-is-a-reviewed-contract","A Baseline Is a Reviewed Contract",[11,197,198],{},"Performance numbers vary across processors, operating systems, browser versions, and CI machines. Pretending otherwise creates a test suite that is either noisy or so tolerant that it never fails.",[11,200,201],{},"We use checked-in baselines with two kinds of limits:",[38,203,204,207],{},[41,205,206],{},"a scenario-specific p75 budget for normal variation",[41,208,209],{},"an absolute ceiling for obviously bad individual interactions",[11,211,212],{},"Changing a baseline is treated as a reviewable product decision. The generated reports and traces remain build artifacts, while the small baseline and inventory files stay in version control.",[11,214,215],{},"We also separated correctness from calibration. When moving from the original arm64 environment to GitHub's native x64 runners, timing violations remained visible but advisory during calibration. Broken fixtures, invalid reports, missing inventory entries, and failed test execution still blocked the change.",[11,217,218],{},"This distinction prevented a new machine profile from disguising genuine test failures.",[27,220,222],{"id":221},"keep-the-feedback-loop-practical","Keep the Feedback Loop Practical",[11,224,225],{},"Forty-two browser workloads can make a pull request pipeline painfully slow if they run serially. We divided the scenarios into four workload-balanced CI shards and kept the application performance job independent.",[11,227,228],{},"The full screen completes in roughly ten minutes and retains JSON reports and diagnostic traces when something fails. A developer can reproduce the exact scenario locally instead of trying to infer a regression from one red number.",[11,230,231],{},"Performance tests only change behaviour when people trust and use them. Fast feedback, clear failure messages, and reproducible fixtures are part of the measurement system—not secondary tooling concerns.",[27,233,235],{"id":234},"what-i-would-repeat","What I Would Repeat",[11,237,238],{},"Several principles from this work transfer beyond Web Components:",[67,240,241,247,253,259,265,271],{},[41,242,243,246],{},[44,244,245],{},"Inventory the surface first."," Coverage should be an explicit decision, not an accidental collection of tests.",[41,248,249,252],{},[44,250,251],{},"Use trusted input."," Measure the browser responding to a user, not JavaScript calling an internal method.",[41,254,255,258],{},[44,256,257],{},"Measure rendering work as well as duration."," Layout and paint often explain regressions that one timing number cannot.",[41,260,261,264],{},[44,262,263],{},"Test isolation and composition."," Component fixtures locate problems; application scenarios expose amplification.",[41,266,267,270],{},[44,268,269],{},"Review baselines like code."," Updating a number should require an explanation of what changed and why it is acceptable.",[41,272,273,276],{},[44,274,275],{},"Calibrate without disabling correctness."," A different runner may change timings, but it should not make malformed tests pass.",[11,278,279,280,287],{},"The full implementation is available in ",[281,282,286],"a",{"href":283,"rel":284},"https:\u002F\u002Fgithub.com\u002Fnordhealth\u002Fdesign-system\u002Fpull\u002F696",[285],"nofollow","the Nord Design System performance-testing pull request",".",[27,289,291],{"id":290},"performance-is-part-of-the-component-api","Performance Is Part of the Component API",[11,293,294],{},"A component API is more than properties, events, and slots. Consumers also depend on a component responding within a reasonable amount of time and composing without invalidating an entire page.",[11,296,297],{},"By checking in the inventory, workloads, and budgets, we made those expectations visible. The suite will not prevent every slow interaction, but it gives us a repeatable way to notice when the system changes—and enough evidence to decide what to do next.",{"title":299,"searchDepth":300,"depth":300,"links":301},"",2,[302,303,304,305,306,307,308,309],{"id":29,"depth":300,"text":30},{"id":80,"depth":300,"text":81},{"id":93,"depth":300,"text":94},{"id":133,"depth":300,"text":134},{"id":194,"depth":300,"text":195},{"id":221,"depth":300,"text":222},{"id":234,"depth":300,"text":235},{"id":290,"depth":300,"text":291},"2026-08-08","How we turned interaction timing, rendering work, and application traces into a repeatable performance contract for Nord Design System.",false,"md",{},true,"\u002Farticles\u002Fperformance-budgets-for-web-components","\u002Farticles\u002Fmigrating-a-design-system-to-tailwind",{"title":5,"description":311},{"loc":316},"articles\u002Fperformance-budgets-for-web-components",[322,323,324,325,326],"Design Systems","Web Components","Performance","Playwright","Testing","8EMPnG5bkpMdvf0n6VLZ72AnUDZGWcDuGtbkqGzaOPI",[329,529,914,1222,2280,4911,5059,5872],{"id":4,"title":5,"author":6,"body":330,"date":310,"description":311,"draft":312,"extension":313,"image":6,"meta":525,"navigation":315,"path":316,"related":317,"seo":526,"sitemap":527,"stem":320,"tags":528,"__hash__":327},{"type":8,"value":331,"toc":515},[332,334,336,338,340,342,344,346,348,362,364,366,372,374,376,378,380,382,384,386,388,390,406,408,410,412,414,416,418,442,444,446,448,450,452,454,460,462,464,466,468,470,472,474,476,478,504,509,511,513],[11,333,13],{},[11,335,16],{},[11,337,19],{},[11,339,22],{},[24,341],{},[27,343,30],{"id":29},[11,345,33],{},[11,347,36],{},[38,349,350,354,358],{},[41,351,352,47],{},[44,353,46],{},[41,355,356,53],{},[44,357,52],{},[41,359,360,59],{},[44,361,58],{},[11,363,62],{},[11,365,65],{},[67,367,368,370],{},[41,369,71],{},[41,371,74],{},[11,373,77],{},[27,375,81],{"id":80},[11,377,84],{},[11,379,87],{},[11,381,90],{},[27,383,94],{"id":93},[11,385,97],{},[11,387,100],{},[11,389,103],{},[38,391,392,394,396,400,402,404],{},[41,393,108],{},[41,395,111],{},[41,397,398,118],{},[115,399,117],{},[41,401,121],{},[41,403,124],{},[41,405,127],{},[11,407,130],{},[27,409,134],{"id":133},[11,411,137],{},[11,413,140],{},[11,415,143],{},[11,417,146],{},[148,419,420,428],{},[151,421,422],{},[154,423,424,426],{},[157,425,159],{},[157,427,162],{},[164,429,430,436],{},[154,431,432,434],{},[169,433,171],{},[169,435,174],{},[154,437,438,440],{},[169,439,179],{},[169,441,182],{},[11,443,185],{},[11,445,188],{},[11,447,191],{},[27,449,195],{"id":194},[11,451,198],{},[11,453,201],{},[38,455,456,458],{},[41,457,206],{},[41,459,209],{},[11,461,212],{},[11,463,215],{},[11,465,218],{},[27,467,222],{"id":221},[11,469,225],{},[11,471,228],{},[11,473,231],{},[27,475,235],{"id":234},[11,477,238],{},[67,479,480,484,488,492,496,500],{},[41,481,482,246],{},[44,483,245],{},[41,485,486,252],{},[44,487,251],{},[41,489,490,258],{},[44,491,257],{},[41,493,494,264],{},[44,495,263],{},[41,497,498,270],{},[44,499,269],{},[41,501,502,276],{},[44,503,275],{},[11,505,279,506,287],{},[281,507,286],{"href":283,"rel":508},[285],[27,510,291],{"id":290},[11,512,294],{},[11,514,297],{},{"title":299,"searchDepth":300,"depth":300,"links":516},[517,518,519,520,521,522,523,524],{"id":29,"depth":300,"text":30},{"id":80,"depth":300,"text":81},{"id":93,"depth":300,"text":94},{"id":133,"depth":300,"text":134},{"id":194,"depth":300,"text":195},{"id":221,"depth":300,"text":222},{"id":234,"depth":300,"text":235},{"id":290,"depth":300,"text":291},{},{"title":5,"description":311},{"loc":316},[322,323,324,325,326],{"id":530,"title":531,"author":6,"body":532,"date":901,"description":902,"draft":312,"extension":313,"image":6,"meta":903,"navigation":315,"path":317,"related":904,"seo":905,"sitemap":906,"stem":907,"tags":908,"__hash__":913},"articles\u002Farticles\u002Fmigrating-a-design-system-to-tailwind.md","Migrating a Design System to Tailwind Without Breaking Its Consumers",{"type":8,"value":533,"toc":890},[534,537,540,543,547,550,557,560,563,574,577,580,584,587,590,593,596,600,603,606,609,612,616,619,622,625,681,684,688,691,715,718,727,742,749,773,776,779,783,786,789,792,795,799,807,810,813,833,835,838,876,880,883,886],[11,535,536],{},"Moving an application to a new CSS framework is mostly a local decision. Moving a design system is an ecosystem change.",[11,538,539],{},"The old classes are already spread across products, templates, documentation, tests, and code owned by teams you do not control. A migration that makes the design-system repository cleaner can still leave every consumer with years of repetitive work.",[11,541,542],{},"When we made Tailwind CSS the main styling driver for Nord Design System, the difficult part was not generating utilities. It was giving existing consumers a safe path from the old system to the new one.",[27,544,546],{"id":545},"treat-existing-classes-as-an-api","Treat Existing Classes as an API",[11,548,549],{},"A utility class may look like an implementation detail, but once it appears in consumer markup it behaves like public API.",[11,551,552,553,556],{},"Nord's existing CSS exposed both simple utilities and compound layout classes. A class such as ",[115,554,555],{},"n-stack"," represented several declarations at once. Products could also combine it with an explicit gap or alignment class.",[11,558,559],{},"Replacing the stylesheet without understanding those combinations would create subtle changes in spacing, direction, and responsive behaviour. The migration therefore had to preserve intent, not merely replace one string with another.",[11,561,562],{},"We approached the work as three connected products:",[67,564,565,568,571],{},[41,566,567],{},"A Tailwind theme that expresses Nord's design decisions.",[41,569,570],{},"Documentation that explains the new vocabulary.",[41,572,573],{},"Automated tooling that can translate existing usage safely.",[11,575,576],{},"Shipping only the first would have transferred the migration cost to every product team.",[578,579],"tailwind-migration-figure",{},[27,581,583],{"id":582},"make-tokens-the-centre-of-the-tailwind-theme","Make Tokens the Centre of the Tailwind Theme",[11,585,586],{},"Tailwind became the driver, but it did not become the source of truth for the visual language.",[11,588,589],{},"The custom Nord theme maps the existing colour, spacing, typography, radius, and shadow tokens into Tailwind. Base styles cover fonts, resets, typesetting, and shared HTML patterns. Nord-specific utilities handle forms and logical layout behaviour that should remain consistent across products.",[11,591,592],{},"This separation matters. Consumers get Tailwind's composition model while the design system retains control over the available decisions.",[11,594,595],{},"It also avoids a common migration failure: replacing a constrained system with arbitrary framework defaults. If every product starts using a slightly different grey, spacing value, or shadow because it exists in the framework, the migration has weakened the design system.",[27,597,599],{"id":598},"prefer-logical-properties-in-the-new-vocabulary","Prefer Logical Properties in the New Vocabulary",[11,601,602],{},"A new styling layer is an opportunity to remove assumptions that were difficult to change in the old one.",[11,604,605],{},"Nord's utilities use logical directions—inline and block, start and end—instead of encoding left and right into the API. Negative margin and inset utilities follow the same model.",[11,607,608],{},"Logical properties make the utilities work with right-to-left layouts and different writing modes without requiring a parallel set of directional classes. They also let the ESLint migration rule distinguish between a genuinely physical choice and a layout relationship that should follow the document direction.",[11,610,611],{},"The important lesson is that compatibility does not require preserving every historical mistake. It requires a tool-supported transformation from the old intent to a better representation.",[27,613,615],{"id":614},"build-the-migration-into-eslint","Build the Migration Into ESLint",[11,617,618],{},"Documentation helps someone understand a migration. It does not find thousands of affected templates or prevent a deprecated class from returning six months later.",[11,620,621],{},"We created an ESLint plugin that detects legacy Nord classes, recommends the new logical utilities, and can automatically replace supported patterns. This puts migration feedback in the editor and CI, where teams already encounter code-quality rules.",[11,623,624],{},"For a simple utility, replacement can be one-to-one. Compound classes are more difficult:",[626,627,631],"pre",{"className":628,"code":629,"language":630,"meta":299,"style":299},"language-html shiki shiki-themes material-theme-palenight","\u003Cdiv class=\"n-stack n-gap-l\">\n  \u003C!-- ... -->\n\u003C\u002Fdiv>\n","html",[115,632,633,665,671],{"__ignoreMap":299},[634,635,638,642,646,650,653,656,660,662],"span",{"class":636,"line":637},"line",1,[634,639,641],{"class":640},"sAklC","\u003C",[634,643,645],{"class":644},"s-wAU","div",[634,647,649],{"class":648},"sJ14y"," class",[634,651,652],{"class":640},"=",[634,654,655],{"class":640},"\"",[634,657,659],{"class":658},"sfyAc","n-stack n-gap-l",[634,661,655],{"class":640},[634,663,664],{"class":640},">\n",[634,666,667],{"class":636,"line":300},[634,668,670],{"class":669},"saEQR","  \u003C!-- ... -->\n",[634,672,674,677,679],{"class":636,"line":673},3,[634,675,676],{"class":640},"\u003C\u002F",[634,678,645],{"class":644},[634,680,664],{"class":640},[11,682,683],{},"The stack class may expand into flex direction and a default gap, while the explicit gap is meant to override that default. A mechanical expansion must not produce two competing gap utilities.",[27,685,687],{"id":686},"conflict-resolution-is-the-real-migration-logic","Conflict Resolution Is the Real Migration Logic",[11,689,690],{},"Our first rule was not enough if it generated output such as:",[626,692,694],{"className":628,"code":693,"language":630,"meta":299,"style":299},"\u003Cdiv class=\"n:flex n:flex-col n:gap-m n:gap-l\">\n",[115,695,696],{"__ignoreMap":299},[634,697,698,700,702,704,706,708,711,713],{"class":636,"line":637},[634,699,641],{"class":640},[634,701,645],{"class":644},[634,703,649],{"class":648},[634,705,652],{"class":640},[634,707,655],{"class":640},[634,709,710],{"class":658},"n:flex n:flex-col n:gap-m n:gap-l",[634,712,655],{"class":640},[634,714,664],{"class":640},[11,716,717],{},"Both gap classes are valid in isolation, but together they make the result depend on generated CSS order rather than the author's intent.",[11,719,720,721,726],{},"The ",[281,722,725],{"href":723,"rel":724},"https:\u002F\u002Fgithub.com\u002Fnordhealth\u002Fdesign-system\u002Fpull\u002F567",[285],"conflict-detection work"," established an explicit priority:",[67,728,729,736,739],{},[41,730,731,732,735],{},"Existing ",[115,733,734],{},"n:*"," utilities have the highest priority.",[41,737,738],{},"Explicit single-output legacy classes come next.",[41,740,741],{},"When compound legacy classes conflict, the rightmost class wins.",[11,743,744,745,748],{},"With that model, ",[115,746,747],{},"n-stack n:gap-l"," becomes:",[626,750,752],{"className":628,"code":751,"language":630,"meta":299,"style":299},"\u003Cdiv class=\"n:flex n:flex-col n:gap-l\">\n",[115,753,754],{"__ignoreMap":299},[634,755,756,758,760,762,764,766,769,771],{"class":636,"line":637},[634,757,641],{"class":640},[634,759,645],{"class":644},[634,761,649],{"class":648},[634,763,652],{"class":640},[634,765,655],{"class":640},[634,767,768],{"class":658},"n:flex n:flex-col n:gap-l",[634,770,655],{"class":640},[634,772,664],{"class":640},[11,774,775],{},"The rule is doing more than renaming classes. It is interpreting a small styling language, resolving conflicts, and emitting an equivalent expression in the new language.",[11,777,778],{},"This is where migration tooling earns its value. The easy 80% can be handled by search and replace; the remaining combinations are where visual regressions hide.",[27,780,782],{"id":781},"documentation-must-support-mixed-states","Documentation Must Support Mixed States",[11,784,785],{},"Large consumers do not migrate in one pull request. For a while, teams need to understand both the legacy class they encounter and the utility they should use next.",[11,787,788],{},"The initial implementation included a new Tailwind documentation area, live examples, package entry points, and a migration guide. The documentation explains how the Nord theme differs from unconfigured Tailwind and keeps examples tied to the supported token set.",[11,790,791],{},"During the transition, we also had to test the old and new paths together. Component tests, documentation examples, visual regression tests, package builds, and lint rules all became part of the migration surface.",[11,793,794],{},"That mixed state is not temporary noise to ignore. It is the actual product experience until the final consumer has moved.",[27,796,798],{"id":797},"release-the-path-not-just-the-destination","Release the Path, Not Just the Destination",[11,800,720,801,806],{},[281,802,805],{"href":803,"rel":804},"https:\u002F\u002Fgithub.com\u002Fnordhealth\u002Fdesign-system\u002Fpull\u002F559",[285],"main Tailwind implementation"," touched 68 files and added theme configuration, base styles, utilities, build tooling, documentation, and the first version of the ESLint migration support.",[11,808,809],{},"Later changes refined conflict handling, logical selectors, negative values, and legacy replacements as real combinations were exercised. That progression was expected: migration rules improve when they meet the full variety of consumer code.",[11,811,812],{},"A safer rollout looks less like a framework switch and more like an API migration:",[38,814,815,818,821,824,827,830],{},[41,816,817],{},"publish the new representation",[41,819,820],{},"keep a documented compatibility period",[41,822,823],{},"provide detection and automatic fixes",[41,825,826],{},"test equivalent output",[41,828,829],{},"collect cases the automatic migration cannot decide",[41,831,832],{},"remove the old path only when consumers have somewhere reliable to go",[27,834,235],{"id":234},[11,836,837],{},"If I were planning another design-system styling migration, I would keep these principles:",[67,839,840,846,852,858,864,870],{},[41,841,842,845],{},[44,843,844],{},"Assume consumed CSS is public API."," Whether it was intended to be or not, products depend on it.",[41,847,848,851],{},[44,849,850],{},"Put design tokens above the framework."," Tailwind should express the system, not redefine it.",[41,853,854,857],{},[44,855,856],{},"Ship migration tooling with the new API."," A destination without a path is incomplete.",[41,859,860,863],{},[44,861,862],{},"Model conflicts explicitly."," Class order and compound utilities contain intent that string replacement cannot preserve.",[41,865,866,869],{},[44,867,868],{},"Design for a long mixed state."," Documentation, tests, and tooling must work while old and new syntax coexist.",[41,871,872,875],{},[44,873,874],{},"Use migration pressure to improve the model."," Logical properties and clearer utilities are worth introducing when automation can carry consumers forward.",[27,877,879],{"id":878},"the-framework-was-the-small-part","The Framework Was the Small Part",[11,881,882],{},"Tailwind gave us a flexible engine and a familiar authoring model. The design-system work was everything around it: translating tokens, defining supported utilities, protecting existing products, and making the new path easier than staying on the old one.",[11,884,885],{},"That is the difference between adopting a CSS framework and evolving a shared platform.",[887,888,889],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}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);}",{"title":299,"searchDepth":300,"depth":300,"links":891},[892,893,894,895,896,897,898,899,900],{"id":545,"depth":300,"text":546},{"id":582,"depth":300,"text":583},{"id":598,"depth":300,"text":599},{"id":614,"depth":300,"text":615},{"id":686,"depth":300,"text":687},{"id":781,"depth":300,"text":782},{"id":797,"depth":300,"text":798},{"id":234,"depth":300,"text":235},{"id":878,"depth":300,"text":879},"2026-07-26","The migration tools, compatibility decisions, and rollout strategy behind making Tailwind CSS the main styling driver for Nord Design System.",{},"\u002Farticles\u002Fdocumentation-is-an-api-for-coding-agents",{"title":531,"description":902},{"loc":317},"articles\u002Fmigrating-a-design-system-to-tailwind",[322,909,910,911,912],"Tailwind CSS","ESLint","Migration","Developer Experience","25wYSs0F2zajLBnb2CrJjl3YRj8GBCllg4P2rVTz32M",{"id":915,"title":916,"author":6,"body":917,"date":1210,"description":1211,"draft":312,"extension":313,"image":6,"meta":1212,"navigation":315,"path":904,"related":1213,"seo":1214,"sitemap":1215,"stem":1216,"tags":1217,"__hash__":1221},"articles\u002Farticles\u002Fdocumentation-is-an-api-for-coding-agents.md","Documentation Is Becoming an API for Coding Agents",{"type":8,"value":918,"toc":1188},[919,922,925,928,932,935,942,945,948,952,960,986,993,996,999,1003,1006,1009,1012,1019,1022,1026,1029,1040,1043,1060,1063,1069,1072,1079,1082,1086,1089,1093,1096,1099,1103,1106,1110,1113,1117,1124,1128,1131,1135,1138,1142,1145,1149,1152,1172,1175,1179,1182,1185],[11,920,921],{},"Documentation used to have one primary interface: a page read by a person.",[11,923,924],{},"That page may also have been indexed by search, copied into an issue, or shown inside an IDE, but its structure was still designed around human navigation. Coding agents change that assumption. They need to discover the right documentation, retrieve a small relevant part, understand its version, and turn it into code without loading an entire website into context.",[11,926,927],{},"This does not make human documentation less important. It means documentation now has another consumer—and that consumer behaves more like an API client than a reader.",[27,929,931],{"id":930},"the-stale-knowledge-problem","The Stale-Knowledge Problem",[11,933,934],{},"Language models are good at recalling common component APIs. They are much less reliable when a library has recently changed a prop, replaced a component, or introduced a new pattern.",[11,936,937,938,941],{},"While building an MCP server for Nuxt UI, I tested a refactor involving ",[115,939,940],{},"FormGroup",". The model initially suggested the older v2 API from memory. It then queried the MCP server, found the current component documentation and examples, and corrected the implementation.",[11,943,944],{},"The interesting part was not that the model had been wrong. Stale knowledge is inevitable. The useful behaviour was giving it a dependable way to notice uncertainty and retrieve the current answer.",[11,946,947],{},"Search can sometimes solve this, but general web search has weak guarantees around library version, canonical sources, and the relationship between prose and source code. A documentation interface can do better.",[27,949,951],{"id":950},"give-agents-small-intentional-tools","Give Agents Small, Intentional Tools",[11,953,720,954,959],{},[281,955,958],{"href":956,"rel":957},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F4878",[285],"Nuxt UI MCP contribution"," exposed focused tools for the tasks a coding agent actually performs:",[67,961,962,965,968,971,974,977,980,983],{},[41,963,964],{},"List available components and their categories.",[41,966,967],{},"Retrieve a component's source and metadata.",[41,969,970],{},"Retrieve a working component demo.",[41,972,973],{},"Inspect props, slots, and events.",[41,975,976],{},"List available templates.",[41,978,979],{},"Retrieve the details of a template.",[41,981,982],{},"Search the documentation.",[41,984,985],{},"Inspect the relevant repository structure.",[11,987,988,989,992],{},"This is deliberately different from a single ",[115,990,991],{},"get_all_docs"," tool.",[11,994,995],{},"A model usually does not need every component, every example, and the full migration guide. It needs the Button API, one Modal example, or the current way to structure a form. Smaller tools produce smaller responses, make intent observable, and let the server cache resources according to how often they change.",[11,997,998],{},"The hosted server supports HTTP and Server-Sent Events transports, with caching around component source, templates, and documentation. Transport is not the product, though. The product is a reliable question-and-answer boundary over the library's canonical material.",[27,1000,1002],{"id":1001},"structure-once-publish-to-several-interfaces","Structure Once, Publish to Several Interfaces",[11,1004,1005],{},"Maintaining a separate set of “AI documentation” would quickly create the same stale-knowledge problem inside the repository.",[11,1007,1008],{},"A better architecture starts with structured source material and derives several interfaces from it:",[1010,1011],"documentation-interfaces-figure",{},[11,1013,1014,1015,1018],{},"The website can add navigation, interactive examples, syntax highlighting, and visual hierarchy. ",[115,1016,1017],{},"llms.txt"," can provide a discoverable map of the important material. MCP can expose narrow retrieval operations. All three should point back to the same component names, examples, and API metadata.",[11,1020,1021],{},"That shared source is more important than any particular protocol.",[27,1023,1025],{"id":1024},"rebuilding-the-documentation-platform","Rebuilding the Documentation Platform",[11,1027,1028],{},"We made the same idea part of Nord Design System's documentation architecture.",[11,1030,720,1031,1036,1037,1039],{},[281,1032,1035],{"href":1033,"rel":1034},"https:\u002F\u002Fgithub.com\u002Fnordhealth\u002Fdesign-system\u002Fpull\u002F572",[285],"Nord documentation rewrite"," moved the site from Eleventy to Nuxt 4 and Nuxt Content. Alongside the human-facing site, it added server routes for component metadata, examples, navigation, and assets; full-text search; syntax highlighting; sitemap generation; and ",[115,1038,1017],{}," support.",[11,1041,1042],{},"The rewrite was not only a framework upgrade. It established a platform from which the same documentation could support:",[38,1044,1045,1048,1051,1054,1057],{},[41,1046,1047],{},"developers reading guides and examples",[41,1049,1050],{},"designers editing structured content",[41,1052,1053],{},"IDE and community integrations",[41,1055,1056],{},"search and metadata endpoints",[41,1058,1059],{},"coding agents using skills or MCP",[11,1061,1062],{},"Once content is addressable as structured data, new interfaces do not require duplicating the documentation itself.",[27,1064,1066,1068],{"id":1065},"llmstxt-skills-and-mcp-solve-different-problems",[115,1067,1017],{},", Skills, and MCP Solve Different Problems",[11,1070,1071],{},"These formats are often grouped together, but they operate at different levels.",[1073,1074,1076,1078],"h3",{"id":1075},"llmstxt-provides-discovery",[115,1077,1017],{}," provides discovery",[11,1080,1081],{},"It gives a model a concise map of the site and points toward canonical resources. It is useful when an agent can fetch web content but does not already know the documentation structure.",[1073,1083,1085],{"id":1084},"skills-provide-procedure","Skills provide procedure",[11,1087,1088],{},"A skill can explain how to apply the system: which component to prefer, how to compose patterns, what constraints to preserve, and where to look next. It turns reference material into an operational workflow.",[1073,1090,1092],{"id":1091},"mcp-provides-retrieval-and-actions","MCP provides retrieval and actions",[11,1094,1095],{},"An MCP server exposes explicit capabilities with typed inputs and bounded outputs. It is appropriate when an agent needs to search, inspect metadata, or fetch one resource repeatedly during implementation.",[11,1097,1098],{},"The three can reinforce each other. None of them compensates for inaccurate source documentation.",[27,1100,1102],{"id":1101},"design-the-documentation-like-a-public-interface","Design the Documentation Like a Public Interface",[11,1104,1105],{},"Thinking of documentation as an API changes the questions we ask.",[1073,1107,1109],{"id":1108},"is-every-concept-addressable","Is every concept addressable?",[11,1111,1112],{},"A page title visible in a navigation menu is not enough. Components, examples, templates, and migration guides need stable identifiers that tools can retrieve directly.",[1073,1114,1116],{"id":1115},"can-a-consumer-ask-a-narrow-question","Can a consumer ask a narrow question?",[11,1118,1119,1120,1123],{},"If the only interface returns a megabyte of Markdown, every agent must build its own search layer. Focused operations such as ",[115,1121,1122],{},"get_component_demo"," make the intended access pattern clear.",[1073,1125,1127],{"id":1126},"is-version-information-preserved","Is version information preserved?",[11,1129,1130],{},"An accurate answer for v2 can be a broken answer for v4. Generated metadata and examples should come from the same revision as the installed package whenever possible.",[1073,1132,1134],{"id":1133},"can-the-answer-be-verified","Can the answer be verified?",[11,1136,1137],{},"Examples should be buildable, component metadata should be generated from source, and links should resolve. A fluent response is not evidence that the retrieved material is valid.",[1073,1139,1141],{"id":1140},"are-the-boundaries-honest","Are the boundaries honest?",[11,1143,1144],{},"Documentation tools should return documentation. They should not silently invent product-specific architecture, business rules, or security decisions that belong to the consuming application.",[27,1146,1148],{"id":1147},"what-not-to-do","What Not to Do",[11,1150,1151],{},"Several shortcuts make an impressive demo but a weak documentation system:",[38,1153,1154,1157,1160,1163,1166,1169],{},[41,1155,1156],{},"dumping the entire documentation site into every prompt",[41,1158,1159],{},"copying examples into a separate AI-only repository",[41,1161,1162],{},"relying on model memory for fast-moving APIs",[41,1164,1165],{},"exposing one unbounded search tool with inconsistent output",[41,1167,1168],{},"publishing generated metadata without testing it against the package",[41,1170,1171],{},"treating an MCP server as a replacement for navigable human documentation",[11,1173,1174],{},"Agents make information architecture more visible. Ambiguous naming, duplicate examples, missing versioning, and undocumented behaviour become retrieval problems very quickly.",[27,1176,1178],{"id":1177},"documentation-is-part-of-the-runtime-experience","Documentation Is Part of the Runtime Experience",[11,1180,1181],{},"For a developer using a component library, the practical interface is not only the JavaScript package. It is the package, types, examples, migration guidance, editor feedback, and now the information available to their coding agent.",[11,1183,1184],{},"Designing those surfaces from one structured source gives people better documentation and gives agents a way to correct their own stale assumptions.",[11,1186,1187],{},"The goal is not to write documentation for robots. It is to make the knowledge behind a system precise enough that every consumer can ask for the right part and receive a current, verifiable answer.",{"title":299,"searchDepth":300,"depth":300,"links":1189},[1190,1191,1192,1193,1194,1201,1208,1209],{"id":930,"depth":300,"text":931},{"id":950,"depth":300,"text":951},{"id":1001,"depth":300,"text":1002},{"id":1024,"depth":300,"text":1025},{"id":1065,"depth":300,"text":1195,"children":1196},"llms.txt, Skills, and MCP Solve Different Problems",[1197,1199,1200],{"id":1075,"depth":673,"text":1198},"llms.txt provides discovery",{"id":1084,"depth":673,"text":1085},{"id":1091,"depth":673,"text":1092},{"id":1101,"depth":300,"text":1102,"children":1202},[1203,1204,1205,1206,1207],{"id":1108,"depth":673,"text":1109},{"id":1115,"depth":673,"text":1116},{"id":1126,"depth":673,"text":1127},{"id":1133,"depth":673,"text":1134},{"id":1140,"depth":673,"text":1141},{"id":1147,"depth":300,"text":1148},{"id":1177,"depth":300,"text":1178},"2026-07-12","What building MCP servers, llms.txt, and machine-readable component documentation taught me about designing documentation for people and coding agents.",{},"\u002Farticles\u002Fone-close-method-across-reka-ui-and-nuxt-ui",{"title":916,"description":1211},{"loc":904},"articles\u002Fdocumentation-is-an-api-for-coding-agents",[1218,1219,1220,322,912],"Documentation","MCP","AI","xjB1j3YOHgFcNbayCIbGcCYEgkhZlVImXmu0eevp4d8",{"id":1223,"title":1224,"author":6,"body":1225,"date":2267,"description":2268,"draft":312,"extension":313,"image":6,"meta":2269,"navigation":315,"path":1213,"related":316,"seo":2270,"sitemap":2271,"stem":2272,"tags":2273,"__hash__":2279},"articles\u002Farticles\u002Fone-close-method-across-reka-ui-and-nuxt-ui.md","One close() Method Across Reka UI and Nuxt UI",{"type":8,"value":1226,"toc":2257},[1227,1230,1240,1243,1247,1250,1470,1473,1476,1479,1604,1607,1610,1614,1617,1620,1623,1626,1629,1633,1647,1650,1852,1858,1861,1865,1874,1877,2147,2150,2154,2157,2166,2180,2184,2190,2193,2196,2200,2203,2241,2245,2248,2254],[11,1228,1229],{},"Small component APIs can expose large architectural problems.",[11,1231,1232,1233,1236,1237,287],{},"I wanted content inside a Nuxt UI Modal to close the modal that contained it. The visible requirement was tiny: provide a ",[115,1234,1235],{},"close()"," function in the component's scoped slots. The existing solution required consumers to create and maintain an external boolean ref solely to set it back to ",[115,1238,1239],{},"false",[11,1241,1242],{},"Adding one function turned into a change across Nuxt UI, Reka UI, and two different primitives. That path is a useful example of finding the correct layer for an API rather than hiding the problem in the highest-level component.",[27,1244,1246],{"id":1245},"the-consumer-problem","The Consumer Problem",[11,1248,1249],{},"A controlled modal is explicit, but it creates state even when the consumer has no other reason to own that state:",[626,1251,1255],{"className":1252,"code":1253,"language":1254,"meta":299,"style":299},"language-vue shiki shiki-themes material-theme-palenight","\u003Cscript setup lang=\"ts\">\nconst open = ref(false)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUModal v-model:open=\"open\">\n    \u003CUButton label=\"Open\" \u002F>\n\n    \u003Ctemplate #footer>\n      \u003CUButton label=\"Close\" @click=\"open = false\" \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUModal>\n\u003C\u002Ftemplate>\n","vue",[115,1256,1257,1281,1305,1313,1319,1329,1357,1381,1386,1401,1441,1451,1461],{"__ignoreMap":299},[634,1258,1259,1261,1264,1267,1270,1272,1274,1277,1279],{"class":636,"line":637},[634,1260,641],{"class":640},[634,1262,1263],{"class":644},"script",[634,1265,1266],{"class":648}," setup",[634,1268,1269],{"class":648}," lang",[634,1271,652],{"class":640},[634,1273,655],{"class":640},[634,1275,1276],{"class":658},"ts",[634,1278,655],{"class":640},[634,1280,664],{"class":640},[634,1282,1283,1286,1290,1292,1296,1299,1302],{"class":636,"line":300},[634,1284,1285],{"class":648},"const",[634,1287,1289],{"class":1288},"s0W1g"," open ",[634,1291,652],{"class":640},[634,1293,1295],{"class":1294},"sdLwU"," ref",[634,1297,1298],{"class":1288},"(",[634,1300,1239],{"class":1301},"sbqyR",[634,1303,1304],{"class":1288},")\n",[634,1306,1307,1309,1311],{"class":636,"line":673},[634,1308,676],{"class":640},[634,1310,1263],{"class":644},[634,1312,664],{"class":640},[634,1314,1316],{"class":636,"line":1315},4,[634,1317,1318],{"emptyLinePlaceholder":315},"\n",[634,1320,1322,1324,1327],{"class":636,"line":1321},5,[634,1323,641],{"class":640},[634,1325,1326],{"class":644},"template",[634,1328,664],{"class":640},[634,1330,1332,1335,1338,1341,1344,1347,1349,1351,1353,1355],{"class":636,"line":1331},6,[634,1333,1334],{"class":640},"  \u003C",[634,1336,1337],{"class":644},"UModal",[634,1339,1340],{"class":648}," v-model",[634,1342,1343],{"class":640},":",[634,1345,1346],{"class":648},"open",[634,1348,652],{"class":640},[634,1350,655],{"class":640},[634,1352,1346],{"class":1288},[634,1354,655],{"class":640},[634,1356,664],{"class":640},[634,1358,1360,1363,1366,1369,1371,1373,1376,1378],{"class":636,"line":1359},7,[634,1361,1362],{"class":640},"    \u003C",[634,1364,1365],{"class":644},"UButton",[634,1367,1368],{"class":648}," label",[634,1370,652],{"class":640},[634,1372,655],{"class":640},[634,1374,1375],{"class":658},"Open",[634,1377,655],{"class":640},[634,1379,1380],{"class":640}," \u002F>\n",[634,1382,1384],{"class":636,"line":1383},8,[634,1385,1318],{"emptyLinePlaceholder":315},[634,1387,1389,1391,1393,1396,1399],{"class":636,"line":1388},9,[634,1390,1362],{"class":640},[634,1392,1326],{"class":644},[634,1394,1395],{"class":640}," #",[634,1397,1398],{"class":648},"footer",[634,1400,664],{"class":640},[634,1402,1404,1407,1409,1411,1413,1415,1418,1420,1423,1426,1428,1430,1432,1435,1437,1439],{"class":636,"line":1403},10,[634,1405,1406],{"class":640},"      \u003C",[634,1408,1365],{"class":644},[634,1410,1368],{"class":648},[634,1412,652],{"class":640},[634,1414,655],{"class":640},[634,1416,1417],{"class":658},"Close",[634,1419,655],{"class":640},[634,1421,1422],{"class":640}," @",[634,1424,1425],{"class":648},"click",[634,1427,652],{"class":640},[634,1429,655],{"class":640},[634,1431,1346],{"class":1288},[634,1433,1434],{"class":640}," = ",[634,1436,1239],{"class":1301},[634,1438,655],{"class":640},[634,1440,1380],{"class":640},[634,1442,1444,1447,1449],{"class":636,"line":1443},11,[634,1445,1446],{"class":640},"    \u003C\u002F",[634,1448,1326],{"class":644},[634,1450,664],{"class":640},[634,1452,1454,1457,1459],{"class":636,"line":1453},12,[634,1455,1456],{"class":640},"  \u003C\u002F",[634,1458,1337],{"class":644},[634,1460,664],{"class":640},[634,1462,1464,1466,1468],{"class":636,"line":1463},13,[634,1465,676],{"class":640},[634,1467,1326],{"class":644},[634,1469,664],{"class":640},[11,1471,1472],{},"This becomes more awkward with nested modals. Each instance needs a separate ref, and content must close over the correct one.",[11,1474,1475],{},"The component already owns or coordinates its open state. Its content needs a safe, local way to request the common transition from open to closed.",[11,1477,1478],{},"The API I wanted looked like this:",[626,1480,1482],{"className":1252,"code":1481,"language":1254,"meta":299,"style":299},"\u003Ctemplate>\n  \u003CUModal>\n    \u003CUButton label=\"Open\" \u002F>\n\n    \u003Ctemplate #footer=\"{ close }\">\n      \u003CUButton label=\"Close\" @click=\"close\" \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUModal>\n\u003C\u002Ftemplate>\n",[115,1483,1484,1492,1500,1518,1522,1549,1580,1588,1596],{"__ignoreMap":299},[634,1485,1486,1488,1490],{"class":636,"line":637},[634,1487,641],{"class":640},[634,1489,1326],{"class":644},[634,1491,664],{"class":640},[634,1493,1494,1496,1498],{"class":636,"line":300},[634,1495,1334],{"class":640},[634,1497,1337],{"class":644},[634,1499,664],{"class":640},[634,1501,1502,1504,1506,1508,1510,1512,1514,1516],{"class":636,"line":673},[634,1503,1362],{"class":640},[634,1505,1365],{"class":644},[634,1507,1368],{"class":648},[634,1509,652],{"class":640},[634,1511,655],{"class":640},[634,1513,1375],{"class":658},[634,1515,655],{"class":640},[634,1517,1380],{"class":640},[634,1519,1520],{"class":636,"line":1315},[634,1521,1318],{"emptyLinePlaceholder":315},[634,1523,1524,1526,1528,1530,1532,1534,1536,1539,1542,1545,1547],{"class":636,"line":1321},[634,1525,1362],{"class":640},[634,1527,1326],{"class":644},[634,1529,1395],{"class":640},[634,1531,1398],{"class":648},[634,1533,652],{"class":640},[634,1535,655],{"class":640},[634,1537,1538],{"class":640},"{",[634,1540,1541],{"class":1288}," close ",[634,1543,1544],{"class":640},"}",[634,1546,655],{"class":640},[634,1548,664],{"class":640},[634,1550,1551,1553,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1576,1578],{"class":636,"line":1331},[634,1552,1406],{"class":640},[634,1554,1365],{"class":644},[634,1556,1368],{"class":648},[634,1558,652],{"class":640},[634,1560,655],{"class":640},[634,1562,1417],{"class":658},[634,1564,655],{"class":640},[634,1566,1422],{"class":640},[634,1568,1425],{"class":648},[634,1570,652],{"class":640},[634,1572,655],{"class":640},[634,1574,1575],{"class":1288},"close",[634,1577,655],{"class":640},[634,1579,1380],{"class":640},[634,1581,1582,1584,1586],{"class":636,"line":1359},[634,1583,1446],{"class":640},[634,1585,1326],{"class":644},[634,1587,664],{"class":640},[634,1589,1590,1592,1594],{"class":636,"line":1383},[634,1591,1456],{"class":640},[634,1593,1337],{"class":644},[634,1595,664],{"class":640},[634,1597,1598,1600,1602],{"class":636,"line":1388},[634,1599,676],{"class":640},[634,1601,1326],{"class":644},[634,1603,664],{"class":640},[11,1605,1606],{},"The difference is small, but the ownership is clearer: consumers decide when to close; the component decides how its state changes.",[1608,1609],"close-method-demo",{},[27,1611,1613],{"id":1612},"the-first-solution-was-too-high-in-the-stack","The First Solution Was Too High in the Stack",[11,1615,1616],{},"Nuxt UI's Modal and Slideover are built on Reka UI's Dialog primitive. My first Nuxt UI implementation introduced an additional context so the wrapper could expose its internal state-changing function to the relevant slots.",[11,1618,1619],{},"It worked, but it duplicated responsibility already present lower in the stack. The wrapper had to recreate a path from the primitive's state to the consumer-facing slot.",[11,1621,1622],{},"That was the signal to move down a layer.",[11,1624,1625],{},"If Dialog Root exposed a local close function through its default slot, every wrapper could compose that capability without introducing another state channel.",[1627,1628],"close-ownership-figure",{},[27,1630,1632],{"id":1631},"put-the-capability-next-to-the-state","Put the Capability Next to the State",[11,1634,720,1635,1640,1641,1643,1644,1646],{},[281,1636,1639],{"href":1637,"rel":1638},"https:\u002F\u002Fgithub.com\u002Funovue\u002Freka-ui\u002Fpull\u002F1943",[285],"Reka UI Dialog change"," added ",[115,1642,1346],{}," and ",[115,1645,1575],{}," to the Dialog Root's default scoped slot.",[11,1648,1649],{},"A simplified version of the primitive looks like this:",[626,1651,1653],{"className":1252,"code":1652,"language":1254,"meta":299,"style":299},"\u003Cscript setup lang=\"ts\">\nconst open = useVModel(props, 'open', emit, {\n  defaultValue: props.defaultOpen,\n  passive: props.open === undefined,\n})\n\nfunction close() {\n  open.value = false\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cslot :open=\"open\" :close=\"close\" \u002F>\n\u003C\u002Ftemplate>\n",[115,1654,1655,1675,1710,1728,1748,1754,1758,1771,1787,1792,1800,1804,1812,1843],{"__ignoreMap":299},[634,1656,1657,1659,1661,1663,1665,1667,1669,1671,1673],{"class":636,"line":637},[634,1658,641],{"class":640},[634,1660,1263],{"class":644},[634,1662,1266],{"class":648},[634,1664,1269],{"class":648},[634,1666,652],{"class":640},[634,1668,655],{"class":640},[634,1670,1276],{"class":658},[634,1672,655],{"class":640},[634,1674,664],{"class":640},[634,1676,1677,1679,1681,1683,1686,1689,1692,1695,1697,1700,1702,1705,1707],{"class":636,"line":300},[634,1678,1285],{"class":648},[634,1680,1289],{"class":1288},[634,1682,652],{"class":640},[634,1684,1685],{"class":1294}," useVModel",[634,1687,1688],{"class":1288},"(props",[634,1690,1691],{"class":640},",",[634,1693,1694],{"class":640}," '",[634,1696,1346],{"class":658},[634,1698,1699],{"class":640},"'",[634,1701,1691],{"class":640},[634,1703,1704],{"class":1288}," emit",[634,1706,1691],{"class":640},[634,1708,1709],{"class":640}," {\n",[634,1711,1712,1715,1717,1720,1722,1725],{"class":636,"line":673},[634,1713,1714],{"class":644},"  defaultValue",[634,1716,1343],{"class":640},[634,1718,1719],{"class":1288}," props",[634,1721,287],{"class":640},[634,1723,1724],{"class":1288},"defaultOpen",[634,1726,1727],{"class":640},",\n",[634,1729,1730,1733,1735,1737,1739,1742,1745],{"class":636,"line":1315},[634,1731,1732],{"class":644},"  passive",[634,1734,1343],{"class":640},[634,1736,1719],{"class":1288},[634,1738,287],{"class":640},[634,1740,1741],{"class":1288},"open ",[634,1743,1744],{"class":640},"===",[634,1746,1747],{"class":640}," undefined,\n",[634,1749,1750,1752],{"class":636,"line":1321},[634,1751,1544],{"class":640},[634,1753,1304],{"class":1288},[634,1755,1756],{"class":636,"line":1331},[634,1757,1318],{"emptyLinePlaceholder":315},[634,1759,1760,1763,1766,1769],{"class":636,"line":1359},[634,1761,1762],{"class":648},"function",[634,1764,1765],{"class":1294}," close",[634,1767,1768],{"class":640},"()",[634,1770,1709],{"class":640},[634,1772,1773,1776,1778,1781,1784],{"class":636,"line":1383},[634,1774,1775],{"class":1288},"  open",[634,1777,287],{"class":640},[634,1779,1780],{"class":1288},"value",[634,1782,1783],{"class":640}," =",[634,1785,1786],{"class":1301}," false\n",[634,1788,1789],{"class":636,"line":1388},[634,1790,1791],{"class":640},"}\n",[634,1793,1794,1796,1798],{"class":636,"line":1403},[634,1795,676],{"class":640},[634,1797,1263],{"class":644},[634,1799,664],{"class":640},[634,1801,1802],{"class":636,"line":1443},[634,1803,1318],{"emptyLinePlaceholder":315},[634,1805,1806,1808,1810],{"class":636,"line":1453},[634,1807,641],{"class":640},[634,1809,1326],{"class":644},[634,1811,664],{"class":640},[634,1813,1814,1816,1819,1822,1824,1826,1828,1830,1833,1835,1837,1839,1841],{"class":636,"line":1463},[634,1815,1334],{"class":640},[634,1817,1818],{"class":644},"slot",[634,1820,1821],{"class":648}," :open",[634,1823,652],{"class":640},[634,1825,655],{"class":640},[634,1827,1346],{"class":658},[634,1829,655],{"class":640},[634,1831,1832],{"class":648}," :close",[634,1834,652],{"class":640},[634,1836,655],{"class":640},[634,1838,1575],{"class":658},[634,1840,655],{"class":640},[634,1842,1380],{"class":640},[634,1844,1846,1848,1850],{"class":636,"line":1845},14,[634,1847,676],{"class":640},[634,1849,1326],{"class":644},[634,1851,664],{"class":640},[11,1853,1854,1855,1857],{},"The important detail is not the function body. It is that ",[115,1856,1235],{}," uses the same reactive state that already supports controlled and uncontrolled Dialog usage.",[11,1859,1860],{},"The primitive does not need a second context, a template ref, or a parallel event. It exposes a narrow operation over the state it already owns.",[27,1862,1864],{"id":1863},"let-higher-level-components-pass-it-through","Let Higher-Level Components Pass It Through",[11,1866,1867,1868,1873],{},"Once Dialog Root exposed the capability, ",[281,1869,1872],{"href":1870,"rel":1871},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F4219",[285],"Nuxt UI could add it to Modal and Slideover slots"," without maintaining a separate model of dialog state.",[11,1875,1876],{},"This also made nested usage easier to read:",[626,1878,1880],{"className":1252,"code":1879,"language":1254,"meta":299,"style":299},"\u003Ctemplate>\n  \u003CUModal title=\"First modal\">\n    \u003CUButton label=\"Open\" \u002F>\n\n    \u003Ctemplate #footer=\"{ close: closeFirst }\">\n      \u003CUButton label=\"Close first\" @click=\"closeFirst\" \u002F>\n\n      \u003CUModal title=\"Second modal\">\n        \u003CUButton label=\"Open second\" \u002F>\n\n        \u003Ctemplate #footer=\"{ close: closeSecond }\">\n          \u003CUButton label=\"Close second\" @click=\"closeSecond\" \u002F>\n        \u003C\u002Ftemplate>\n      \u003C\u002FUModal>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUModal>\n\u003C\u002Ftemplate>\n",[115,1881,1882,1890,1910,1928,1932,1961,1993,1997,2016,2036,2040,2069,2102,2111,2120,2129,2138],{"__ignoreMap":299},[634,1883,1884,1886,1888],{"class":636,"line":637},[634,1885,641],{"class":640},[634,1887,1326],{"class":644},[634,1889,664],{"class":640},[634,1891,1892,1894,1896,1899,1901,1903,1906,1908],{"class":636,"line":300},[634,1893,1334],{"class":640},[634,1895,1337],{"class":644},[634,1897,1898],{"class":648}," title",[634,1900,652],{"class":640},[634,1902,655],{"class":640},[634,1904,1905],{"class":658},"First modal",[634,1907,655],{"class":640},[634,1909,664],{"class":640},[634,1911,1912,1914,1916,1918,1920,1922,1924,1926],{"class":636,"line":673},[634,1913,1362],{"class":640},[634,1915,1365],{"class":644},[634,1917,1368],{"class":648},[634,1919,652],{"class":640},[634,1921,655],{"class":640},[634,1923,1375],{"class":658},[634,1925,655],{"class":640},[634,1927,1380],{"class":640},[634,1929,1930],{"class":636,"line":1315},[634,1931,1318],{"emptyLinePlaceholder":315},[634,1933,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1955,1957,1959],{"class":636,"line":1321},[634,1935,1362],{"class":640},[634,1937,1326],{"class":644},[634,1939,1395],{"class":640},[634,1941,1398],{"class":648},[634,1943,652],{"class":640},[634,1945,655],{"class":640},[634,1947,1538],{"class":640},[634,1949,1765],{"class":644},[634,1951,1343],{"class":640},[634,1953,1954],{"class":1288}," closeFirst ",[634,1956,1544],{"class":640},[634,1958,655],{"class":640},[634,1960,664],{"class":640},[634,1962,1963,1965,1967,1969,1971,1973,1976,1978,1980,1982,1984,1986,1989,1991],{"class":636,"line":1331},[634,1964,1406],{"class":640},[634,1966,1365],{"class":644},[634,1968,1368],{"class":648},[634,1970,652],{"class":640},[634,1972,655],{"class":640},[634,1974,1975],{"class":658},"Close first",[634,1977,655],{"class":640},[634,1979,1422],{"class":640},[634,1981,1425],{"class":648},[634,1983,652],{"class":640},[634,1985,655],{"class":640},[634,1987,1988],{"class":1288},"closeFirst",[634,1990,655],{"class":640},[634,1992,1380],{"class":640},[634,1994,1995],{"class":636,"line":1359},[634,1996,1318],{"emptyLinePlaceholder":315},[634,1998,1999,2001,2003,2005,2007,2009,2012,2014],{"class":636,"line":1383},[634,2000,1406],{"class":640},[634,2002,1337],{"class":644},[634,2004,1898],{"class":648},[634,2006,652],{"class":640},[634,2008,655],{"class":640},[634,2010,2011],{"class":658},"Second modal",[634,2013,655],{"class":640},[634,2015,664],{"class":640},[634,2017,2018,2021,2023,2025,2027,2029,2032,2034],{"class":636,"line":1388},[634,2019,2020],{"class":640},"        \u003C",[634,2022,1365],{"class":644},[634,2024,1368],{"class":648},[634,2026,652],{"class":640},[634,2028,655],{"class":640},[634,2030,2031],{"class":658},"Open second",[634,2033,655],{"class":640},[634,2035,1380],{"class":640},[634,2037,2038],{"class":636,"line":1403},[634,2039,1318],{"emptyLinePlaceholder":315},[634,2041,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2063,2065,2067],{"class":636,"line":1443},[634,2043,2020],{"class":640},[634,2045,1326],{"class":644},[634,2047,1395],{"class":640},[634,2049,1398],{"class":648},[634,2051,652],{"class":640},[634,2053,655],{"class":640},[634,2055,1538],{"class":640},[634,2057,1765],{"class":644},[634,2059,1343],{"class":640},[634,2061,2062],{"class":1288}," closeSecond ",[634,2064,1544],{"class":640},[634,2066,655],{"class":640},[634,2068,664],{"class":640},[634,2070,2071,2074,2076,2078,2080,2082,2085,2087,2089,2091,2093,2095,2098,2100],{"class":636,"line":1453},[634,2072,2073],{"class":640},"          \u003C",[634,2075,1365],{"class":644},[634,2077,1368],{"class":648},[634,2079,652],{"class":640},[634,2081,655],{"class":640},[634,2083,2084],{"class":658},"Close second",[634,2086,655],{"class":640},[634,2088,1422],{"class":640},[634,2090,1425],{"class":648},[634,2092,652],{"class":640},[634,2094,655],{"class":640},[634,2096,2097],{"class":1288},"closeSecond",[634,2099,655],{"class":640},[634,2101,1380],{"class":640},[634,2103,2104,2107,2109],{"class":636,"line":1463},[634,2105,2106],{"class":640},"        \u003C\u002F",[634,2108,1326],{"class":644},[634,2110,664],{"class":640},[634,2112,2113,2116,2118],{"class":636,"line":1845},[634,2114,2115],{"class":640},"      \u003C\u002F",[634,2117,1337],{"class":644},[634,2119,664],{"class":640},[634,2121,2123,2125,2127],{"class":636,"line":2122},15,[634,2124,1446],{"class":640},[634,2126,1326],{"class":644},[634,2128,664],{"class":640},[634,2130,2132,2134,2136],{"class":636,"line":2131},16,[634,2133,1456],{"class":640},[634,2135,1337],{"class":644},[634,2137,664],{"class":640},[634,2139,2141,2143,2145],{"class":636,"line":2140},17,[634,2142,676],{"class":640},[634,2144,1326],{"class":644},[634,2146,664],{"class":640},[11,2148,2149],{},"Each function is scoped to the instance that provided it. There is no shared ref to accidentally mutate and no need to reach into the component through an imperative template API.",[27,2151,2153],{"id":2152},"apply-the-pattern-consistently","Apply the Pattern Consistently",[11,2155,2156],{},"Dialog was not the only primitive with this interaction shape. Popovers also contain actions that commonly dismiss the surface after a choice is made.",[11,2158,2159,2160,2165],{},"The later ",[281,2161,2164],{"href":2162,"rel":2163},"https:\u002F\u002Fgithub.com\u002Funovue\u002Freka-ui\u002Fpull\u002F1956",[285],"Popover Root contribution"," applied the same default-slot API there. Consistency matters: if two primitives model open state in similar ways, consumers should not need unrelated techniques to close them.",[11,2167,2168,2169,2172,2173,2175,2176,2179],{},"This does not mean every component needs ",[115,2170,2171],{},"open()",", ",[115,2174,1235],{},", and ",[115,2177,2178],{},"toggle()"," methods everywhere. The useful API is the smallest operation that removes recurring state plumbing while preserving the component's normal state path.",[27,2181,2183],{"id":2182},"why-a-function-is-better-than-exposing-the-ref","Why a Function Is Better Than Exposing the Ref",[11,2185,2186,2187,2189],{},"Reka UI could have exposed a mutable ",[115,2188,1346],{}," ref and let consumers assign to it directly. A function creates a better boundary.",[11,2191,2192],{},"It communicates intent, prevents consumers from depending on the exact state representation, and leaves room for the primitive to change what closing means later. The high-level library can pass it through without teaching users about the lower-level implementation.",[11,2194,2195],{},"This distinction becomes valuable as components gain focus restoration, transitions, nested layers, and controlled-state behaviour. Consumers should request the transition rather than reproduce it.",[27,2197,2199],{"id":2198},"what-this-change-taught-me","What This Change Taught Me",[11,2201,2202],{},"Several lessons from this small contribution apply to component libraries more generally:",[67,2204,2205,2211,2217,2223,2229,2235],{},[41,2206,2207,2210],{},[44,2208,2209],{},"Repeated consumer state can indicate a missing component operation."," A boolean ref is simple, but hundreds of identical refs are still API friction.",[41,2212,2213,2216],{},[44,2214,2215],{},"Put operations beside the state they affect."," Wrappers should compose primitive capabilities rather than recreate internal state channels.",[41,2218,2219,2222],{},[44,2220,2221],{},"Scoped slots are useful capability boundaries."," They expose an operation to the correct subtree and component instance.",[41,2224,2225,2228],{},[44,2226,2227],{},"Test nested composition."," A convenient API for one modal can become ambiguous when two are open.",[41,2230,2231,2234],{},[44,2232,2233],{},"Carry patterns across related primitives."," Dialog and Popover should feel related without being artificially forced into one component.",[41,2236,2237,2240],{},[44,2238,2239],{},"Upstream the missing primitive."," A local workaround solves one wrapper; a primitive improvement benefits the surrounding ecosystem.",[27,2242,2244],{"id":2243},"small-apis-reveal-the-shape-of-the-system","Small APIs Reveal the Shape of the System",[11,2246,2247],{},"The final consumer API is one function. Getting there required deciding which library owned the behaviour, how controlled and uncontrolled state should share it, and how wrappers should expose it without duplicating state.",[11,2249,2250,2251,2253],{},"That is why small component changes are often worth examining. The amount of code is not the measure of the design problem. Sometimes a single ",[115,2252,1235],{}," method is enough to show whether the layers in a component system are doing the right jobs.",[887,2255,2256],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}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);}",{"title":299,"searchDepth":300,"depth":300,"links":2258},[2259,2260,2261,2262,2263,2264,2265,2266],{"id":1245,"depth":300,"text":1246},{"id":1612,"depth":300,"text":1613},{"id":1631,"depth":300,"text":1632},{"id":1863,"depth":300,"text":1864},{"id":2152,"depth":300,"text":2153},{"id":2182,"depth":300,"text":2183},{"id":2198,"depth":300,"text":2199},{"id":2243,"depth":300,"text":2244},"2026-06-28","How a small modal API improvement moved through two library layers and removed state-management boilerplate for their consumers.",{},{"title":1224,"description":2268},{"loc":1213},"articles\u002Fone-close-method-across-reka-ui-and-nuxt-ui",[2274,2275,2276,2277,2278],"Vue","Nuxt UI","Reka UI","Component APIs","Open Source","4TgBMIyVJ2QXi5WvyVaBUei31ho2Wu4na8ZsqRvLtvg",{"id":2281,"title":2282,"author":6,"body":2283,"date":4894,"description":4895,"draft":312,"extension":313,"image":6,"meta":4896,"navigation":315,"path":4901,"related":6,"seo":4902,"sitemap":4903,"stem":4904,"tags":4905,"__hash__":4910},"articles\u002Farticles\u002Ftype-safety-with-drizzle.md","Achieving Full-Stack Type Safety with Nuxt, Drizzle, and Directus",{"type":8,"value":2284,"toc":4882},[2285,2288,2292,2295,2299,2302,2321,2325,2328,2339,2342,2346,2349,2508,2511,2663,2667,2674,3483,3486,3809,3813,3816,4380,4384,4387,4789,4793,4824,4828,4831,4845,4849,4852,4855,4858,4879],[11,2286,2287],{},"In my recent Nuxt projects, I've been exploring ways to maintain type safety across the full stack while providing a great content management experience. The combination of Drizzle ORM and Directus CMS has proven to be a powerful solution, especially when integrated with Nuxt's server routes.",[27,2289,2291],{"id":2290},"the-challenge","The Challenge",[11,2293,2294],{},"When building Nuxt applications with TypeScript, maintaining type safety between your database and application code can be challenging. This becomes even more complex when you add a CMS into the mix. Here's how I solved this using Drizzle ORM's schema introspection capabilities with Directus in a Nuxt environment.",[27,2296,2298],{"id":2297},"prerequisites","Prerequisites",[11,2300,2301],{},"Before we begin, you'll need:",[38,2303,2304,2307,2318],{},[41,2305,2306],{},"A Nuxt 3 project",[41,2308,2309,2310],{},"A PostgreSQL database\n",[38,2311,2312,2315],{},[41,2313,2314],{},"This can be any Postgres database you can connect to (local, hosted, or managed)",[41,2316,2317],{},"Examples include: Supabase, Neon, Railway, or your own PostgreSQL instance",[41,2319,2320],{},"Basic understanding of TypeScript",[27,2322,2324],{"id":2323},"database-setup-options","Database Setup Options",[11,2326,2327],{},"While this guide uses Directus as an example, you can use any PostgreSQL database setup. Directus provides a nice admin UI and API out of the box, but you could also:",[38,2329,2330,2333,2336],{},[41,2331,2332],{},"Connect directly to your Postgres database",[41,2334,2335],{},"Use other database management tools",[41,2337,2338],{},"Set up your own custom admin interface",[11,2340,2341],{},"The type safety benefits of Drizzle work with any Postgres setup - choose what works best for your project!",[27,2343,2345],{"id":2344},"setting-up-the-foundation","Setting Up the Foundation",[11,2347,2348],{},"First, I configure Drizzle to connect to my Directus-managed database:",[626,2350,2354],{"className":2351,"code":2352,"language":2353,"meta":299,"style":299},"language-typescript shiki shiki-themes material-theme-palenight","\u002F\u002F drizzle.config.ts\nimport { defineConfig } from 'drizzle-kit'\nimport 'dotenv\u002Fconfig'\n\nexport default defineConfig({\n  out: '.\u002Fdrizzle',\n  schema: '.\u002Fdb\u002Fschema.ts',\n  dialect: 'postgresql',\n  dbCredentials: {\n    url: process.env.DATABASE_URL!,\n  },\n})\n","typescript",[115,2355,2356,2361,2387,2398,2402,2417,2433,2449,2465,2474,2497,2502],{"__ignoreMap":299},[634,2357,2358],{"class":636,"line":637},[634,2359,2360],{"class":669},"\u002F\u002F drizzle.config.ts\n",[634,2362,2363,2367,2370,2373,2376,2379,2381,2384],{"class":636,"line":300},[634,2364,2366],{"class":2365},"s6cf3","import",[634,2368,2369],{"class":640}," {",[634,2371,2372],{"class":1288}," defineConfig",[634,2374,2375],{"class":640}," }",[634,2377,2378],{"class":2365}," from",[634,2380,1694],{"class":640},[634,2382,2383],{"class":658},"drizzle-kit",[634,2385,2386],{"class":640},"'\n",[634,2388,2389,2391,2393,2396],{"class":636,"line":673},[634,2390,2366],{"class":2365},[634,2392,1694],{"class":640},[634,2394,2395],{"class":658},"dotenv\u002Fconfig",[634,2397,2386],{"class":640},[634,2399,2400],{"class":636,"line":1315},[634,2401,1318],{"emptyLinePlaceholder":315},[634,2403,2404,2407,2410,2412,2414],{"class":636,"line":1321},[634,2405,2406],{"class":2365},"export",[634,2408,2409],{"class":2365}," default",[634,2411,2372],{"class":1294},[634,2413,1298],{"class":1288},[634,2415,2416],{"class":640},"{\n",[634,2418,2419,2422,2424,2426,2429,2431],{"class":636,"line":1331},[634,2420,2421],{"class":644},"  out",[634,2423,1343],{"class":640},[634,2425,1694],{"class":640},[634,2427,2428],{"class":658},".\u002Fdrizzle",[634,2430,1699],{"class":640},[634,2432,1727],{"class":640},[634,2434,2435,2438,2440,2442,2445,2447],{"class":636,"line":1359},[634,2436,2437],{"class":644},"  schema",[634,2439,1343],{"class":640},[634,2441,1694],{"class":640},[634,2443,2444],{"class":658},".\u002Fdb\u002Fschema.ts",[634,2446,1699],{"class":640},[634,2448,1727],{"class":640},[634,2450,2451,2454,2456,2458,2461,2463],{"class":636,"line":1383},[634,2452,2453],{"class":644},"  dialect",[634,2455,1343],{"class":640},[634,2457,1694],{"class":640},[634,2459,2460],{"class":658},"postgresql",[634,2462,1699],{"class":640},[634,2464,1727],{"class":640},[634,2466,2467,2470,2472],{"class":636,"line":1388},[634,2468,2469],{"class":644},"  dbCredentials",[634,2471,1343],{"class":640},[634,2473,1709],{"class":640},[634,2475,2476,2479,2481,2484,2486,2489,2491,2494],{"class":636,"line":1403},[634,2477,2478],{"class":644},"    url",[634,2480,1343],{"class":640},[634,2482,2483],{"class":1288}," process",[634,2485,287],{"class":640},[634,2487,2488],{"class":1288},"env",[634,2490,287],{"class":640},[634,2492,2493],{"class":1288},"DATABASE_URL",[634,2495,2496],{"class":640},"!,\n",[634,2498,2499],{"class":636,"line":1443},[634,2500,2501],{"class":640},"  },\n",[634,2503,2504,2506],{"class":636,"line":1453},[634,2505,1544],{"class":640},[634,2507,1304],{"class":1288},[11,2509,2510],{},"Then, I create a type-safe database instance that Nuxt can use:",[626,2512,2514],{"className":2351,"code":2513,"language":2353,"meta":299,"style":299},"\u002F\u002F server\u002Fdb.ts\nimport { drizzle } from 'drizzle-orm\u002Fnode-postgres'\nimport * as relations from '..\u002Fdrizzle\u002Frelations'\nimport * as schema from '..\u002Fdrizzle\u002Fschema'\nimport 'dotenv\u002Fconfig'\n\nexport const db = drizzle(import.meta.env.DATABASE_URL!, {\n  schema: { ...relations, ...schema }\n})\n",[115,2515,2516,2521,2541,2564,2584,2594,2598,2634,2657],{"__ignoreMap":299},[634,2517,2518],{"class":636,"line":637},[634,2519,2520],{"class":669},"\u002F\u002F server\u002Fdb.ts\n",[634,2522,2523,2525,2527,2530,2532,2534,2536,2539],{"class":636,"line":300},[634,2524,2366],{"class":2365},[634,2526,2369],{"class":640},[634,2528,2529],{"class":1288}," drizzle",[634,2531,2375],{"class":640},[634,2533,2378],{"class":2365},[634,2535,1694],{"class":640},[634,2537,2538],{"class":658},"drizzle-orm\u002Fnode-postgres",[634,2540,2386],{"class":640},[634,2542,2543,2545,2548,2551,2554,2557,2559,2562],{"class":636,"line":673},[634,2544,2366],{"class":2365},[634,2546,2547],{"class":640}," *",[634,2549,2550],{"class":2365}," as",[634,2552,2553],{"class":1288}," relations ",[634,2555,2556],{"class":2365},"from",[634,2558,1694],{"class":640},[634,2560,2561],{"class":658},"..\u002Fdrizzle\u002Frelations",[634,2563,2386],{"class":640},[634,2565,2566,2568,2570,2572,2575,2577,2579,2582],{"class":636,"line":1315},[634,2567,2366],{"class":2365},[634,2569,2547],{"class":640},[634,2571,2550],{"class":2365},[634,2573,2574],{"class":1288}," schema ",[634,2576,2556],{"class":2365},[634,2578,1694],{"class":640},[634,2580,2581],{"class":658},"..\u002Fdrizzle\u002Fschema",[634,2583,2386],{"class":640},[634,2585,2586,2588,2590,2592],{"class":636,"line":1321},[634,2587,2366],{"class":2365},[634,2589,1694],{"class":640},[634,2591,2395],{"class":658},[634,2593,2386],{"class":640},[634,2595,2596],{"class":636,"line":1331},[634,2597,1318],{"emptyLinePlaceholder":315},[634,2599,2600,2602,2605,2608,2610,2612,2614,2616,2618,2621,2623,2625,2627,2629,2632],{"class":636,"line":1359},[634,2601,2406],{"class":2365},[634,2603,2604],{"class":648}," const",[634,2606,2607],{"class":1288}," db ",[634,2609,652],{"class":640},[634,2611,2529],{"class":1294},[634,2613,1298],{"class":1288},[634,2615,2366],{"class":2365},[634,2617,287],{"class":640},[634,2619,2620],{"class":1288},"meta",[634,2622,287],{"class":640},[634,2624,2488],{"class":1288},[634,2626,287],{"class":640},[634,2628,2493],{"class":1288},[634,2630,2631],{"class":640},"!,",[634,2633,1709],{"class":640},[634,2635,2636,2638,2640,2642,2645,2648,2650,2652,2655],{"class":636,"line":1383},[634,2637,2437],{"class":644},[634,2639,1343],{"class":640},[634,2641,2369],{"class":640},[634,2643,2644],{"class":640}," ...",[634,2646,2647],{"class":1288},"relations",[634,2649,1691],{"class":640},[634,2651,2644],{"class":640},[634,2653,2654],{"class":1288},"schema ",[634,2656,1791],{"class":640},[634,2658,2659,2661],{"class":636,"line":1388},[634,2660,1544],{"class":640},[634,2662,1304],{"class":1288},[27,2664,2666],{"id":2665},"schema-introspection","Schema Introspection",[11,2668,2669,2670,2673],{},"When I run ",[115,2671,2672],{},"drizzle-kit pull",", Drizzle automatically generates TypeScript definitions from my Directus database. What's great is that these types work seamlessly with Nuxt's server routes:",[626,2675,2677],{"className":2351,"code":2676,"language":2353,"meta":299,"style":299},"\u002F\u002F drizzle\u002Fschema.ts\nexport const post = pgTable('Post', {\n  id: uuid('id').primaryKey().notNull(),\n  status: varchar('status').default('draft').notNull(),\n  categoryId: uuid('category_id'),\n  title: varchar('title', { length: 255 }).notNull(),\n  slug: varchar('slug', { length: 255 }).notNull(),\n  content: text('content'),\n  dateCreated: timestamp('date_created').defaultNow().notNull(),\n  dateUpdated: timestamp('date_updated').defaultNow().notNull(),\n  userCreated: uuid('user_created').notNull(),\n}, table => [\n  \u002F\u002F Foreign keys are automatically detected from the database\n  foreignKey({\n    columns: [table.categoryId],\n    foreignColumns: [category.id],\n    name: 'post_category_id_foreign'\n  })\n])\n\nexport const postTranslations = pgTable('Post_translations', {\n  id: uuid('id').primaryKey().notNull(),\n  postId: uuid('post_id').notNull(),\n  languagesCode: varchar('languages_code', { length: 255 }).notNull(),\n  title: varchar('title', { length: 255 }),\n  content: text('content'),\n}, table => [\n  \u002F\u002F These relations are automatically generated based on your database structure\n  foreignKey({\n    columns: [table.postId],\n    foreignColumns: [post.id],\n    name: 'post_translations_post_id_foreign',\n    onDelete: 'cascade'\n  }),\n  foreignKey({\n    columns: [table.languagesCode],\n    foreignColumns: [languages.code],\n    name: 'post_translations_languages_code_foreign',\n    onDelete: 'cascade'\n  }),\n])\n",[115,2678,2679,2684,2711,2749,2794,2816,2859,2899,2922,2958,2992,3020,3035,3040,3049,3066,3083,3097,3105,3111,3116,3143,3176,3205,3246,3279,3300,3311,3317,3326,3342,3358,3374,3389,3398,3407,3423,3440,3456,3469,3478],{"__ignoreMap":299},[634,2680,2681],{"class":636,"line":637},[634,2682,2683],{"class":669},"\u002F\u002F drizzle\u002Fschema.ts\n",[634,2685,2686,2688,2690,2693,2695,2698,2700,2702,2705,2707,2709],{"class":636,"line":300},[634,2687,2406],{"class":2365},[634,2689,2604],{"class":648},[634,2691,2692],{"class":1288}," post ",[634,2694,652],{"class":640},[634,2696,2697],{"class":1294}," pgTable",[634,2699,1298],{"class":1288},[634,2701,1699],{"class":640},[634,2703,2704],{"class":658},"Post",[634,2706,1699],{"class":640},[634,2708,1691],{"class":640},[634,2710,1709],{"class":640},[634,2712,2713,2716,2718,2721,2723,2725,2728,2730,2733,2735,2738,2740,2742,2745,2747],{"class":636,"line":673},[634,2714,2715],{"class":644},"  id",[634,2717,1343],{"class":640},[634,2719,2720],{"class":1294}," uuid",[634,2722,1298],{"class":1288},[634,2724,1699],{"class":640},[634,2726,2727],{"class":658},"id",[634,2729,1699],{"class":640},[634,2731,2732],{"class":1288},")",[634,2734,287],{"class":640},[634,2736,2737],{"class":1294},"primaryKey",[634,2739,1768],{"class":1288},[634,2741,287],{"class":640},[634,2743,2744],{"class":1294},"notNull",[634,2746,1768],{"class":1288},[634,2748,1727],{"class":640},[634,2750,2751,2754,2756,2759,2761,2763,2766,2768,2770,2772,2775,2777,2779,2782,2784,2786,2788,2790,2792],{"class":636,"line":1315},[634,2752,2753],{"class":644},"  status",[634,2755,1343],{"class":640},[634,2757,2758],{"class":1294}," varchar",[634,2760,1298],{"class":1288},[634,2762,1699],{"class":640},[634,2764,2765],{"class":658},"status",[634,2767,1699],{"class":640},[634,2769,2732],{"class":1288},[634,2771,287],{"class":640},[634,2773,2774],{"class":1294},"default",[634,2776,1298],{"class":1288},[634,2778,1699],{"class":640},[634,2780,2781],{"class":658},"draft",[634,2783,1699],{"class":640},[634,2785,2732],{"class":1288},[634,2787,287],{"class":640},[634,2789,2744],{"class":1294},[634,2791,1768],{"class":1288},[634,2793,1727],{"class":640},[634,2795,2796,2799,2801,2803,2805,2807,2810,2812,2814],{"class":636,"line":1321},[634,2797,2798],{"class":644},"  categoryId",[634,2800,1343],{"class":640},[634,2802,2720],{"class":1294},[634,2804,1298],{"class":1288},[634,2806,1699],{"class":640},[634,2808,2809],{"class":658},"category_id",[634,2811,1699],{"class":640},[634,2813,2732],{"class":1288},[634,2815,1727],{"class":640},[634,2817,2818,2821,2823,2825,2827,2829,2832,2834,2836,2838,2841,2843,2847,2849,2851,2853,2855,2857],{"class":636,"line":1331},[634,2819,2820],{"class":644},"  title",[634,2822,1343],{"class":640},[634,2824,2758],{"class":1294},[634,2826,1298],{"class":1288},[634,2828,1699],{"class":640},[634,2830,2831],{"class":658},"title",[634,2833,1699],{"class":640},[634,2835,1691],{"class":640},[634,2837,2369],{"class":640},[634,2839,2840],{"class":644}," length",[634,2842,1343],{"class":640},[634,2844,2846],{"class":2845},"sx098"," 255",[634,2848,2375],{"class":640},[634,2850,2732],{"class":1288},[634,2852,287],{"class":640},[634,2854,2744],{"class":1294},[634,2856,1768],{"class":1288},[634,2858,1727],{"class":640},[634,2860,2861,2864,2866,2868,2870,2872,2875,2877,2879,2881,2883,2885,2887,2889,2891,2893,2895,2897],{"class":636,"line":1359},[634,2862,2863],{"class":644},"  slug",[634,2865,1343],{"class":640},[634,2867,2758],{"class":1294},[634,2869,1298],{"class":1288},[634,2871,1699],{"class":640},[634,2873,2874],{"class":658},"slug",[634,2876,1699],{"class":640},[634,2878,1691],{"class":640},[634,2880,2369],{"class":640},[634,2882,2840],{"class":644},[634,2884,1343],{"class":640},[634,2886,2846],{"class":2845},[634,2888,2375],{"class":640},[634,2890,2732],{"class":1288},[634,2892,287],{"class":640},[634,2894,2744],{"class":1294},[634,2896,1768],{"class":1288},[634,2898,1727],{"class":640},[634,2900,2901,2904,2906,2909,2911,2913,2916,2918,2920],{"class":636,"line":1383},[634,2902,2903],{"class":644},"  content",[634,2905,1343],{"class":640},[634,2907,2908],{"class":1294}," text",[634,2910,1298],{"class":1288},[634,2912,1699],{"class":640},[634,2914,2915],{"class":658},"content",[634,2917,1699],{"class":640},[634,2919,2732],{"class":1288},[634,2921,1727],{"class":640},[634,2923,2924,2927,2929,2932,2934,2936,2939,2941,2943,2945,2948,2950,2952,2954,2956],{"class":636,"line":1388},[634,2925,2926],{"class":644},"  dateCreated",[634,2928,1343],{"class":640},[634,2930,2931],{"class":1294}," timestamp",[634,2933,1298],{"class":1288},[634,2935,1699],{"class":640},[634,2937,2938],{"class":658},"date_created",[634,2940,1699],{"class":640},[634,2942,2732],{"class":1288},[634,2944,287],{"class":640},[634,2946,2947],{"class":1294},"defaultNow",[634,2949,1768],{"class":1288},[634,2951,287],{"class":640},[634,2953,2744],{"class":1294},[634,2955,1768],{"class":1288},[634,2957,1727],{"class":640},[634,2959,2960,2963,2965,2967,2969,2971,2974,2976,2978,2980,2982,2984,2986,2988,2990],{"class":636,"line":1403},[634,2961,2962],{"class":644},"  dateUpdated",[634,2964,1343],{"class":640},[634,2966,2931],{"class":1294},[634,2968,1298],{"class":1288},[634,2970,1699],{"class":640},[634,2972,2973],{"class":658},"date_updated",[634,2975,1699],{"class":640},[634,2977,2732],{"class":1288},[634,2979,287],{"class":640},[634,2981,2947],{"class":1294},[634,2983,1768],{"class":1288},[634,2985,287],{"class":640},[634,2987,2744],{"class":1294},[634,2989,1768],{"class":1288},[634,2991,1727],{"class":640},[634,2993,2994,2997,2999,3001,3003,3005,3008,3010,3012,3014,3016,3018],{"class":636,"line":1443},[634,2995,2996],{"class":644},"  userCreated",[634,2998,1343],{"class":640},[634,3000,2720],{"class":1294},[634,3002,1298],{"class":1288},[634,3004,1699],{"class":640},[634,3006,3007],{"class":658},"user_created",[634,3009,1699],{"class":640},[634,3011,2732],{"class":1288},[634,3013,287],{"class":640},[634,3015,2744],{"class":1294},[634,3017,1768],{"class":1288},[634,3019,1727],{"class":640},[634,3021,3022,3025,3029,3032],{"class":636,"line":1453},[634,3023,3024],{"class":640},"},",[634,3026,3028],{"class":3027},"s7ZW3"," table",[634,3030,3031],{"class":648}," =>",[634,3033,3034],{"class":1288}," [\n",[634,3036,3037],{"class":636,"line":1463},[634,3038,3039],{"class":669},"  \u002F\u002F Foreign keys are automatically detected from the database\n",[634,3041,3042,3045,3047],{"class":636,"line":1845},[634,3043,3044],{"class":1294},"  foreignKey",[634,3046,1298],{"class":1288},[634,3048,2416],{"class":640},[634,3050,3051,3054,3056,3059,3061,3064],{"class":636,"line":2122},[634,3052,3053],{"class":644},"    columns",[634,3055,1343],{"class":640},[634,3057,3058],{"class":1288}," [table",[634,3060,287],{"class":640},[634,3062,3063],{"class":1288},"categoryId]",[634,3065,1727],{"class":640},[634,3067,3068,3071,3073,3076,3078,3081],{"class":636,"line":2131},[634,3069,3070],{"class":644},"    foreignColumns",[634,3072,1343],{"class":640},[634,3074,3075],{"class":1288}," [category",[634,3077,287],{"class":640},[634,3079,3080],{"class":1288},"id]",[634,3082,1727],{"class":640},[634,3084,3085,3088,3090,3092,3095],{"class":636,"line":2140},[634,3086,3087],{"class":644},"    name",[634,3089,1343],{"class":640},[634,3091,1694],{"class":640},[634,3093,3094],{"class":658},"post_category_id_foreign",[634,3096,2386],{"class":640},[634,3098,3100,3103],{"class":636,"line":3099},18,[634,3101,3102],{"class":640},"  }",[634,3104,1304],{"class":1288},[634,3106,3108],{"class":636,"line":3107},19,[634,3109,3110],{"class":1288},"])\n",[634,3112,3114],{"class":636,"line":3113},20,[634,3115,1318],{"emptyLinePlaceholder":315},[634,3117,3119,3121,3123,3126,3128,3130,3132,3134,3137,3139,3141],{"class":636,"line":3118},21,[634,3120,2406],{"class":2365},[634,3122,2604],{"class":648},[634,3124,3125],{"class":1288}," postTranslations ",[634,3127,652],{"class":640},[634,3129,2697],{"class":1294},[634,3131,1298],{"class":1288},[634,3133,1699],{"class":640},[634,3135,3136],{"class":658},"Post_translations",[634,3138,1699],{"class":640},[634,3140,1691],{"class":640},[634,3142,1709],{"class":640},[634,3144,3146,3148,3150,3152,3154,3156,3158,3160,3162,3164,3166,3168,3170,3172,3174],{"class":636,"line":3145},22,[634,3147,2715],{"class":644},[634,3149,1343],{"class":640},[634,3151,2720],{"class":1294},[634,3153,1298],{"class":1288},[634,3155,1699],{"class":640},[634,3157,2727],{"class":658},[634,3159,1699],{"class":640},[634,3161,2732],{"class":1288},[634,3163,287],{"class":640},[634,3165,2737],{"class":1294},[634,3167,1768],{"class":1288},[634,3169,287],{"class":640},[634,3171,2744],{"class":1294},[634,3173,1768],{"class":1288},[634,3175,1727],{"class":640},[634,3177,3179,3182,3184,3186,3188,3190,3193,3195,3197,3199,3201,3203],{"class":636,"line":3178},23,[634,3180,3181],{"class":644},"  postId",[634,3183,1343],{"class":640},[634,3185,2720],{"class":1294},[634,3187,1298],{"class":1288},[634,3189,1699],{"class":640},[634,3191,3192],{"class":658},"post_id",[634,3194,1699],{"class":640},[634,3196,2732],{"class":1288},[634,3198,287],{"class":640},[634,3200,2744],{"class":1294},[634,3202,1768],{"class":1288},[634,3204,1727],{"class":640},[634,3206,3208,3211,3213,3215,3217,3219,3222,3224,3226,3228,3230,3232,3234,3236,3238,3240,3242,3244],{"class":636,"line":3207},24,[634,3209,3210],{"class":644},"  languagesCode",[634,3212,1343],{"class":640},[634,3214,2758],{"class":1294},[634,3216,1298],{"class":1288},[634,3218,1699],{"class":640},[634,3220,3221],{"class":658},"languages_code",[634,3223,1699],{"class":640},[634,3225,1691],{"class":640},[634,3227,2369],{"class":640},[634,3229,2840],{"class":644},[634,3231,1343],{"class":640},[634,3233,2846],{"class":2845},[634,3235,2375],{"class":640},[634,3237,2732],{"class":1288},[634,3239,287],{"class":640},[634,3241,2744],{"class":1294},[634,3243,1768],{"class":1288},[634,3245,1727],{"class":640},[634,3247,3249,3251,3253,3255,3257,3259,3261,3263,3265,3267,3269,3271,3273,3275,3277],{"class":636,"line":3248},25,[634,3250,2820],{"class":644},[634,3252,1343],{"class":640},[634,3254,2758],{"class":1294},[634,3256,1298],{"class":1288},[634,3258,1699],{"class":640},[634,3260,2831],{"class":658},[634,3262,1699],{"class":640},[634,3264,1691],{"class":640},[634,3266,2369],{"class":640},[634,3268,2840],{"class":644},[634,3270,1343],{"class":640},[634,3272,2846],{"class":2845},[634,3274,2375],{"class":640},[634,3276,2732],{"class":1288},[634,3278,1727],{"class":640},[634,3280,3282,3284,3286,3288,3290,3292,3294,3296,3298],{"class":636,"line":3281},26,[634,3283,2903],{"class":644},[634,3285,1343],{"class":640},[634,3287,2908],{"class":1294},[634,3289,1298],{"class":1288},[634,3291,1699],{"class":640},[634,3293,2915],{"class":658},[634,3295,1699],{"class":640},[634,3297,2732],{"class":1288},[634,3299,1727],{"class":640},[634,3301,3303,3305,3307,3309],{"class":636,"line":3302},27,[634,3304,3024],{"class":640},[634,3306,3028],{"class":3027},[634,3308,3031],{"class":648},[634,3310,3034],{"class":1288},[634,3312,3314],{"class":636,"line":3313},28,[634,3315,3316],{"class":669},"  \u002F\u002F These relations are automatically generated based on your database structure\n",[634,3318,3320,3322,3324],{"class":636,"line":3319},29,[634,3321,3044],{"class":1294},[634,3323,1298],{"class":1288},[634,3325,2416],{"class":640},[634,3327,3329,3331,3333,3335,3337,3340],{"class":636,"line":3328},30,[634,3330,3053],{"class":644},[634,3332,1343],{"class":640},[634,3334,3058],{"class":1288},[634,3336,287],{"class":640},[634,3338,3339],{"class":1288},"postId]",[634,3341,1727],{"class":640},[634,3343,3345,3347,3349,3352,3354,3356],{"class":636,"line":3344},31,[634,3346,3070],{"class":644},[634,3348,1343],{"class":640},[634,3350,3351],{"class":1288}," [post",[634,3353,287],{"class":640},[634,3355,3080],{"class":1288},[634,3357,1727],{"class":640},[634,3359,3361,3363,3365,3367,3370,3372],{"class":636,"line":3360},32,[634,3362,3087],{"class":644},[634,3364,1343],{"class":640},[634,3366,1694],{"class":640},[634,3368,3369],{"class":658},"post_translations_post_id_foreign",[634,3371,1699],{"class":640},[634,3373,1727],{"class":640},[634,3375,3377,3380,3382,3384,3387],{"class":636,"line":3376},33,[634,3378,3379],{"class":644},"    onDelete",[634,3381,1343],{"class":640},[634,3383,1694],{"class":640},[634,3385,3386],{"class":658},"cascade",[634,3388,2386],{"class":640},[634,3390,3392,3394,3396],{"class":636,"line":3391},34,[634,3393,3102],{"class":640},[634,3395,2732],{"class":1288},[634,3397,1727],{"class":640},[634,3399,3401,3403,3405],{"class":636,"line":3400},35,[634,3402,3044],{"class":1294},[634,3404,1298],{"class":1288},[634,3406,2416],{"class":640},[634,3408,3410,3412,3414,3416,3418,3421],{"class":636,"line":3409},36,[634,3411,3053],{"class":644},[634,3413,1343],{"class":640},[634,3415,3058],{"class":1288},[634,3417,287],{"class":640},[634,3419,3420],{"class":1288},"languagesCode]",[634,3422,1727],{"class":640},[634,3424,3426,3428,3430,3433,3435,3438],{"class":636,"line":3425},37,[634,3427,3070],{"class":644},[634,3429,1343],{"class":640},[634,3431,3432],{"class":1288}," [languages",[634,3434,287],{"class":640},[634,3436,3437],{"class":1288},"code]",[634,3439,1727],{"class":640},[634,3441,3443,3445,3447,3449,3452,3454],{"class":636,"line":3442},38,[634,3444,3087],{"class":644},[634,3446,1343],{"class":640},[634,3448,1694],{"class":640},[634,3450,3451],{"class":658},"post_translations_languages_code_foreign",[634,3453,1699],{"class":640},[634,3455,1727],{"class":640},[634,3457,3459,3461,3463,3465,3467],{"class":636,"line":3458},39,[634,3460,3379],{"class":644},[634,3462,1343],{"class":640},[634,3464,1694],{"class":640},[634,3466,3386],{"class":658},[634,3468,2386],{"class":640},[634,3470,3472,3474,3476],{"class":636,"line":3471},40,[634,3473,3102],{"class":640},[634,3475,2732],{"class":1288},[634,3477,1727],{"class":640},[634,3479,3481],{"class":636,"line":3480},41,[634,3482,3110],{"class":1288},[11,3484,3485],{},"The relations file is also automatically generated:",[626,3487,3489],{"className":2351,"code":3488,"language":2353,"meta":299,"style":299},"\u002F\u002F drizzle\u002Frelations.ts\nexport const postRelations = relations(post, ({ one, many }) => ({\n  category: one(category, {\n    fields: [post.categoryId],\n    references: [category.id],\n  }),\n  translations: many(postTranslations),\n  author: one(directusUsers, {\n    fields: [post.userCreated],\n    references: [directusUsers.id],\n  }),\n}))\n\nexport const postTranslationsRelations = relations(postTranslations, ({ one }) => ({\n  post: one(post, {\n    fields: [postTranslations.postId],\n    references: [post.id],\n  }),\n  language: one(languages, {\n    fields: [postTranslations.languagesCode],\n    references: [languages.code],\n  }),\n}))\n",[115,3490,3491,3496,3536,3552,3567,3582,3590,3604,3620,3635,3650,3658,3665,3669,3699,3714,3729,3743,3751,3767,3781,3795,3803],{"__ignoreMap":299},[634,3492,3493],{"class":636,"line":637},[634,3494,3495],{"class":669},"\u002F\u002F drizzle\u002Frelations.ts\n",[634,3497,3498,3500,3502,3505,3507,3510,3513,3515,3518,3521,3523,3526,3529,3531,3534],{"class":636,"line":300},[634,3499,2406],{"class":2365},[634,3501,2604],{"class":648},[634,3503,3504],{"class":1288}," postRelations ",[634,3506,652],{"class":640},[634,3508,3509],{"class":1294}," relations",[634,3511,3512],{"class":1288},"(post",[634,3514,1691],{"class":640},[634,3516,3517],{"class":640}," ({",[634,3519,3520],{"class":3027}," one",[634,3522,1691],{"class":640},[634,3524,3525],{"class":3027}," many",[634,3527,3528],{"class":640}," })",[634,3530,3031],{"class":648},[634,3532,3533],{"class":1288}," (",[634,3535,2416],{"class":640},[634,3537,3538,3541,3543,3545,3548,3550],{"class":636,"line":673},[634,3539,3540],{"class":644},"  category",[634,3542,1343],{"class":640},[634,3544,3520],{"class":1294},[634,3546,3547],{"class":1288},"(category",[634,3549,1691],{"class":640},[634,3551,1709],{"class":640},[634,3553,3554,3557,3559,3561,3563,3565],{"class":636,"line":1315},[634,3555,3556],{"class":644},"    fields",[634,3558,1343],{"class":640},[634,3560,3351],{"class":1288},[634,3562,287],{"class":640},[634,3564,3063],{"class":1288},[634,3566,1727],{"class":640},[634,3568,3569,3572,3574,3576,3578,3580],{"class":636,"line":1321},[634,3570,3571],{"class":644},"    references",[634,3573,1343],{"class":640},[634,3575,3075],{"class":1288},[634,3577,287],{"class":640},[634,3579,3080],{"class":1288},[634,3581,1727],{"class":640},[634,3583,3584,3586,3588],{"class":636,"line":1331},[634,3585,3102],{"class":640},[634,3587,2732],{"class":1288},[634,3589,1727],{"class":640},[634,3591,3592,3595,3597,3599,3602],{"class":636,"line":1359},[634,3593,3594],{"class":644},"  translations",[634,3596,1343],{"class":640},[634,3598,3525],{"class":1294},[634,3600,3601],{"class":1288},"(postTranslations)",[634,3603,1727],{"class":640},[634,3605,3606,3609,3611,3613,3616,3618],{"class":636,"line":1383},[634,3607,3608],{"class":644},"  author",[634,3610,1343],{"class":640},[634,3612,3520],{"class":1294},[634,3614,3615],{"class":1288},"(directusUsers",[634,3617,1691],{"class":640},[634,3619,1709],{"class":640},[634,3621,3622,3624,3626,3628,3630,3633],{"class":636,"line":1388},[634,3623,3556],{"class":644},[634,3625,1343],{"class":640},[634,3627,3351],{"class":1288},[634,3629,287],{"class":640},[634,3631,3632],{"class":1288},"userCreated]",[634,3634,1727],{"class":640},[634,3636,3637,3639,3641,3644,3646,3648],{"class":636,"line":1403},[634,3638,3571],{"class":644},[634,3640,1343],{"class":640},[634,3642,3643],{"class":1288}," [directusUsers",[634,3645,287],{"class":640},[634,3647,3080],{"class":1288},[634,3649,1727],{"class":640},[634,3651,3652,3654,3656],{"class":636,"line":1443},[634,3653,3102],{"class":640},[634,3655,2732],{"class":1288},[634,3657,1727],{"class":640},[634,3659,3660,3662],{"class":636,"line":1453},[634,3661,1544],{"class":640},[634,3663,3664],{"class":1288},"))\n",[634,3666,3667],{"class":636,"line":1463},[634,3668,1318],{"emptyLinePlaceholder":315},[634,3670,3671,3673,3675,3678,3680,3682,3685,3687,3689,3691,3693,3695,3697],{"class":636,"line":1845},[634,3672,2406],{"class":2365},[634,3674,2604],{"class":648},[634,3676,3677],{"class":1288}," postTranslationsRelations ",[634,3679,652],{"class":640},[634,3681,3509],{"class":1294},[634,3683,3684],{"class":1288},"(postTranslations",[634,3686,1691],{"class":640},[634,3688,3517],{"class":640},[634,3690,3520],{"class":3027},[634,3692,3528],{"class":640},[634,3694,3031],{"class":648},[634,3696,3533],{"class":1288},[634,3698,2416],{"class":640},[634,3700,3701,3704,3706,3708,3710,3712],{"class":636,"line":2122},[634,3702,3703],{"class":644},"  post",[634,3705,1343],{"class":640},[634,3707,3520],{"class":1294},[634,3709,3512],{"class":1288},[634,3711,1691],{"class":640},[634,3713,1709],{"class":640},[634,3715,3716,3718,3720,3723,3725,3727],{"class":636,"line":2131},[634,3717,3556],{"class":644},[634,3719,1343],{"class":640},[634,3721,3722],{"class":1288}," [postTranslations",[634,3724,287],{"class":640},[634,3726,3339],{"class":1288},[634,3728,1727],{"class":640},[634,3730,3731,3733,3735,3737,3739,3741],{"class":636,"line":2140},[634,3732,3571],{"class":644},[634,3734,1343],{"class":640},[634,3736,3351],{"class":1288},[634,3738,287],{"class":640},[634,3740,3080],{"class":1288},[634,3742,1727],{"class":640},[634,3744,3745,3747,3749],{"class":636,"line":3099},[634,3746,3102],{"class":640},[634,3748,2732],{"class":1288},[634,3750,1727],{"class":640},[634,3752,3753,3756,3758,3760,3763,3765],{"class":636,"line":3107},[634,3754,3755],{"class":644},"  language",[634,3757,1343],{"class":640},[634,3759,3520],{"class":1294},[634,3761,3762],{"class":1288},"(languages",[634,3764,1691],{"class":640},[634,3766,1709],{"class":640},[634,3768,3769,3771,3773,3775,3777,3779],{"class":636,"line":3113},[634,3770,3556],{"class":644},[634,3772,1343],{"class":640},[634,3774,3722],{"class":1288},[634,3776,287],{"class":640},[634,3778,3420],{"class":1288},[634,3780,1727],{"class":640},[634,3782,3783,3785,3787,3789,3791,3793],{"class":636,"line":3118},[634,3784,3571],{"class":644},[634,3786,1343],{"class":640},[634,3788,3432],{"class":1288},[634,3790,287],{"class":640},[634,3792,3437],{"class":1288},[634,3794,1727],{"class":640},[634,3796,3797,3799,3801],{"class":636,"line":3145},[634,3798,3102],{"class":640},[634,3800,2732],{"class":1288},[634,3802,1727],{"class":640},[634,3804,3805,3807],{"class":636,"line":3178},[634,3806,1544],{"class":640},[634,3808,3664],{"class":1288},[27,3810,3812],{"id":3811},"using-drizzle-in-nuxt-server-routes","Using Drizzle in Nuxt Server Routes",[11,3814,3815],{},"These introspected drizzle types make it easy to create type-safe queries in your Nuxt server routes:",[626,3817,3819],{"className":2351,"code":3818,"language":2353,"meta":299,"style":299},"\u002F\u002F server\u002Fapi\u002Fposts\u002F[id].get.ts\nimport { eq } from 'drizzle-orm'\nimport { z } from 'zod'\nimport { post, postTranslations } from '~\u002Fdrizzle\u002Fschema'\n\nconst QuerySchema = z.object({\n  lang: z.string().optional(),\n})\n\nconst ParamsSchema = z.object({\n  id: z.string().uuid('Invalid post ID'),\n})\n\nexport default defineEventHandler(async (event) => {\n  const params = await getValidatedRouterParams(event, ParamsSchema.parse)\n  const query = await getValidatedQuery(event, QuerySchema.parse)\n\n  \u002F\u002F TypeScript knows exactly what relations are available\n  const post = await db.query.post.findFirst({\n    where: eq(post.id, params.id),\n    with: {\n      category: true,\n      translations: {\n        where: eq(postTranslations.languagesCode, query.lang),\n      },\n      author: {\n        columns: {\n          id: true,\n          firstName: true,\n          lastName: true,\n        }\n      }\n    }\n  })\n\n  if (!post)\n    throw createError({ statusCode: 404, message: 'Post not found' })\n\n  return post\n})\n",[115,3820,3821,3826,3846,3866,3891,3895,3915,3940,3946,3950,3969,4001,4007,4011,4036,4068,4097,4101,4106,4138,4167,4176,4188,4197,4229,4234,4243,4252,4263,4274,4285,4290,4295,4300,4306,4310,4324,4362,4366,4374],{"__ignoreMap":299},[634,3822,3823],{"class":636,"line":637},[634,3824,3825],{"class":669},"\u002F\u002F server\u002Fapi\u002Fposts\u002F[id].get.ts\n",[634,3827,3828,3830,3832,3835,3837,3839,3841,3844],{"class":636,"line":300},[634,3829,2366],{"class":2365},[634,3831,2369],{"class":640},[634,3833,3834],{"class":1288}," eq",[634,3836,2375],{"class":640},[634,3838,2378],{"class":2365},[634,3840,1694],{"class":640},[634,3842,3843],{"class":658},"drizzle-orm",[634,3845,2386],{"class":640},[634,3847,3848,3850,3852,3855,3857,3859,3861,3864],{"class":636,"line":673},[634,3849,2366],{"class":2365},[634,3851,2369],{"class":640},[634,3853,3854],{"class":1288}," z",[634,3856,2375],{"class":640},[634,3858,2378],{"class":2365},[634,3860,1694],{"class":640},[634,3862,3863],{"class":658},"zod",[634,3865,2386],{"class":640},[634,3867,3868,3870,3872,3875,3877,3880,3882,3884,3886,3889],{"class":636,"line":1315},[634,3869,2366],{"class":2365},[634,3871,2369],{"class":640},[634,3873,3874],{"class":1288}," post",[634,3876,1691],{"class":640},[634,3878,3879],{"class":1288}," postTranslations",[634,3881,2375],{"class":640},[634,3883,2378],{"class":2365},[634,3885,1694],{"class":640},[634,3887,3888],{"class":658},"~\u002Fdrizzle\u002Fschema",[634,3890,2386],{"class":640},[634,3892,3893],{"class":636,"line":1321},[634,3894,1318],{"emptyLinePlaceholder":315},[634,3896,3897,3899,3902,3904,3906,3908,3911,3913],{"class":636,"line":1331},[634,3898,1285],{"class":648},[634,3900,3901],{"class":1288}," QuerySchema ",[634,3903,652],{"class":640},[634,3905,3854],{"class":1288},[634,3907,287],{"class":640},[634,3909,3910],{"class":1294},"object",[634,3912,1298],{"class":1288},[634,3914,2416],{"class":640},[634,3916,3917,3920,3922,3924,3926,3929,3931,3933,3936,3938],{"class":636,"line":1359},[634,3918,3919],{"class":644},"  lang",[634,3921,1343],{"class":640},[634,3923,3854],{"class":1288},[634,3925,287],{"class":640},[634,3927,3928],{"class":1294},"string",[634,3930,1768],{"class":1288},[634,3932,287],{"class":640},[634,3934,3935],{"class":1294},"optional",[634,3937,1768],{"class":1288},[634,3939,1727],{"class":640},[634,3941,3942,3944],{"class":636,"line":1383},[634,3943,1544],{"class":640},[634,3945,1304],{"class":1288},[634,3947,3948],{"class":636,"line":1388},[634,3949,1318],{"emptyLinePlaceholder":315},[634,3951,3952,3954,3957,3959,3961,3963,3965,3967],{"class":636,"line":1403},[634,3953,1285],{"class":648},[634,3955,3956],{"class":1288}," ParamsSchema ",[634,3958,652],{"class":640},[634,3960,3854],{"class":1288},[634,3962,287],{"class":640},[634,3964,3910],{"class":1294},[634,3966,1298],{"class":1288},[634,3968,2416],{"class":640},[634,3970,3971,3973,3975,3977,3979,3981,3983,3985,3988,3990,3992,3995,3997,3999],{"class":636,"line":1443},[634,3972,2715],{"class":644},[634,3974,1343],{"class":640},[634,3976,3854],{"class":1288},[634,3978,287],{"class":640},[634,3980,3928],{"class":1294},[634,3982,1768],{"class":1288},[634,3984,287],{"class":640},[634,3986,3987],{"class":1294},"uuid",[634,3989,1298],{"class":1288},[634,3991,1699],{"class":640},[634,3993,3994],{"class":658},"Invalid post ID",[634,3996,1699],{"class":640},[634,3998,2732],{"class":1288},[634,4000,1727],{"class":640},[634,4002,4003,4005],{"class":636,"line":1453},[634,4004,1544],{"class":640},[634,4006,1304],{"class":1288},[634,4008,4009],{"class":636,"line":1463},[634,4010,1318],{"emptyLinePlaceholder":315},[634,4012,4013,4015,4017,4020,4022,4025,4027,4030,4032,4034],{"class":636,"line":1845},[634,4014,2406],{"class":2365},[634,4016,2409],{"class":2365},[634,4018,4019],{"class":1294}," defineEventHandler",[634,4021,1298],{"class":1288},[634,4023,4024],{"class":648},"async",[634,4026,3533],{"class":640},[634,4028,4029],{"class":3027},"event",[634,4031,2732],{"class":640},[634,4033,3031],{"class":648},[634,4035,1709],{"class":640},[634,4037,4038,4041,4044,4046,4049,4052,4054,4056,4058,4061,4063,4066],{"class":636,"line":2122},[634,4039,4040],{"class":648},"  const",[634,4042,4043],{"class":1288}," params",[634,4045,1783],{"class":640},[634,4047,4048],{"class":2365}," await",[634,4050,4051],{"class":1294}," getValidatedRouterParams",[634,4053,1298],{"class":644},[634,4055,4029],{"class":1288},[634,4057,1691],{"class":640},[634,4059,4060],{"class":1288}," ParamsSchema",[634,4062,287],{"class":640},[634,4064,4065],{"class":1288},"parse",[634,4067,1304],{"class":644},[634,4069,4070,4072,4075,4077,4079,4082,4084,4086,4088,4091,4093,4095],{"class":636,"line":2131},[634,4071,4040],{"class":648},[634,4073,4074],{"class":1288}," query",[634,4076,1783],{"class":640},[634,4078,4048],{"class":2365},[634,4080,4081],{"class":1294}," getValidatedQuery",[634,4083,1298],{"class":644},[634,4085,4029],{"class":1288},[634,4087,1691],{"class":640},[634,4089,4090],{"class":1288}," QuerySchema",[634,4092,287],{"class":640},[634,4094,4065],{"class":1288},[634,4096,1304],{"class":644},[634,4098,4099],{"class":636,"line":2140},[634,4100,1318],{"emptyLinePlaceholder":315},[634,4102,4103],{"class":636,"line":3099},[634,4104,4105],{"class":669},"  \u002F\u002F TypeScript knows exactly what relations are available\n",[634,4107,4108,4110,4112,4114,4116,4119,4121,4124,4126,4129,4131,4134,4136],{"class":636,"line":3107},[634,4109,4040],{"class":648},[634,4111,3874],{"class":1288},[634,4113,1783],{"class":640},[634,4115,4048],{"class":2365},[634,4117,4118],{"class":1288}," db",[634,4120,287],{"class":640},[634,4122,4123],{"class":1288},"query",[634,4125,287],{"class":640},[634,4127,4128],{"class":1288},"post",[634,4130,287],{"class":640},[634,4132,4133],{"class":1294},"findFirst",[634,4135,1298],{"class":644},[634,4137,2416],{"class":640},[634,4139,4140,4143,4145,4147,4149,4151,4153,4155,4157,4159,4161,4163,4165],{"class":636,"line":3113},[634,4141,4142],{"class":644},"    where",[634,4144,1343],{"class":640},[634,4146,3834],{"class":1294},[634,4148,1298],{"class":644},[634,4150,4128],{"class":1288},[634,4152,287],{"class":640},[634,4154,2727],{"class":1288},[634,4156,1691],{"class":640},[634,4158,4043],{"class":1288},[634,4160,287],{"class":640},[634,4162,2727],{"class":1288},[634,4164,2732],{"class":644},[634,4166,1727],{"class":640},[634,4168,4169,4172,4174],{"class":636,"line":3118},[634,4170,4171],{"class":644},"    with",[634,4173,1343],{"class":640},[634,4175,1709],{"class":640},[634,4177,4178,4181,4183,4186],{"class":636,"line":3145},[634,4179,4180],{"class":644},"      category",[634,4182,1343],{"class":640},[634,4184,4185],{"class":1301}," true",[634,4187,1727],{"class":640},[634,4189,4190,4193,4195],{"class":636,"line":3178},[634,4191,4192],{"class":644},"      translations",[634,4194,1343],{"class":640},[634,4196,1709],{"class":640},[634,4198,4199,4202,4204,4206,4208,4211,4213,4216,4218,4220,4222,4225,4227],{"class":636,"line":3207},[634,4200,4201],{"class":644},"        where",[634,4203,1343],{"class":640},[634,4205,3834],{"class":1294},[634,4207,1298],{"class":644},[634,4209,4210],{"class":1288},"postTranslations",[634,4212,287],{"class":640},[634,4214,4215],{"class":1288},"languagesCode",[634,4217,1691],{"class":640},[634,4219,4074],{"class":1288},[634,4221,287],{"class":640},[634,4223,4224],{"class":1288},"lang",[634,4226,2732],{"class":644},[634,4228,1727],{"class":640},[634,4230,4231],{"class":636,"line":3248},[634,4232,4233],{"class":640},"      },\n",[634,4235,4236,4239,4241],{"class":636,"line":3281},[634,4237,4238],{"class":644},"      author",[634,4240,1343],{"class":640},[634,4242,1709],{"class":640},[634,4244,4245,4248,4250],{"class":636,"line":3302},[634,4246,4247],{"class":644},"        columns",[634,4249,1343],{"class":640},[634,4251,1709],{"class":640},[634,4253,4254,4257,4259,4261],{"class":636,"line":3313},[634,4255,4256],{"class":644},"          id",[634,4258,1343],{"class":640},[634,4260,4185],{"class":1301},[634,4262,1727],{"class":640},[634,4264,4265,4268,4270,4272],{"class":636,"line":3319},[634,4266,4267],{"class":644},"          firstName",[634,4269,1343],{"class":640},[634,4271,4185],{"class":1301},[634,4273,1727],{"class":640},[634,4275,4276,4279,4281,4283],{"class":636,"line":3328},[634,4277,4278],{"class":644},"          lastName",[634,4280,1343],{"class":640},[634,4282,4185],{"class":1301},[634,4284,1727],{"class":640},[634,4286,4287],{"class":636,"line":3344},[634,4288,4289],{"class":640},"        }\n",[634,4291,4292],{"class":636,"line":3360},[634,4293,4294],{"class":640},"      }\n",[634,4296,4297],{"class":636,"line":3376},[634,4298,4299],{"class":640},"    }\n",[634,4301,4302,4304],{"class":636,"line":3391},[634,4303,3102],{"class":640},[634,4305,1304],{"class":644},[634,4307,4308],{"class":636,"line":3400},[634,4309,1318],{"emptyLinePlaceholder":315},[634,4311,4312,4315,4317,4320,4322],{"class":636,"line":3409},[634,4313,4314],{"class":2365},"  if",[634,4316,3533],{"class":644},[634,4318,4319],{"class":640},"!",[634,4321,4128],{"class":1288},[634,4323,1304],{"class":644},[634,4325,4326,4329,4332,4334,4336,4339,4341,4344,4346,4349,4351,4353,4356,4358,4360],{"class":636,"line":3425},[634,4327,4328],{"class":2365},"    throw",[634,4330,4331],{"class":1294}," createError",[634,4333,1298],{"class":644},[634,4335,1538],{"class":640},[634,4337,4338],{"class":644}," statusCode",[634,4340,1343],{"class":640},[634,4342,4343],{"class":2845}," 404",[634,4345,1691],{"class":640},[634,4347,4348],{"class":644}," message",[634,4350,1343],{"class":640},[634,4352,1694],{"class":640},[634,4354,4355],{"class":658},"Post not found",[634,4357,1699],{"class":640},[634,4359,2375],{"class":640},[634,4361,1304],{"class":644},[634,4363,4364],{"class":636,"line":3442},[634,4365,1318],{"emptyLinePlaceholder":315},[634,4367,4368,4371],{"class":636,"line":3458},[634,4369,4370],{"class":2365},"  return",[634,4372,4373],{"class":1288}," post\n",[634,4375,4376,4378],{"class":636,"line":3471},[634,4377,1544],{"class":640},[634,4379,1304],{"class":1288},[27,4381,4383],{"id":4382},"using-related-data-in-components","Using Related Data in Components",[11,4385,4386],{},"The type safety extends to your Nuxt components:",[626,4388,4390],{"className":1252,"code":4389,"language":1254,"meta":299,"style":299},"\u003Cscript setup lang=\"ts\">\nconst route = useRoute()\nconst { data: post, error } = await useFetch(`\u002Fapi\u002Fposts\u002F${route.params.id}`, {\n  \u002F\u002F TypeScript knows the exact shape of your data\n  transform: post => ({\n    ...post,\n    formattedDate: new Date(post.dateCreated).toLocaleDateString()\n  })\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cp v-if=\"error\" class=\"error\">\n      {{ error.statusCode === 404 ? 'Post not found' : 'Error loading post' }}\n    \u003C\u002Fp>\n\n    \u003Carticle v-else-if=\"post\">\n      \u003Ch1>{{ post.title }}\u003C\u002Fh1>\n      \u003Cdiv class=\"metadata\">\n        \u003Cspan>Category: {{ post.category?.name }}\u003C\u002Fspan>\n        \u003Cspan>Author: {{ post.author.firstName }} {{ post.author.lastName }}\u003C\u002Fspan>\n        \u003Ctime>{{ post.formattedDate }}\u003C\u002Ftime>\n      \u003C\u002Fdiv>\n      \u003Cdiv v-html=\"post.content\" \u002F>\n    \u003C\u002Farticle>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[115,4391,4392,4412,4427,4484,4489,4504,4513,4540,4546,4552,4560,4564,4572,4580,4610,4615,4623,4627,4647,4666,4685,4702,4719,4737,4745,4765,4773,4781],{"__ignoreMap":299},[634,4393,4394,4396,4398,4400,4402,4404,4406,4408,4410],{"class":636,"line":637},[634,4395,641],{"class":640},[634,4397,1263],{"class":644},[634,4399,1266],{"class":648},[634,4401,1269],{"class":648},[634,4403,652],{"class":640},[634,4405,655],{"class":640},[634,4407,1276],{"class":658},[634,4409,655],{"class":640},[634,4411,664],{"class":640},[634,4413,4414,4416,4419,4421,4424],{"class":636,"line":300},[634,4415,1285],{"class":648},[634,4417,4418],{"class":1288}," route ",[634,4420,652],{"class":640},[634,4422,4423],{"class":1294}," useRoute",[634,4425,4426],{"class":1288},"()\n",[634,4428,4429,4431,4433,4436,4438,4440,4442,4445,4447,4449,4451,4454,4456,4459,4462,4465,4468,4470,4473,4475,4477,4480,4482],{"class":636,"line":673},[634,4430,1285],{"class":648},[634,4432,2369],{"class":640},[634,4434,4435],{"class":644}," data",[634,4437,1343],{"class":640},[634,4439,3874],{"class":1288},[634,4441,1691],{"class":640},[634,4443,4444],{"class":1288}," error ",[634,4446,1544],{"class":640},[634,4448,1783],{"class":640},[634,4450,4048],{"class":2365},[634,4452,4453],{"class":1294}," useFetch",[634,4455,1298],{"class":1288},[634,4457,4458],{"class":640},"`",[634,4460,4461],{"class":658},"\u002Fapi\u002Fposts\u002F",[634,4463,4464],{"class":640},"${",[634,4466,4467],{"class":1288},"route",[634,4469,287],{"class":640},[634,4471,4472],{"class":1288},"params",[634,4474,287],{"class":640},[634,4476,2727],{"class":1288},[634,4478,4479],{"class":640},"}`",[634,4481,1691],{"class":640},[634,4483,1709],{"class":640},[634,4485,4486],{"class":636,"line":1315},[634,4487,4488],{"class":669},"  \u002F\u002F TypeScript knows the exact shape of your data\n",[634,4490,4491,4494,4496,4498,4500,4502],{"class":636,"line":1321},[634,4492,4493],{"class":1294},"  transform",[634,4495,1343],{"class":640},[634,4497,3874],{"class":3027},[634,4499,3031],{"class":648},[634,4501,3533],{"class":1288},[634,4503,2416],{"class":640},[634,4505,4506,4509,4511],{"class":636,"line":1331},[634,4507,4508],{"class":640},"    ...",[634,4510,4128],{"class":1288},[634,4512,1727],{"class":640},[634,4514,4515,4518,4520,4523,4526,4528,4530,4533,4535,4538],{"class":636,"line":1359},[634,4516,4517],{"class":644},"    formattedDate",[634,4519,1343],{"class":640},[634,4521,4522],{"class":640}," new",[634,4524,4525],{"class":1294}," Date",[634,4527,3512],{"class":1288},[634,4529,287],{"class":640},[634,4531,4532],{"class":1288},"dateCreated)",[634,4534,287],{"class":640},[634,4536,4537],{"class":1294},"toLocaleDateString",[634,4539,4426],{"class":1288},[634,4541,4542,4544],{"class":636,"line":1383},[634,4543,3102],{"class":640},[634,4545,1304],{"class":1288},[634,4547,4548,4550],{"class":636,"line":1388},[634,4549,1544],{"class":640},[634,4551,1304],{"class":1288},[634,4553,4554,4556,4558],{"class":636,"line":1403},[634,4555,676],{"class":640},[634,4557,1263],{"class":644},[634,4559,664],{"class":640},[634,4561,4562],{"class":636,"line":1443},[634,4563,1318],{"emptyLinePlaceholder":315},[634,4565,4566,4568,4570],{"class":636,"line":1453},[634,4567,641],{"class":640},[634,4569,1326],{"class":644},[634,4571,664],{"class":640},[634,4573,4574,4576,4578],{"class":636,"line":1463},[634,4575,1334],{"class":640},[634,4577,645],{"class":644},[634,4579,664],{"class":640},[634,4581,4582,4584,4586,4589,4591,4593,4596,4598,4600,4602,4604,4606,4608],{"class":636,"line":1845},[634,4583,1362],{"class":640},[634,4585,11],{"class":644},[634,4587,4588],{"class":648}," v-if",[634,4590,652],{"class":640},[634,4592,655],{"class":640},[634,4594,4595],{"class":658},"error",[634,4597,655],{"class":640},[634,4599,649],{"class":648},[634,4601,652],{"class":640},[634,4603,655],{"class":640},[634,4605,4595],{"class":658},[634,4607,655],{"class":640},[634,4609,664],{"class":640},[634,4611,4612],{"class":636,"line":2122},[634,4613,4614],{"class":1288},"      {{ error.statusCode === 404 ? 'Post not found' : 'Error loading post' }}\n",[634,4616,4617,4619,4621],{"class":636,"line":2131},[634,4618,1446],{"class":640},[634,4620,11],{"class":644},[634,4622,664],{"class":640},[634,4624,4625],{"class":636,"line":2140},[634,4626,1318],{"emptyLinePlaceholder":315},[634,4628,4629,4631,4634,4637,4639,4641,4643,4645],{"class":636,"line":3099},[634,4630,1362],{"class":640},[634,4632,4633],{"class":644},"article",[634,4635,4636],{"class":648}," v-else-if",[634,4638,652],{"class":640},[634,4640,655],{"class":640},[634,4642,4128],{"class":658},[634,4644,655],{"class":640},[634,4646,664],{"class":640},[634,4648,4649,4651,4654,4657,4660,4662,4664],{"class":636,"line":3107},[634,4650,1406],{"class":640},[634,4652,4653],{"class":644},"h1",[634,4655,4656],{"class":640},">",[634,4658,4659],{"class":1288},"{{ post.title }}",[634,4661,676],{"class":640},[634,4663,4653],{"class":644},[634,4665,664],{"class":640},[634,4667,4668,4670,4672,4674,4676,4678,4681,4683],{"class":636,"line":3113},[634,4669,1406],{"class":640},[634,4671,645],{"class":644},[634,4673,649],{"class":648},[634,4675,652],{"class":640},[634,4677,655],{"class":640},[634,4679,4680],{"class":658},"metadata",[634,4682,655],{"class":640},[634,4684,664],{"class":640},[634,4686,4687,4689,4691,4693,4696,4698,4700],{"class":636,"line":3118},[634,4688,2020],{"class":640},[634,4690,634],{"class":644},[634,4692,4656],{"class":640},[634,4694,4695],{"class":1288},"Category: {{ post.category?.name }}",[634,4697,676],{"class":640},[634,4699,634],{"class":644},[634,4701,664],{"class":640},[634,4703,4704,4706,4708,4710,4713,4715,4717],{"class":636,"line":3145},[634,4705,2020],{"class":640},[634,4707,634],{"class":644},[634,4709,4656],{"class":640},[634,4711,4712],{"class":1288},"Author: {{ post.author.firstName }} {{ post.author.lastName }}",[634,4714,676],{"class":640},[634,4716,634],{"class":644},[634,4718,664],{"class":640},[634,4720,4721,4723,4726,4728,4731,4733,4735],{"class":636,"line":3178},[634,4722,2020],{"class":640},[634,4724,4725],{"class":644},"time",[634,4727,4656],{"class":640},[634,4729,4730],{"class":1288},"{{ post.formattedDate }}",[634,4732,676],{"class":640},[634,4734,4725],{"class":644},[634,4736,664],{"class":640},[634,4738,4739,4741,4743],{"class":636,"line":3207},[634,4740,2115],{"class":640},[634,4742,645],{"class":644},[634,4744,664],{"class":640},[634,4746,4747,4749,4751,4754,4756,4758,4761,4763],{"class":636,"line":3248},[634,4748,1406],{"class":640},[634,4750,645],{"class":644},[634,4752,4753],{"class":648}," v-html",[634,4755,652],{"class":640},[634,4757,655],{"class":640},[634,4759,4760],{"class":658},"post.content",[634,4762,655],{"class":640},[634,4764,1380],{"class":640},[634,4766,4767,4769,4771],{"class":636,"line":3281},[634,4768,1446],{"class":640},[634,4770,4633],{"class":644},[634,4772,664],{"class":640},[634,4774,4775,4777,4779],{"class":636,"line":3302},[634,4776,1456],{"class":640},[634,4778,645],{"class":644},[634,4780,664],{"class":640},[634,4782,4783,4785,4787],{"class":636,"line":3313},[634,4784,676],{"class":640},[634,4786,1326],{"class":644},[634,4788,664],{"class":640},[27,4790,4792],{"id":4791},"why-this-approach-works-great-with-nuxt","Why This Approach Works Great with Nuxt",[67,4794,4795,4801,4807,4813,4818],{},[41,4796,4797,4800],{},[44,4798,4799],{},"Nuxt Server Routes",": Drizzle integrates perfectly with Nuxt's server directory structure",[41,4802,4803,4806],{},[44,4804,4805],{},"Auto-imports",": Nuxt's auto-imports work seamlessly with your type-safe services",[41,4808,4809,4812],{},[44,4810,4811],{},"Full-Stack Type Safety",": Types flow from database through API to components",[41,4814,4815,4817],{},[44,4816,912],{},": Great IDE support and error catching",[41,4819,4820,4823],{},[44,4821,4822],{},"Content Management",": Non-technical users can use Directus's interface",[27,4825,4827],{"id":4826},"real-world-benefits","Real-World Benefits",[11,4829,4830],{},"In my Nuxt projects, this setup has:",[38,4832,4833,4836,4839,4842],{},[41,4834,4835],{},"Caught type errors before they reach production",[41,4837,4838],{},"Made refactoring much safer",[41,4840,4841],{},"Improved development speed",[41,4843,4844],{},"Provided great content management for clients",[27,4846,4848],{"id":4847},"conclusion","Conclusion",[11,4850,4851],{},"The combination of Nuxt, Drizzle ORM, and Directus has transformed how I build type-safe applications. The automatic schema introspection and Nuxt's server capabilities mean I spend less time writing types and more time building features.",[11,4853,4854],{},"If you're building a Nuxt application that needs both type safety and content management, I highly recommend this stack. The initial setup effort pays off many times over in development efficiency and code reliability.",[11,4856,4857],{},"Remember to:",[67,4859,4860,4866,4869,4876],{},[41,4861,4862,4863,4865],{},"Run ",[115,4864,2672],{}," after schema changes in Directus",[41,4867,4868],{},"Leverage Nuxt's server directory structure",[41,4870,4871,4872,4875],{},"Use ",[115,4873,4874],{},"useFetch"," for type-safe API calls",[41,4877,4878],{},"Let TypeScript guide you through using your database correctly!",[887,4880,4881],{},"html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}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 pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}",{"title":299,"searchDepth":300,"depth":300,"links":4883},[4884,4885,4886,4887,4888,4889,4890,4891,4892,4893],{"id":2290,"depth":300,"text":2291},{"id":2297,"depth":300,"text":2298},{"id":2323,"depth":300,"text":2324},{"id":2344,"depth":300,"text":2345},{"id":2665,"depth":300,"text":2666},{"id":3811,"depth":300,"text":3812},{"id":4382,"depth":300,"text":4383},{"id":4791,"depth":300,"text":4792},{"id":4826,"depth":300,"text":4827},{"id":4847,"depth":300,"text":4848},"2025-01-15","Learn how I leveraged Drizzle ORM's schema introspection with Directus CMS in a Nuxt application for end-to-end type safety",{"categories":4897},[4898,4899,4900],"Nuxt","Database","TypeScript","\u002Farticles\u002Ftype-safety-with-drizzle",{"title":2282,"description":4895},{"loc":4901},"articles\u002Ftype-safety-with-drizzle",[4906,4907,4908,4909],"Drizzle ORM","Directus","Type Safety","Database Schema","JBHrhOq_AojTJS0pTfPVd-qyx3rvGdoj2gKXcNcFlmY",{"id":4912,"title":4913,"author":6,"body":4914,"date":5045,"description":5046,"draft":312,"extension":313,"image":6,"meta":5047,"navigation":315,"path":5050,"related":6,"seo":5051,"sitemap":5052,"stem":5053,"tags":5054,"__hash__":5058},"articles\u002Farticles\u002Ffailures-of-web-components.md","Why Web Components Might Be Setting You Up for Failure",{"type":8,"value":4915,"toc":5032},[4916,4919,4923,4926,4929,4933,4936,4939,4943,4946,4949,4953,4956,4960,4963,4966,4970,4973,4977,4980,5003,5007,5014,5018,5021,5024,5026,5029],[11,4917,4918],{},"Web components are a relatively new technology that allows developers to create custom HTML elements that can be used in web pages. They were introduced as a way to make it easier to build reusable UI components, but in reality, they might be setting you up for failure.",[27,4920,4922],{"id":4921},"the-promise-of-web-components","The Promise of Web Components",[11,4924,4925],{},"Web components were hailed as a revolutionary technology that would make it easier to build complex web applications. They promised to provide a way to create reusable UI components that could be easily composed together to build complex interfaces.",[11,4927,4928],{},"However, in reality, web components have failed to deliver on this promise. They are often difficult to use, and their limitations can make it hard to build complex applications.",[27,4930,4932],{"id":4931},"the-limitations-of-web-components","The Limitations of Web Components",[11,4934,4935],{},"One of the biggest limitations of web components is that they don't work well with all major frameworks. For example, Vue, React, and Angular have their own component models that are not compatible with web components. This means that if you want to use web components with one of these frameworks, you'll most often have problems working with them.",[11,4937,4938],{},"Additionally, web components often have accessibility issues. Because they are custom HTML elements, they can be difficult for screen readers and other accessibility tools to interpret. This means that web components can make it harder to build accessible web applications.",[27,4940,4942],{"id":4941},"working-with-the-shadow-dom","Working with the Shadow DOM",[11,4944,4945],{},"Another challenge of working with web components is the Shadow DOM. The Shadow DOM is a separate DOM tree that is used to render the content of a web component. While it provides a way to encapsulate the component's content and styles, it can also make it difficult to work with the component's DOM.",[11,4947,4948],{},"For example, if you want to access an element inside a web component, you'll need to use the shadowRoot property to access the Shadow DOM. This can make it harder to work with the component's DOM, especially if you're used to working with the regular DOM.",[27,4950,4952],{"id":4951},"the-lack-of-state-management","The Lack of State Management",[11,4954,4955],{},"Another major limitation of web components is that most web component frameworks don't offer state management. This means that you'll need to use another framework, such as Vue, React, or Angular, to manage the state of your application. This can add complexity to your code and make it harder to maintain.",[27,4957,4959],{"id":4958},"server-side-rendering","Server-Side Rendering",[11,4961,4962],{},"Another limitation of web components is that they don't work well with server-side rendering (SSR). SSR is a technique that allows you to render your web application on the server, rather than on the client. This can improve the performance and SEO of your application.",[11,4964,4965],{},"However, web components are not designed to work with SSR. They are meant to be used on the client-side, and they don't provide a way to render the component on the server. This means that if you want to use SSR with web components, you'll have to use a workaround or a third-party library.",[27,4967,4969],{"id":4968},"the-complexity-of-web-components","The Complexity of Web Components",[11,4971,4972],{},"Most companies that are using custom Web Components often don't create them to a high enough standard to avoid creating issues for other teams when they consume the created web components. Often you need a team of highly skilled developers to create well designed web components, which often isn't the case when starting out and working with newer technologies.",[27,4974,4976],{"id":4975},"what-others-are-saying","What Others Are Saying",[11,4978,4979],{},"Many developers and industry experts have expressed similar concerns about web components. Here are a few examples:",[38,4981,4982,4993],{},[41,4983,4984,4987,4988],{},[44,4985,4986],{},"\"Why I don't use web components\""," by Rich Harris (2019) - ",[281,4989,4992],{"href":4990,"rel":4991},"https:\u002F\u002Fdev.to\u002Frichharris\u002Fwhy-i-don-t-use-web-components-2cia",[285],"Read more",[41,4994,4995,4998,4999],{},[44,4996,4997],{},"\"Maybe Web Components are not the Future?\""," by Ryan Carniato (2020) - ",[281,5000,4992],{"href":5001,"rel":5002},"https:\u002F\u002Fdev.to\u002Fryansolid\u002Fmaybe-web-components-are-not-the-future-hfh",[285],[27,5004,5006],{"id":5005},"the-vue-documentation","The Vue Documentation",[11,5008,5009,5010],{},"The Vue documentation also provides some guidance on using web components with Vue. According to the documentation, \"Some developers believe that framework-proprietary component models should be avoided, and that exclusively using Custom Elements makes an application \"future-proof\". Here we will try to explain why we believe that this is an overly simplistic take on the problem.\" ",[281,5011,4992],{"href":5012,"rel":5013},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Fweb-components#web-components-vs-vue-components",[285],[27,5015,5017],{"id":5016},"the-better-alternative","The Better Alternative",[11,5019,5020],{},"So, what's the better alternative to web components? In many cases, the answer is to use existing technologies, such as Vue, React, or Angular, to build complex web applications.",[11,5022,5023],{},"These technologies have been around for longer, and they have a more established ecosystem of tools and libraries. They are also often easier to learn and use than web components.",[1073,5025,4848],{"id":4847},[11,5027,5028],{},"In conclusion, web components might be setting you up for failure. They are a complex technology that can add complexity to your code, make it harder to debug, and introduce new security risks. They also don't work well with all major frameworks, have accessibility issues, and lack state management.",[11,5030,5031],{},"Instead of using web components, consider using existing technologies, such as Vue, React, or Angular, to build complex web applications. These technologies have a more established ecosystem of tools and libraries, and they are often easier to learn and use than web components.",{"title":299,"searchDepth":300,"depth":300,"links":5033},[5034,5035,5036,5037,5038,5039,5040,5041,5042],{"id":4921,"depth":300,"text":4922},{"id":4931,"depth":300,"text":4932},{"id":4941,"depth":300,"text":4942},{"id":4951,"depth":300,"text":4952},{"id":4958,"depth":300,"text":4959},{"id":4968,"depth":300,"text":4969},{"id":4975,"depth":300,"text":4976},{"id":5005,"depth":300,"text":5006},{"id":5016,"depth":300,"text":5017,"children":5043},[5044],{"id":4847,"depth":673,"text":4848},"2024-03-29","Learn why web components might be setting you up for failure, and what alternatives you can use instead.",{"categories":5048},[5049,323],"Web Development","\u002Farticles\u002Ffailures-of-web-components",{"title":4913,"description":5046},{"loc":5050},"articles\u002Ffailures-of-web-components",[323,5049,5055,2274,5056,5057],"Failure","React","Angular","ISDSeqABPUCwAQtpWCIFNO0X-HjWoOgXo9zSI5iXpFA",{"id":5060,"title":5061,"author":6,"body":5062,"date":5857,"description":5858,"draft":312,"extension":313,"image":6,"meta":5859,"navigation":315,"path":5862,"related":6,"seo":5863,"sitemap":5864,"stem":5865,"tags":5866,"__hash__":5871},"articles\u002Farticles\u002Fhandling-try-catch-in-api-with-result-pattern.md","Handling Try-Catch in API Endpoints with the Result Pattern",{"type":8,"value":5063,"toc":5850},[5064,5067,5071,5074,5078,5081,5488,5501,5505,5804,5824,5828,5831,5842,5844,5847],[11,5065,5066],{},"When building API endpoints, handling errors and exceptions is a crucial aspect of ensuring a robust and maintainable application. One common approach is to use try-catch blocks to catch and handle errors. However, this approach can lead to cluttered code and make it difficult to handle errors consistently across the application. In this post, we'll explore how to use the Result pattern to handle try-catch in API endpoints, providing a more elegant and scalable solution.",[27,5068,5070],{"id":5069},"what-is-the-result-pattern","What is the Result Pattern?",[11,5072,5073],{},"The Result pattern is a software design pattern that returns an object containing the outcome of an operation, along with any data it returned. This pattern allows for a more explicit and consistent way of handling errors and exceptions, making code more robust and maintainable.",[27,5075,5077],{"id":5076},"implementing-the-result-pattern-in-api-endpoints","Implementing the Result Pattern in API Endpoints",[11,5079,5080],{},"Here's an example of how to implement the Result pattern in an API endpoint using TypeScript:",[626,5082,5084],{"className":2351,"code":5083,"language":2353,"meta":299,"style":299},"export type ApiError = 'InvalidJson' | 'RequestFailed' | 'NetworkError'\n\nexport type Result\u003CT, E extends ApiError>\n  = | { ok: true, data: T }\n    | { ok: false, error: E }\n\nasync function getResult\u003CT>(\n  apiCall: () => Promise\u003CResponse>\n): Promise\u003CResult\u003CT, ApiError>> {\n  try {\n    const response = await apiCall()\n    if (!response.ok)\n      throw new Error('Not OK!')\n    try {\n      const data = await response.json()\n      return { ok: true, data }\n    }\n    catch (jsonError) {\n      return { ok: false, error: 'InvalidJson' }\n    }\n  }\n  catch (error) {\n    return { ok: false, error: 'RequestFailed' }\n  }\n}\n",[115,5085,5086,5125,5129,5155,5183,5208,5212,5229,5251,5276,5283,5300,5319,5340,5347,5367,5386,5390,5405,5431,5435,5440,5453,5480,5484],{"__ignoreMap":299},[634,5087,5088,5090,5093,5097,5099,5101,5104,5106,5109,5111,5114,5116,5118,5120,5123],{"class":636,"line":637},[634,5089,2406],{"class":2365},[634,5091,5092],{"class":648}," type",[634,5094,5096],{"class":5095},"s5Dmg"," ApiError",[634,5098,1783],{"class":640},[634,5100,1694],{"class":640},[634,5102,5103],{"class":658},"InvalidJson",[634,5105,1699],{"class":640},[634,5107,5108],{"class":640}," |",[634,5110,1694],{"class":640},[634,5112,5113],{"class":658},"RequestFailed",[634,5115,1699],{"class":640},[634,5117,5108],{"class":640},[634,5119,1694],{"class":640},[634,5121,5122],{"class":658},"NetworkError",[634,5124,2386],{"class":640},[634,5126,5127],{"class":636,"line":300},[634,5128,1318],{"emptyLinePlaceholder":315},[634,5130,5131,5133,5135,5138,5140,5143,5145,5148,5151,5153],{"class":636,"line":673},[634,5132,2406],{"class":2365},[634,5134,5092],{"class":648},[634,5136,5137],{"class":5095}," Result",[634,5139,641],{"class":640},[634,5141,5142],{"class":5095},"T",[634,5144,1691],{"class":640},[634,5146,5147],{"class":5095}," E",[634,5149,5150],{"class":648}," extends",[634,5152,5096],{"class":5095},[634,5154,664],{"class":640},[634,5156,5157,5160,5162,5164,5167,5169,5171,5173,5175,5177,5180],{"class":636,"line":1315},[634,5158,5159],{"class":640},"  =",[634,5161,5108],{"class":640},[634,5163,2369],{"class":640},[634,5165,5166],{"class":644}," ok",[634,5168,1343],{"class":640},[634,5170,4185],{"class":5095},[634,5172,1691],{"class":640},[634,5174,4435],{"class":644},[634,5176,1343],{"class":640},[634,5178,5179],{"class":5095}," T",[634,5181,5182],{"class":640}," }\n",[634,5184,5185,5188,5190,5192,5194,5197,5199,5202,5204,5206],{"class":636,"line":1321},[634,5186,5187],{"class":640},"    |",[634,5189,2369],{"class":640},[634,5191,5166],{"class":644},[634,5193,1343],{"class":640},[634,5195,5196],{"class":5095}," false",[634,5198,1691],{"class":640},[634,5200,5201],{"class":644}," error",[634,5203,1343],{"class":640},[634,5205,5147],{"class":5095},[634,5207,5182],{"class":640},[634,5209,5210],{"class":636,"line":1331},[634,5211,1318],{"emptyLinePlaceholder":315},[634,5213,5214,5216,5219,5222,5224,5226],{"class":636,"line":1359},[634,5215,4024],{"class":648},[634,5217,5218],{"class":648}," function",[634,5220,5221],{"class":1294}," getResult",[634,5223,641],{"class":640},[634,5225,5142],{"class":5095},[634,5227,5228],{"class":640},">(\n",[634,5230,5231,5234,5236,5239,5241,5244,5246,5249],{"class":636,"line":1383},[634,5232,5233],{"class":1294},"  apiCall",[634,5235,1343],{"class":640},[634,5237,5238],{"class":640}," ()",[634,5240,3031],{"class":648},[634,5242,5243],{"class":5095}," Promise",[634,5245,641],{"class":640},[634,5247,5248],{"class":5095},"Response",[634,5250,664],{"class":640},[634,5252,5253,5256,5258,5260,5263,5265,5267,5269,5271,5274],{"class":636,"line":1388},[634,5254,5255],{"class":640},"):",[634,5257,5243],{"class":5095},[634,5259,641],{"class":640},[634,5261,5262],{"class":5095},"Result",[634,5264,641],{"class":640},[634,5266,5142],{"class":5095},[634,5268,1691],{"class":640},[634,5270,5096],{"class":5095},[634,5272,5273],{"class":640},">>",[634,5275,1709],{"class":640},[634,5277,5278,5281],{"class":636,"line":1403},[634,5279,5280],{"class":2365},"  try",[634,5282,1709],{"class":640},[634,5284,5285,5288,5291,5293,5295,5298],{"class":636,"line":1443},[634,5286,5287],{"class":648},"    const",[634,5289,5290],{"class":1288}," response",[634,5292,1783],{"class":640},[634,5294,4048],{"class":2365},[634,5296,5297],{"class":1294}," apiCall",[634,5299,4426],{"class":644},[634,5301,5302,5305,5307,5309,5312,5314,5317],{"class":636,"line":1453},[634,5303,5304],{"class":2365},"    if",[634,5306,3533],{"class":644},[634,5308,4319],{"class":640},[634,5310,5311],{"class":1288},"response",[634,5313,287],{"class":640},[634,5315,5316],{"class":1288},"ok",[634,5318,1304],{"class":644},[634,5320,5321,5324,5326,5329,5331,5333,5336,5338],{"class":636,"line":1463},[634,5322,5323],{"class":2365},"      throw",[634,5325,4522],{"class":640},[634,5327,5328],{"class":1294}," Error",[634,5330,1298],{"class":644},[634,5332,1699],{"class":640},[634,5334,5335],{"class":658},"Not OK!",[634,5337,1699],{"class":640},[634,5339,1304],{"class":644},[634,5341,5342,5345],{"class":636,"line":1845},[634,5343,5344],{"class":2365},"    try",[634,5346,1709],{"class":640},[634,5348,5349,5352,5354,5356,5358,5360,5362,5365],{"class":636,"line":2122},[634,5350,5351],{"class":648},"      const",[634,5353,4435],{"class":1288},[634,5355,1783],{"class":640},[634,5357,4048],{"class":2365},[634,5359,5290],{"class":1288},[634,5361,287],{"class":640},[634,5363,5364],{"class":1294},"json",[634,5366,4426],{"class":644},[634,5368,5369,5372,5374,5376,5378,5380,5382,5384],{"class":636,"line":2131},[634,5370,5371],{"class":2365},"      return",[634,5373,2369],{"class":640},[634,5375,5166],{"class":644},[634,5377,1343],{"class":640},[634,5379,4185],{"class":1301},[634,5381,1691],{"class":640},[634,5383,4435],{"class":1288},[634,5385,5182],{"class":640},[634,5387,5388],{"class":636,"line":2140},[634,5389,4299],{"class":640},[634,5391,5392,5395,5397,5400,5403],{"class":636,"line":3099},[634,5393,5394],{"class":2365},"    catch",[634,5396,3533],{"class":644},[634,5398,5399],{"class":1288},"jsonError",[634,5401,5402],{"class":644},") ",[634,5404,2416],{"class":640},[634,5406,5407,5409,5411,5413,5415,5417,5419,5421,5423,5425,5427,5429],{"class":636,"line":3107},[634,5408,5371],{"class":2365},[634,5410,2369],{"class":640},[634,5412,5166],{"class":644},[634,5414,1343],{"class":640},[634,5416,5196],{"class":1301},[634,5418,1691],{"class":640},[634,5420,5201],{"class":644},[634,5422,1343],{"class":640},[634,5424,1694],{"class":640},[634,5426,5103],{"class":658},[634,5428,1699],{"class":640},[634,5430,5182],{"class":640},[634,5432,5433],{"class":636,"line":3113},[634,5434,4299],{"class":640},[634,5436,5437],{"class":636,"line":3118},[634,5438,5439],{"class":640},"  }\n",[634,5441,5442,5445,5447,5449,5451],{"class":636,"line":3145},[634,5443,5444],{"class":2365},"  catch",[634,5446,3533],{"class":644},[634,5448,4595],{"class":1288},[634,5450,5402],{"class":644},[634,5452,2416],{"class":640},[634,5454,5455,5458,5460,5462,5464,5466,5468,5470,5472,5474,5476,5478],{"class":636,"line":3178},[634,5456,5457],{"class":2365},"    return",[634,5459,2369],{"class":640},[634,5461,5166],{"class":644},[634,5463,1343],{"class":640},[634,5465,5196],{"class":1301},[634,5467,1691],{"class":640},[634,5469,5201],{"class":644},[634,5471,1343],{"class":640},[634,5473,1694],{"class":640},[634,5475,5113],{"class":658},[634,5477,1699],{"class":640},[634,5479,5182],{"class":640},[634,5481,5482],{"class":636,"line":3207},[634,5483,5439],{"class":640},[634,5485,5486],{"class":636,"line":3248},[634,5487,1791],{"class":640},[11,5489,5490,5491,5494,5495,5497,5498,5500],{},"In this example, the ",[115,5492,5493],{},"getResult"," function returns a ",[115,5496,5262],{}," object, the result is a discriminated union the discriminator key ",[115,5499,5316],{}," that can be used to safely check if the api call was successful.",[27,5502,5504],{"id":5503},"handling-errors-with-the-result-pattern","Handling Errors with the Result Pattern",[626,5506,5509],{"className":5507,"code":5508,"language":1276,"meta":299,"style":299},"language-ts shiki shiki-themes material-theme-palenight","interface Todo {\n  title: string\n}\n\nasync function getTodo(id: number) {\n  const result = await getResult\u003CTodo>(() => fetch(`https:\u002F\u002Fapi.example.com\u002Ftodos\u002F${id}`))\n\n  if (result.ok) {\n    console.log(`Todo: ${result.data.title}`)\n  }\n  else {\n    \u002F\u002F Handle specific errors\n    switch (result.error) {\n      case 'InvalidJson':\n        console.error('Error parsing JSON response')\n        break\n      case 'RequestFailed':\n        console.error('API request failed')\n        break\n      default:\n        console.error('Unknown error')\n    }\n  }\n}\n",[115,5510,5511,5521,5530,5534,5538,5560,5604,5608,5625,5659,5663,5670,5675,5692,5706,5726,5731,5743,5762,5766,5773,5792,5796,5800],{"__ignoreMap":299},[634,5512,5513,5516,5519],{"class":636,"line":637},[634,5514,5515],{"class":648},"interface",[634,5517,5518],{"class":5095}," Todo",[634,5520,1709],{"class":640},[634,5522,5523,5525,5527],{"class":636,"line":300},[634,5524,2820],{"class":644},[634,5526,1343],{"class":640},[634,5528,5529],{"class":5095}," string\n",[634,5531,5532],{"class":636,"line":673},[634,5533,1791],{"class":640},[634,5535,5536],{"class":636,"line":1315},[634,5537,1318],{"emptyLinePlaceholder":315},[634,5539,5540,5542,5544,5547,5549,5551,5553,5556,5558],{"class":636,"line":1321},[634,5541,4024],{"class":648},[634,5543,5218],{"class":648},[634,5545,5546],{"class":1294}," getTodo",[634,5548,1298],{"class":640},[634,5550,2727],{"class":3027},[634,5552,1343],{"class":640},[634,5554,5555],{"class":5095}," number",[634,5557,2732],{"class":640},[634,5559,1709],{"class":640},[634,5561,5562,5564,5567,5569,5571,5573,5575,5578,5580,5582,5584,5586,5589,5591,5593,5596,5598,5600,5602],{"class":636,"line":1331},[634,5563,4040],{"class":648},[634,5565,5566],{"class":1288}," result",[634,5568,1783],{"class":640},[634,5570,4048],{"class":2365},[634,5572,5221],{"class":1294},[634,5574,641],{"class":640},[634,5576,5577],{"class":5095},"Todo",[634,5579,4656],{"class":640},[634,5581,1298],{"class":644},[634,5583,1768],{"class":640},[634,5585,3031],{"class":648},[634,5587,5588],{"class":1294}," fetch",[634,5590,1298],{"class":644},[634,5592,4458],{"class":640},[634,5594,5595],{"class":658},"https:\u002F\u002Fapi.example.com\u002Ftodos\u002F",[634,5597,4464],{"class":640},[634,5599,2727],{"class":1288},[634,5601,4479],{"class":640},[634,5603,3664],{"class":644},[634,5605,5606],{"class":636,"line":1359},[634,5607,1318],{"emptyLinePlaceholder":315},[634,5609,5610,5612,5614,5617,5619,5621,5623],{"class":636,"line":1383},[634,5611,4314],{"class":2365},[634,5613,3533],{"class":644},[634,5615,5616],{"class":1288},"result",[634,5618,287],{"class":640},[634,5620,5316],{"class":1288},[634,5622,5402],{"class":644},[634,5624,2416],{"class":640},[634,5626,5627,5630,5632,5635,5637,5639,5642,5644,5646,5648,5651,5653,5655,5657],{"class":636,"line":1388},[634,5628,5629],{"class":1288},"    console",[634,5631,287],{"class":640},[634,5633,5634],{"class":1294},"log",[634,5636,1298],{"class":644},[634,5638,4458],{"class":640},[634,5640,5641],{"class":658},"Todo: ",[634,5643,4464],{"class":640},[634,5645,5616],{"class":1288},[634,5647,287],{"class":640},[634,5649,5650],{"class":1288},"data",[634,5652,287],{"class":640},[634,5654,2831],{"class":1288},[634,5656,4479],{"class":640},[634,5658,1304],{"class":644},[634,5660,5661],{"class":636,"line":1403},[634,5662,5439],{"class":640},[634,5664,5665,5668],{"class":636,"line":1443},[634,5666,5667],{"class":2365},"  else",[634,5669,1709],{"class":640},[634,5671,5672],{"class":636,"line":1453},[634,5673,5674],{"class":669},"    \u002F\u002F Handle specific errors\n",[634,5676,5677,5680,5682,5684,5686,5688,5690],{"class":636,"line":1463},[634,5678,5679],{"class":2365},"    switch",[634,5681,3533],{"class":644},[634,5683,5616],{"class":1288},[634,5685,287],{"class":640},[634,5687,4595],{"class":1288},[634,5689,5402],{"class":644},[634,5691,2416],{"class":640},[634,5693,5694,5697,5699,5701,5703],{"class":636,"line":1845},[634,5695,5696],{"class":2365},"      case",[634,5698,1694],{"class":640},[634,5700,5103],{"class":658},[634,5702,1699],{"class":640},[634,5704,5705],{"class":640},":\n",[634,5707,5708,5711,5713,5715,5717,5719,5722,5724],{"class":636,"line":2122},[634,5709,5710],{"class":1288},"        console",[634,5712,287],{"class":640},[634,5714,4595],{"class":1294},[634,5716,1298],{"class":644},[634,5718,1699],{"class":640},[634,5720,5721],{"class":658},"Error parsing JSON response",[634,5723,1699],{"class":640},[634,5725,1304],{"class":644},[634,5727,5728],{"class":636,"line":2131},[634,5729,5730],{"class":2365},"        break\n",[634,5732,5733,5735,5737,5739,5741],{"class":636,"line":2140},[634,5734,5696],{"class":2365},[634,5736,1694],{"class":640},[634,5738,5113],{"class":658},[634,5740,1699],{"class":640},[634,5742,5705],{"class":640},[634,5744,5745,5747,5749,5751,5753,5755,5758,5760],{"class":636,"line":3099},[634,5746,5710],{"class":1288},[634,5748,287],{"class":640},[634,5750,4595],{"class":1294},[634,5752,1298],{"class":644},[634,5754,1699],{"class":640},[634,5756,5757],{"class":658},"API request failed",[634,5759,1699],{"class":640},[634,5761,1304],{"class":644},[634,5763,5764],{"class":636,"line":3107},[634,5765,5730],{"class":2365},[634,5767,5768,5771],{"class":636,"line":3113},[634,5769,5770],{"class":2365},"      default",[634,5772,5705],{"class":640},[634,5774,5775,5777,5779,5781,5783,5785,5788,5790],{"class":636,"line":3118},[634,5776,5710],{"class":1288},[634,5778,287],{"class":640},[634,5780,4595],{"class":1294},[634,5782,1298],{"class":644},[634,5784,1699],{"class":640},[634,5786,5787],{"class":658},"Unknown error",[634,5789,1699],{"class":640},[634,5791,1304],{"class":644},[634,5793,5794],{"class":636,"line":3145},[634,5795,4299],{"class":640},[634,5797,5798],{"class":636,"line":3178},[634,5799,5439],{"class":640},[634,5801,5802],{"class":636,"line":3207},[634,5803,1791],{"class":640},[11,5805,720,5806,5808,5809,5811,5812,5814,5815,5817,5818,5820,5821],{},[115,5807,5493],{}," function uses the Result pattern to handle errors in a consistent and explicit way. Using the ",[115,5810,5316],{}," property we're able to get type safety access to the data or error property. This means for each api call if we want to access the ",[115,5813,5650],{}," property we first need to check if the ",[115,5816,5616],{}," has the ",[115,5819,5316],{}," property set to ",[115,5822,5823],{},"true",[27,5825,5827],{"id":5826},"benefits-of-the-result-pattern","Benefits of the Result Pattern",[11,5829,5830],{},"The Result pattern provides a powerful and flexible way to handle try-catch in API endpoints, making it easier to write robust and maintainable code. By using the Result pattern, you can:",[38,5832,5833,5836,5839],{},[41,5834,5835],{},"Separate error handling logic from the main code flow, making it easier to read and understand",[41,5837,5838],{},"Handle different types of errors and exceptions in a flexible and scalable way",[41,5840,5841],{},"Write more robust and maintainable code",[27,5843,4848],{"id":4847},[11,5845,5846],{},"In this post, we've explored how to use the Result pattern to handle try-catch in API endpoints. By using the Result pattern, you can write more robust and maintainable code that is easier to read and understand. Remember to separate error handling logic from the main code flow and handle different types of errors and exceptions in a flexible and scalable way.",[887,5848,5849],{},"html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}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 pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}",{"title":299,"searchDepth":300,"depth":300,"links":5851},[5852,5853,5854,5855,5856],{"id":5069,"depth":300,"text":5070},{"id":5076,"depth":300,"text":5077},{"id":5503,"depth":300,"text":5504},{"id":5826,"depth":300,"text":5827},{"id":4847,"depth":300,"text":4848},"2024-02-26","Learn how to use the Result pattern to handle try-catch in API endpoints, providing a more elegant and scalable solution.",{"categories":5860},[5861,4900],"API Development","\u002Farticles\u002Fhandling-try-catch-in-api-with-result-pattern",{"title":5061,"description":5858},{"loc":5862},"articles\u002Fhandling-try-catch-in-api-with-result-pattern",[5867,5868,5869,5870,4900],"Result Pattern","API Endpoints","Error Handling","Try-catch","GT__WqevONKmHBe59bumQJRzcmiI-M3dRUh4ffJ2kcE",{"id":5873,"title":5874,"author":6,"body":5875,"date":6445,"description":6446,"draft":312,"extension":313,"image":6,"meta":6447,"navigation":315,"path":6452,"related":6,"seo":6453,"sitemap":6454,"stem":6455,"tags":6,"__hash__":6456},"articles\u002Farticles\u002Fvalidating-api-data-with-zod.md","Validating Backend API Data with Zod in Frontend Applications",{"type":8,"value":5876,"toc":6434},[5877,5880,5886,5889,5895,5898,5924,5930,5933,5937,5940,5958,5962,5965,6106,6110,6113,6312,6316,6319,6421,6424,6428,6431],[11,5878,5879],{},"As a frontend developer, working with data from backend APIs can be a delicate dance. Ensuring that the data received is accurate, complete, and consistent is crucial for building a reliable and user-friendly application. One way to achieve this is by implementing data validation, which helps to catch errors, inconsistencies, and security vulnerabilities. In this post, we'll explore how to use Zod, a popular JavaScript library, to validate backend API data in frontend applications.",[27,5881,5883],{"id":5882},"what-is-zod",[44,5884,5885],{},"What is Zod?",[11,5887,5888],{},"Zod is a lightweight, fast, and flexible validation library for JavaScript. It allows you to define schemas for your data and validate it against those schemas. Zod is particularly useful for validating API responses, as it provides a simple and efficient way to ensure that the data received is correct and consistent.",[27,5890,5892],{"id":5891},"why-use-zod-for-api-data-validation-in-frontend-applications",[44,5893,5894],{},"Why Use Zod for API Data Validation in Frontend Applications?",[11,5896,5897],{},"There are several reasons why Zod is an excellent choice for API data validation in frontend applications:",[38,5899,5900,5906,5912,5918],{},[41,5901,5902,5905],{},[44,5903,5904],{},"Catch errors early",": By validating API responses, you can catch errors and inconsistencies early, reducing the risk of propagating incorrect data throughout your application.",[41,5907,5908,5911],{},[44,5909,5910],{},"Improve user experience",": Validating data ensures that your application displays accurate and consistent information, leading to a better user experience.",[41,5913,5914,5917],{},[44,5915,5916],{},"Enhance security",": Zod helps to prevent security vulnerabilities by detecting and rejecting malformed or malicious data.",[41,5919,5920,5923],{},[44,5921,5922],{},"Simplify debugging",": With Zod, you can quickly identify and debug issues related to API data, reducing the time spent on troubleshooting.",[27,5925,5927],{"id":5926},"implementing-zod-for-api-data-validation-in-frontend-applications",[44,5928,5929],{},"Implementing Zod for API Data Validation in Frontend Applications",[11,5931,5932],{},"Here's a step-by-step guide to implementing Zod for API data validation in your frontend application:",[1073,5934,5936],{"id":5935},"_1-install-zod","1. Install Zod",[11,5938,5939],{},"Run the following command in your terminal:",[626,5941,5945],{"className":5942,"code":5943,"language":5944,"meta":299,"style":299},"language-bash shiki shiki-themes material-theme-palenight","npm install zod\n","bash",[115,5946,5947],{"__ignoreMap":299},[634,5948,5949,5952,5955],{"class":636,"line":637},[634,5950,5951],{"class":5095},"npm",[634,5953,5954],{"class":658}," install",[634,5956,5957],{"class":658}," zod\n",[1073,5959,5961],{"id":5960},"_2-define-a-zod-schema","2. Define a Zod schema",[11,5963,5964],{},"Create a schema that defines the structure and constraints of the API data. For example:",[626,5966,5968],{"className":2351,"code":5967,"language":2353,"meta":299,"style":299},"import { z } from 'zod'\n\nexport const userSchema = z.object({\n  id: z.string(),\n  name: z.string(),\n  email: z.string().email(),\n})\n\nexport type User = z.infer\u003Ctypeof userSchema>\n",[115,5969,5970,5988,5992,6013,6029,6046,6070,6076,6080],{"__ignoreMap":299},[634,5971,5972,5974,5976,5978,5980,5982,5984,5986],{"class":636,"line":637},[634,5973,2366],{"class":2365},[634,5975,2369],{"class":640},[634,5977,3854],{"class":1288},[634,5979,2375],{"class":640},[634,5981,2378],{"class":2365},[634,5983,1694],{"class":640},[634,5985,3863],{"class":658},[634,5987,2386],{"class":640},[634,5989,5990],{"class":636,"line":300},[634,5991,1318],{"emptyLinePlaceholder":315},[634,5993,5994,5996,5998,6001,6003,6005,6007,6009,6011],{"class":636,"line":673},[634,5995,2406],{"class":2365},[634,5997,2604],{"class":648},[634,5999,6000],{"class":1288}," userSchema ",[634,6002,652],{"class":640},[634,6004,3854],{"class":1288},[634,6006,287],{"class":640},[634,6008,3910],{"class":1294},[634,6010,1298],{"class":1288},[634,6012,2416],{"class":640},[634,6014,6015,6017,6019,6021,6023,6025,6027],{"class":636,"line":1315},[634,6016,2715],{"class":644},[634,6018,1343],{"class":640},[634,6020,3854],{"class":1288},[634,6022,287],{"class":640},[634,6024,3928],{"class":1294},[634,6026,1768],{"class":1288},[634,6028,1727],{"class":640},[634,6030,6031,6034,6036,6038,6040,6042,6044],{"class":636,"line":1321},[634,6032,6033],{"class":644},"  name",[634,6035,1343],{"class":640},[634,6037,3854],{"class":1288},[634,6039,287],{"class":640},[634,6041,3928],{"class":1294},[634,6043,1768],{"class":1288},[634,6045,1727],{"class":640},[634,6047,6048,6051,6053,6055,6057,6059,6061,6063,6066,6068],{"class":636,"line":1331},[634,6049,6050],{"class":644},"  email",[634,6052,1343],{"class":640},[634,6054,3854],{"class":1288},[634,6056,287],{"class":640},[634,6058,3928],{"class":1294},[634,6060,1768],{"class":1288},[634,6062,287],{"class":640},[634,6064,6065],{"class":1294},"email",[634,6067,1768],{"class":1288},[634,6069,1727],{"class":640},[634,6071,6072,6074],{"class":636,"line":1359},[634,6073,1544],{"class":640},[634,6075,1304],{"class":1288},[634,6077,6078],{"class":636,"line":1383},[634,6079,1318],{"emptyLinePlaceholder":315},[634,6081,6082,6084,6086,6089,6091,6093,6095,6098,6101,6104],{"class":636,"line":1388},[634,6083,2406],{"class":2365},[634,6085,5092],{"class":648},[634,6087,6088],{"class":5095}," User",[634,6090,1783],{"class":640},[634,6092,3854],{"class":5095},[634,6094,287],{"class":640},[634,6096,6097],{"class":5095},"infer",[634,6099,6100],{"class":640},"\u003Ctypeof",[634,6102,6103],{"class":1288}," userSchema",[634,6105,664],{"class":640},[1073,6107,6109],{"id":6108},"_3-create-a-validation-function","3. Create a validation function",[11,6111,6112],{},"Create a function that takes the API response data as an argument and validates it using the Zod schema. For example:",[626,6114,6116],{"className":2351,"code":6115,"language":2353,"meta":299,"style":299},"async function validateApiResponse(data: unknown): Promise\u003CUser> {\n  try {\n    const validatedData = await userSchema.parseAsync(data)\n    return validatedData\n  }\n  catch (error) {\n    if (error instanceof z.ZodError) {\n      console.error('Validation error:', error.message)\n      throw error\n    }\n    else {\n      console.error('Unknown error:', error)\n      throw error\n    }\n  }\n}\n",[115,6117,6118,6149,6155,6179,6186,6190,6202,6224,6253,6260,6264,6271,6294,6300,6304,6308],{"__ignoreMap":299},[634,6119,6120,6122,6124,6127,6129,6131,6133,6136,6138,6140,6142,6145,6147],{"class":636,"line":637},[634,6121,4024],{"class":648},[634,6123,5218],{"class":648},[634,6125,6126],{"class":1294}," validateApiResponse",[634,6128,1298],{"class":640},[634,6130,5650],{"class":3027},[634,6132,1343],{"class":640},[634,6134,6135],{"class":5095}," unknown",[634,6137,5255],{"class":640},[634,6139,5243],{"class":5095},[634,6141,641],{"class":640},[634,6143,6144],{"class":5095},"User",[634,6146,4656],{"class":640},[634,6148,1709],{"class":640},[634,6150,6151,6153],{"class":636,"line":300},[634,6152,5280],{"class":2365},[634,6154,1709],{"class":640},[634,6156,6157,6159,6162,6164,6166,6168,6170,6173,6175,6177],{"class":636,"line":673},[634,6158,5287],{"class":648},[634,6160,6161],{"class":1288}," validatedData",[634,6163,1783],{"class":640},[634,6165,4048],{"class":2365},[634,6167,6103],{"class":1288},[634,6169,287],{"class":640},[634,6171,6172],{"class":1294},"parseAsync",[634,6174,1298],{"class":644},[634,6176,5650],{"class":1288},[634,6178,1304],{"class":644},[634,6180,6181,6183],{"class":636,"line":1315},[634,6182,5457],{"class":2365},[634,6184,6185],{"class":1288}," validatedData\n",[634,6187,6188],{"class":636,"line":1321},[634,6189,5439],{"class":640},[634,6191,6192,6194,6196,6198,6200],{"class":636,"line":1331},[634,6193,5444],{"class":2365},[634,6195,3533],{"class":644},[634,6197,4595],{"class":1288},[634,6199,5402],{"class":644},[634,6201,2416],{"class":640},[634,6203,6204,6206,6208,6210,6213,6215,6217,6220,6222],{"class":636,"line":1359},[634,6205,5304],{"class":2365},[634,6207,3533],{"class":644},[634,6209,4595],{"class":1288},[634,6211,6212],{"class":640}," instanceof",[634,6214,3854],{"class":5095},[634,6216,287],{"class":640},[634,6218,6219],{"class":5095},"ZodError",[634,6221,5402],{"class":644},[634,6223,2416],{"class":640},[634,6225,6226,6229,6231,6233,6235,6237,6240,6242,6244,6246,6248,6251],{"class":636,"line":1383},[634,6227,6228],{"class":1288},"      console",[634,6230,287],{"class":640},[634,6232,4595],{"class":1294},[634,6234,1298],{"class":644},[634,6236,1699],{"class":640},[634,6238,6239],{"class":658},"Validation error:",[634,6241,1699],{"class":640},[634,6243,1691],{"class":640},[634,6245,5201],{"class":1288},[634,6247,287],{"class":640},[634,6249,6250],{"class":1288},"message",[634,6252,1304],{"class":644},[634,6254,6255,6257],{"class":636,"line":1388},[634,6256,5323],{"class":2365},[634,6258,6259],{"class":1288}," error\n",[634,6261,6262],{"class":636,"line":1403},[634,6263,4299],{"class":640},[634,6265,6266,6269],{"class":636,"line":1443},[634,6267,6268],{"class":2365},"    else",[634,6270,1709],{"class":640},[634,6272,6273,6275,6277,6279,6281,6283,6286,6288,6290,6292],{"class":636,"line":1453},[634,6274,6228],{"class":1288},[634,6276,287],{"class":640},[634,6278,4595],{"class":1294},[634,6280,1298],{"class":644},[634,6282,1699],{"class":640},[634,6284,6285],{"class":658},"Unknown error:",[634,6287,1699],{"class":640},[634,6289,1691],{"class":640},[634,6291,5201],{"class":1288},[634,6293,1304],{"class":644},[634,6295,6296,6298],{"class":636,"line":1463},[634,6297,5323],{"class":2365},[634,6299,6259],{"class":1288},[634,6301,6302],{"class":636,"line":1845},[634,6303,4299],{"class":640},[634,6305,6306],{"class":636,"line":2122},[634,6307,5439],{"class":640},[634,6309,6310],{"class":636,"line":2131},[634,6311,1791],{"class":640},[1073,6313,6315],{"id":6314},"_4-use-the-validation-function","4. Use the validation function",[11,6317,6318],{},"Use the validation function to validate API responses. For example:",[626,6320,6322],{"className":2351,"code":6321,"language":2353,"meta":299,"style":299},"async function fetchData() {\n  const response = await fetch('\u002Fapi\u002Fusers')\n  const data = await response.json()\n  const validatedData = await validateApiResponse(data)\n  \u002F\u002F Use the validated data in your application\n  console.log(validatedData)\n}\n",[115,6323,6324,6337,6360,6378,6396,6401,6417],{"__ignoreMap":299},[634,6325,6326,6328,6330,6333,6335],{"class":636,"line":637},[634,6327,4024],{"class":648},[634,6329,5218],{"class":648},[634,6331,6332],{"class":1294}," fetchData",[634,6334,1768],{"class":640},[634,6336,1709],{"class":640},[634,6338,6339,6341,6343,6345,6347,6349,6351,6353,6356,6358],{"class":636,"line":300},[634,6340,4040],{"class":648},[634,6342,5290],{"class":1288},[634,6344,1783],{"class":640},[634,6346,4048],{"class":2365},[634,6348,5588],{"class":1294},[634,6350,1298],{"class":644},[634,6352,1699],{"class":640},[634,6354,6355],{"class":658},"\u002Fapi\u002Fusers",[634,6357,1699],{"class":640},[634,6359,1304],{"class":644},[634,6361,6362,6364,6366,6368,6370,6372,6374,6376],{"class":636,"line":673},[634,6363,4040],{"class":648},[634,6365,4435],{"class":1288},[634,6367,1783],{"class":640},[634,6369,4048],{"class":2365},[634,6371,5290],{"class":1288},[634,6373,287],{"class":640},[634,6375,5364],{"class":1294},[634,6377,4426],{"class":644},[634,6379,6380,6382,6384,6386,6388,6390,6392,6394],{"class":636,"line":1315},[634,6381,4040],{"class":648},[634,6383,6161],{"class":1288},[634,6385,1783],{"class":640},[634,6387,4048],{"class":2365},[634,6389,6126],{"class":1294},[634,6391,1298],{"class":644},[634,6393,5650],{"class":1288},[634,6395,1304],{"class":644},[634,6397,6398],{"class":636,"line":1321},[634,6399,6400],{"class":669},"  \u002F\u002F Use the validated data in your application\n",[634,6402,6403,6406,6408,6410,6412,6415],{"class":636,"line":1331},[634,6404,6405],{"class":1288},"  console",[634,6407,287],{"class":640},[634,6409,5634],{"class":1294},[634,6411,1298],{"class":644},[634,6413,6414],{"class":1288},"validatedData",[634,6416,1304],{"class":644},[634,6418,6419],{"class":636,"line":1359},[634,6420,1791],{"class":640},[11,6422,6423],{},"By following these steps, you can ensure that the data received from your backend APIs is accurate, complete, and consistent, leading to a more reliable and user-friendly frontend application.",[27,6425,6426],{"id":4847},[44,6427,4848],{},[11,6429,6430],{},"Zod is a powerful and flexible validation library that can help you ensure the integrity of your API data in frontend applications. By implementing Zod, you can catch errors early, improve user experience, enhance security, and simplify debugging. Give Zod a try in your next frontend project and see the benefits for yourself!",[887,6432,6433],{},"html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}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 pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}",{"title":299,"searchDepth":300,"depth":300,"links":6435},[6436,6437,6438,6444],{"id":5882,"depth":300,"text":5885},{"id":5891,"depth":300,"text":5894},{"id":5926,"depth":300,"text":5929,"children":6439},[6440,6441,6442,6443],{"id":5935,"depth":673,"text":5936},{"id":5960,"depth":673,"text":5961},{"id":6108,"depth":673,"text":6109},{"id":6314,"depth":673,"text":6315},{"id":4847,"depth":300,"text":4848},"2024-02-12","Learn how to use Zod, a popular JavaScript library, to validate backend API data in frontend applications and ensure the integrity of your data.",{"categories":6448},[6449,6450,6451,4900],"Zod","API Data Validation","Frontend Development","\u002Farticles\u002Fvalidating-api-data-with-zod",{"title":5874,"description":6446},{"loc":6452},"articles\u002Fvalidating-api-data-with-zod","Nf91V2wMRwHnKne3GPEeyu8RGgkZKRdC0dliBlvl_XE",1786401153941]