PageSourceSearch

https://orm.st/assets/js/a0f3fbfb.8d462bf3.js

js orm.st collected 2026-10-07 16:56:21 UTC 46,480 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkstorm_docs=globalThis.webpackChunkstorm_docs||[]).push([[14045],{89756(t,e,a){a.d(e,{A:()=>u,C:()=>m,CZ:()=>R,EN:()=>k,F:()=>g,Hs:()=>w,K:()=>p,Lj:()=>E,N:()=>h,P:()=>b,Pm:()=>y,QC:()=>v,QK:()=>x,QQ:()=>f,S:()=>c,T:()=>d,UD:()=>q,XS:()=>T,h5:()=>l,hz:()=>C,k4:()=>$,rk:()=>L,vD:()=>P});var o=a(96540),n=a(5260),r=a(74848);const s=t=>t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),i="https://github.com/storm-orm/storm-framework",l="https://discord.gg/SgQpcweUJD",p=t=>`<span class="code-k">${s(t)}</span>`,d=t=>`<span class="code-t">${s(t)}</span>`,c=t=>`<span class="code-s">${s(t)}</span>`,m=t=>`<span class="code-c">${s(t)}</span>`,g=t=>`<span class="code-f">${s(t)}</span>`,h=t=>`<span class="code-n">${s(t)}</span>`,u=t=>`<span class="code-a">${s(t)}</span>`,b=t=>`<span class="code-pl">${s(t)}</span>`,x=t=>`<span class="sqlk">${s(t)}</span>`,f=t=>`<span class="sqlq">${s(t)}</span>`,v=t=>`<span class="sqlc">${s(t)}</span>`,y=[{label:"Kotlin 2.0",kotlin:"2.0.21",ksp:"2.0.21-1.0.28",plugin:"2.0",kspRequired:!0},{label:"Kotlin 2.1",kotlin:"2.1.21",ksp:"2.1.21-2.0.2",plugin:"2.1",kspRequired:!0},{label:"Kotlin 2.2",kotlin:"2.2.21",ksp:"2.3.10",plugin:"2.2"},{label:"Kotlin 2.3",kotlin:"2.3.21",ksp:"2.3.10",plugin:"2.3"},{label:"Kotlin 2.4",kotlin:"2.4.0",ksp:"2.3.10",plugin:"2.4",selected:!0}],w=[{label:"H2",selected:!0,idType:"INT",idClause:"GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY",strType:"VARCHAR",strLen:!0,intType:"INT",dblType:"DOUBLE PRECISION",inlineFk:!0},{label:"PostgreSQL",idType:"INT",idClause:"GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY",strType:"VARCHAR",strLen:!0,intType:"INT",dblType:"DOUBLE PRECISION",inlineFk:!0},{label:"MySQL \xb7 MariaDB",idType:"INT",idClause:"PRIMARY KEY AUTO_INCREMENT",strType:"VARCHAR",strLen:!0,intType:"INT",dblType:"DOUBLE",inlineFk:!1},{label:"Oracle",idType:"NUMBER",idClause:"GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY",strType:"VARCHAR2",strLen:!0,intType:"NUMBER",dblType:"BINARY_DOUBLE",inlineFk:!0},{label:"SQL Server",idType:"INT",idClause:"IDENTITY PRIMARY KEY",strType:"VARCHAR",strLen:!0,intType:"INT",dblType:"FLOAT",inlineFk:!0},{label:"SQLite",idType:"INTEGER",idClause:"PRIMARY KEY",strType:"TEXT",strLen:!1,intType:"INTEGER",dblType:"REAL",inlineFk:!0}];function k({file:t,tag:e,code:a,sql:o,copy:n,variants:r}){const i=t=>{let e,a=t;do{e=a,a=a.replace(/<[^>]+>/g,"")}while(a!==e);return a.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&")},l=t=>{const e=t.split("\n").length;let a="";for(let o=1;o<=e;o++)a+=`<div>${o}</div>`;return`<div class="gutter">${a}</div>\n    <pre class="code">${t}</pre>`},p=r?Math.max(0,r.findIndex(t=>t.selected)):0,d=r?`<span class="varwrap"><select class="varsel" aria-label="Snippet variant">${r.map((t,e)=>`<option value="${e}"${e===p?" selected":""}>${s(t.label)}</option>`).join("")}</select></span>`:"",c=r&&r.some(t=>t.sql),m=Boolean(o)||c,g=m?'<span class="sqlbtn" role="button" tabindex="0" aria-expanded="false"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg><span class="sqlbtntext">Show SQL</span></span>':"",h=!0===n?i(r?r[p].code:a):n,u=h?`<span class="copybtn" data-copy="${s(h).replace(/"/g,"&quot;")}"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span class="copybtntext">Copy</span></span>`:"",b=m?`<div class="sqlhead"><span class="ar">\u21b3</span> generated sql</div><div class="sqlconsole">${c?r.map((t,e)=>`<pre class="sqlpanel sqlpane${e===p?" on":""}">${t.sql||o||""}</pre>`).join(""):`<pre class="sqlpanel">${o}</pre>`}</div>`:"",x=r?r.map((t,e)=>{const a=t.copy||(!0===n?i(t.code):null);return`<div class="codearea varpane${e===p?" on":""}"${a?` data-copy="${s(a).replace(/"/g,"&quot;")}"`:""}${t.tag?` data-tag="${s(t.tag).replace(/"/g,"&quot;")}"`:""}${t.file?` data-file="${s(t.file).replace(/"/g,"&quot;")}"`:""}>${l(t.code)}</div>`}).join(""):`<div class="codearea">${l(a)}</div>`;return`\n<div class="editor">\n  <div class="ebar">\n    <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>\n    <span class="fname">${s(r&&r[p]&&r[p].file||t||"")}</span>\n    <span class="langtag">${s(r&&r[p]&&r[p].tag||e||"")}</span>\n    ${d}${g}${u}\n  </div>\n  ${x}\n  ${b}\n</div>`}function T(t){const e=s(t).replace(/"/g,"&quot;");return`<div class="clonebar"><span class="dollar">$</span><span class="clonecmd">${s(t)}</span><span class="copybtn iconly" data-copy="${e}" title="Copy" aria-label="Copy">
1<svg class="ico ico-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><svg class="ico ico-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg></span></div>`}function P({left:t,right:e}){return`\n<div class="glance">\n  <div class="gpane"><div class="glabel">${s(t.label)}</div><pre>${t.code}</pre></div>\n  <div class="gpane storm"><div class="glabel">${s(e.label)}</div><pre>${e.code}</pre></div>\n</div>`}function S(t){const e=t.querySelector(".copybtntext");let a=null;const o=()=>{const o=t.dataset.copy;if(navigator.clipboard&&window.isSecureContext)navigator.clipboard.writeText(o);else{const t=document.createElement("textarea");t.value=o,document.body.appendChild(t),t.select(),document.execCommand("copy"),t.remove()}t.classList.add("on"),e&&(e.textContent="Copied"),clearTimeout(a),a=setTimeout(()=>{t.classList.remove("on"),e&&(e.textContent="Copy")},1600)};return t.addEventListener("click",o),()=>{t.removeEventListener("click",o),clearTimeout(a)}}function E(){const t=[];return document.querySelectorAll(".storm-tut .editor").forEach(e=>{const a=e.querySelector(".sqlbtn");if(a){const o=a.querySelector(".sqlbtntext"),n=()=>{const t=e.classList.toggle("show-sql");a.classList.toggle("on",t),a.setAttribute("aria-expanded",t?"true":"false"),o&&(o.textContent=t?"Hide SQL":"Show SQL")},r=t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),n())};a.addEventListener("click",n),a.addEventListener("keydown",r),t.push(()=>{a.removeEventListener("click",n),a.removeEventListener("keydown",r)})}const o=e.querySelector(".copybtn");o&&t.push(S(o));const n=e.querySelector(".varsel");if(n){const a=e.querySelectorAll(".varpane"),o=e.querySelectorAll(".sqlpane"),r=()=>{const t=Number(n.value);a.forEach((e,a)=>e.classList.toggle("on",a===t)),o.forEach((e,a)=>e.classList.toggle("on",a===t));const r=e.querySelector(".copybtn");r&&a[t]&&a[t].dataset.copy&&(r.dataset.copy=a[t].dataset.copy);const s=e.querySelector(".langtag");s&&a[t]&&a[t].dataset.tag&&(s.textContent=a[t].dataset.tag);const i=e.querySelector(".fname");i&&a[t]&&a[t].dataset.file&&(i.textContent=a[t].dataset.file)};n.addEventListener("change",r),t.push(()=>n.removeEventListener("change",r))}}),document.querySelectorAll(".storm-tut .clonebar .copybtn").forEach(e=>{t.push(S(e))}),()=>t.forEach(t=>t())}function q({title:t,description:e,slug:a,body:s}){(0,o.useEffect)(()=>E(),[]);const i=`https://orm.st/tutorials/${a}`;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.A,{children:[(0,r.jsx)("html",{lang:"en"}),(0,r.jsx)("title",{children:`${t} \xb7 ST/ORM Tutorials`}),(0,r.jsx)("meta",{name:"description",content:e}),(0,r.jsx)("link",{rel:"canonical",href:i}),(0,r.jsx)("meta",{property:"og:type",content:"article"}),(0,r.jsx)("meta",{property:"og:title",content:t}),(0,r.jsx)("meta",{property:"og:description",content:e}),(0,r.jsx)("meta",{name:"twitter:title",content:t}),(0,r.jsx)("meta",{name:"twitter:description",content:e}),(0,r.jsx)("script",{type:"application/ld+json",children:JSON.stringify({"@context":"https://schema.org","@type":"TechArticle",headline:t,description:e,url:i,proficiencyLevel:"Beginner",author:{"@type":"Organization",name:"Storm",url:"https://github.com/storm-orm"}})}),(0,r.jsx)("link",{rel:"preconnect",href:"https://fonts.googleapis.com"}),(0,r.jsx)("link",{rel:"preconnect",href:"https://fonts.gstatic.com",crossOrigin:"anonymous"}),(0,r.jsx)("link",{href:"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap",rel:"stylesheet"})]}),(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:R}}),(0,r.jsx)("div",{className:"storm-tut",dangerouslySetInnerHTML:{__html:s}})]})}const C=t=>`\n<nav><div class="wrap nav">\n  <div class="brand"><a class="bhome" href="/"><img class="logo" src="/img/storm-light.png" alt="Storm" /><span>ST<b>/ORM</b></span></a><span class="tech-tag">Kotlin 2.0\u20132.4</span></div>\n  <input type="checkbox" id="storm-nav-toggle" class="nav-toggle-cb" aria-label="Toggle navigation menu" />\n  <label for="storm-nav-toggle" class="nav-toggle" aria-hidden="true"><span></span><span></span><span></span></label>\n  <div class="nav-links">\n    <a href="/docs/ai">AI</a>\n    <a href="/tutorials/"${"tutorials"===t?' class="on"':""}>Tutorials</a>\n    <a href="/examples/"${"examples"===t?' class="on"':""}>Examples</a>\n    <a href="/comparison"${"comparison"===t?' class="on"':""}>Comparison</a>\n    <a href="/benchmarks"${"benchmarks"===t?' class="on"':""}>Benchmarks</a>\n    <a href="/blog/"${"blog"===t?' class="on"':""}>Blog</a>\n    <a href="/docs/"${"docs"===t?' class="on"':""}>Documentation</a>\n    <a class="gh" href="${i}" target="_blank" rel="noopener">GitHub</a>\n    <a href="/quickstart" class="btn primary" style="height:36px">Get started</a>\n  </div>\n</div></nav>`,$=(t,{priority:e=!1}={})=>`\n<picture class="heroart heroart-${t}" aria-hidden="true">\n  <source media="(max-width:1200px)" type="image/avif" srcset="/img/hero/orm-${t}-tablet.avif" />\n  <source media="(max-width:1200px)" type="image/webp" srcset="/img/hero/orm-${t}-tablet.webp" />\n  <source type="image/avif" srcset="/img/hero/orm-${t}-desktop.avif" />\n  <img src="/img/hero/orm-${t}-desktop.webp" alt="" width="1600" height="900" decoding="async"\n    ${e?'fetchpriority="high"':'loading="lazy"'} />\n</picture>`,L=`\n<footer><div class="wrap foot">\n  <div class="brand"><img class="logo" src="/img/storm-light.png" alt="Storm" /></div>\n  <div class="links"><a href="/">orm.st</a><a href="/quickstart">Quickstart</a><a href="/docs/ai">Agentic coding</a><a href="/docs/">Documentation</a><a href="/tutorials/">Tutorials</a><a href="/templates/">Templates</a><a href="/examples/">Examples</a><a href="/comparison">Comparison</a><a href="/benchmarks">Benchmarks</a><a href="/blog/">Blog</a><a href="${i}" target="_blank" rel="noopener">GitHub</a><a href="${l}" target="_blank" rel="noopener">Discord</a><a href="https://central.sonatype.com/namespace/st.orm">Maven Central</a></div>\n</div></footer>`,R="\n  :root{\n    --bg:#070709; --panel:#0f0f14; --panel-2:#0b0b0f; --statusbg:#08080b;\n    --border:#20202a; --border-soft:#17171f;\n    --text:#eaeaf0; --muted:#8a8a96; --faint:#565662; --body:#b7b8c2;\n    --accent:#818cf8; --accent-2:#a78bfa; --green:#5eead4;\n    --kw:#c4b5fd; --type:#7dd3fc; --str:#86efac; --com:#767e8f; --fn:#f0abfc; --num:#fcd34d; --anno:#fbbf24; --plain:#cfd0d8;\n    --mono:\"JetBrains Mono\",ui-monospace,SFMono-Regular,Menlo,monospace;\n    --sans:\"Inter\",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;\n  }\n  html{background:var(--bg)}\n  body{margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;\n    background-image:radial-gradient(1100px 540px at 18% -10%,rgba(129,140,248,.14),transparent 62%);}\n  .storm-tut a{color:inherit;text-decoration:none}\n  .storm-tut *{box-sizing:border-box}\n  .storm-tut .wrap{max-width:1120px;margin:0 auto;padding:0 24px}\n\n  .storm-tut nav{position:sticky;top:0;z-index:20;backdrop-filter:blur(12px);background:rgba(7,7,9,.7);border-bottom:1px solid var(--border-soft)}\n  .storm-tut .nav{display:flex;align-items:center;justify-content:space-between;
1height:62px}\n  .storm-tut .brand{display:flex;align-items:center;gap:9px;font-weight:600;letter-spacing:-.01em}\n  .storm-tut .brand .bhome{display:flex;align-items:center;gap:9px}\n  .storm-tut .logo{height:26px;width:auto;display:block;position:relative;top:-2px;filter:drop-shadow(0 0 10px rgba(129,140,248,.35))}\n  .storm-tut .brand b{font-family:var(--mono);font-weight:700}\n  .storm-tut .tech-tag{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.02em;border-left:1px solid var(--border);padding-left:12px}\n  .storm-tut .nav-links{display:flex;align-items:center;gap:24px;font-size:14px;color:var(--muted)}\n  .storm-tut .nav-links a{white-space:nowrap}\n  .storm-tut .nav-links a:hover{color:var(--text)}\n  .storm-tut .nav-links a.on{color:var(--text)}\n  .storm-tut .nav-toggle{display:none}\n  /* Off entirely above the breakpoint, where the links are always visible and a\n     toggle would only add a dead tab stop. */\n  .storm-tut .nav-toggle-cb{display:none}\n  /* One focus ring for every interactive control on the page. Keyboard-only\n     (:focus-visible), so it never fires on a mouse click. */\n  .storm-tut a:focus-visible,.storm-tut button:focus-visible,.storm-tut select:focus-visible,\n  .storm-tut .nav-toggle-cb:focus-visible + .nav-toggle{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}\n  .storm-tut .btn{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 18px;border-radius:9px;font-size:14.5px;\n    font-weight:550;border:1px solid var(--border);background:var(--panel);transition:.16s;cursor:pointer}\n  .storm-tut .btn:hover{border-color:#34343d;transform:translateY(-1px)}\n  .storm-tut .btn.primary{background:var(--accent);color:#0a0a0f;border-color:var(--accent);font-weight:600}\n  .storm-tut .btn.primary:hover{background:#9aa3ff}\n  .storm-tut .grad{background:linear-gradient(100deg,#a78bfa,#818cf8 50%,#7dd3fc);-webkit-background-clip:text;background-clip:text;color:transparent}\n\n  /* article shell */\n  .storm-tut .art{max-width:860px;margin:0 auto;padding:54px 24px 48px}\n  .storm-tut .crumbs{font-family:var(--mono);font-size:12px;color:var(--muted);letter-spacing:.04em}\n  .storm-tut .crumbs a:hover{color:var(--text)}\n  .storm-tut .crumbs .sep{margin:0 9px;color:#2c2c36}\n  .storm-tut h1{font-size:clamp(34px,5vw,52px);line-height:1.05;letter-spacing:-.035em;font-weight:800;margin:18px 0 0}\n  .storm-tut .dek{color:var(--muted);font-size:17.5px;line-height:1.66;margin:20px 0 0;max-width:700px}\n  .storm-tut .meta{display:flex;gap:8px;margin-top:24px;flex-wrap:wrap}\n  .storm-tut .meta span{font-family:var(--mono);font-size:11.5px;color:var(--muted);border:1px solid var(--border-soft);border-radius:999px;padding:5px 13px}\n  .storm-tut h2{font-size:25px;letter-spacing:-.02em;font-weight:700;margin:62px 0 0}\n  .storm-tut h2 .hno{font-family:var(--mono);font-size:14px;font-weight:500;color:var(--accent);margin-right:12px}\n  .storm-tut p{color:var(--body);font-size:15.5px;line-height:1.75;margin:16px 0 0}\n  .storm-tut p b,.storm-tut li b{color:var(--text);font-weight:600}\n  .storm-tut ul{color:var(--body);font-size:15.5px;line-height:1.75;margin:14px 0 0;padding-left:22px}\n  .storm-tut li{margin-top:6px}\n  .storm-tut li::marker{color:var(--faint)}\n  .storm-tut p code,.storm-tut li code,.storm-tut td code,.storm-tut .note code{font-family:var(--mono);font-size:.86em;background:var(--panel);border:1px solid var(--border-soft);border-radius:6px;padding:1.5px 6px;color:var(--plain);white-space:nowrap}\n  .storm-tut .art a.tlink{color:var(--accent)}\n  .storm-tut .art a.tlink:hover{text-decoration:underline}\n  /* Star ask (combined with .grad): the gradient text is transparent, so the\n     underline color is set explicitly and only revealed on hover. */\n  .storm-tut .art a.star{font-weight:600;text-decoration:none}\n  .storm-tut .art a.star:hover{text-decoration:underline;text-decoration-color:#a5b4fc;text-decoration-thickness:2px;text-underline-offset:4px}\n\n  /* editor chrome (landing look, sized for articles) */\n  .storm-tut .editor{margin:26px 0 0;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--panel);\n    box-shadow:0 40px 90px -50px rgba(0,0,0,.8),0 0 0 1px rgba(129,140,248,.05)}\n  .storm-tut .ebar{display:flex;align-items:center;gap:8px;height:44px;padding:0 16px;border-bottom:1px solid var(--border-soft);background:rgba(255,255,255,.014)}\n  .storm-tut .dot{width:11px;height:11px;border-radius:50%;flex:none}.storm-tut .dot.r{background:#ff5f57}.storm-tut .dot.y{background:#febc2e}.storm-tut .dot.g{background:#28c840}\n  .storm-tut .fname{margin-left:10px;font-family:var(--mono);font-size:12.5px;color:var(--faint)}\n  .storm-tut .langtag{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.05em}\n  .storm-tut .sqlbtn,.storm-tut .copybtn{margin-left:14px;display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;\n    color:var(--accent);border:1px solid rgba(129,140,248,.3);border-radius:7px;padding:4px 10px;cursor:pointer;transition:.16s;user-select:none}\n  .storm-tut .sqlbtn:hover,.storm-tut .copybtn:hover{background:rgba(129,140,248,.12);border-color:rgba(129,140,248,.5)}\n  .storm-tut .sqlbtn.on,.storm-tut .copybtn.on{background:rgba(129,140,248,.16);color:#aab2ff}\n  .storm-tut .sqlbtn .ico,.storm-tut .copybtn .ico{width:13px;height:13px;opacity:.9}\n  .storm-tut .clonebar{display:flex;align-items:center;gap:10px;font-family:var(--mono);font-size:13px;color:var(--plain);\n    background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:0 18px;min-height:44px;overflow-x:auto;white-space:nowrap}\n  .storm-tut .clonebar .dollar{color:var(--green);user-select:none}\n  .storm-tut .clonebar .clonecmd{overflow-x:auto}\n  .storm-tut .clonebar .copybtn{margin-left:auto;flex:none;gap:0;padding:5px 8px}\n  .storm-tut .clonebar .copybtn .ico-check{display:none}\n  .storm-tut .clonebar .copybtn.on{color:#7ee787;border-color:rgba(126,231,135,.5);background:rgba(126,231,135,.12)}\n  .storm-tut .clonebar .copybtn.on .ico-copy{display:none}\n  .storm-tut .clonebar .copybtn.on .ico-check{display:inline-block}\n  /* variant selector (same chrome as the buttons; native select behind a\n     custom chevron because appearance:none drops the platform arrow) */\n  .storm-tut .varwrap{position:relative;margin-left:14px;display:inline-flex;align-items:center}\n  .storm-tut .varwrap::after{content:\"\u25be\";position:absolute;right:9px;pointer-events:none;color:var(--accent);font-size:10px}\n  .storm-tut .varsel{appearance:none;-webkit-appearance:none;font-family:var(--mono);font-size:11.5px;color:var(--accent);\n    background:transparent;border:1px solid rgba(129,140,248,.3);border-radius:7px;padding:4px 24px 4px 10px;cursor:pointer;transition:.16s}\n  .storm-tut .varsel:hover{background:rgba(129,140,248,.12);border-color:rgba(129,140,248,.5)}\n  .storm-tut .varsel option{background:var(--panel-2);color:var(--text)}\n  .storm-tut .codearea.varpane{display:none}\n  .storm-tut .codearea.varpane.on{display:flex}\n  .storm-tut .codearea{display:flex;background:linear-gradient(180deg,var(--panel),var(--panel-2))}\n  .storm-tut .gutter{padding:18px 0;width:46px;text-align:right;color:#3b3b46;font-family:var(--mono);font-size:12px;\n    line-height:24px;user-select:none;border-right:1px solid var(--border-soft);flex:none}\n  .storm-tut .gutter div{padding-right:14px}\n  .storm-tut .code{margin:0;padding:18px 22px;font-family:var(--mono);font-size:13.5px;line-height:24px;white-space:pre;overflow-x:auto;flex:1}\n  .storm-tut pre{background:transparent;border:0;border-radius:0;box-shadow:none}\n  .storm-tut .code-k{color:var(--kw)}.storm-tut .code-t{color:var(--type)}.storm-tut .code-s{color:var(--str)}.storm-tut .code-c{color:var(--com)}\n  .storm-tut .code-f{color:var(--fn)}.storm-tut .code-n{color:var(--num)}.storm-tut .code-a{color:var(--anno)}.storm-tut .code-pl{color:var(--plain)}.storm-tut .code-m{color:var(--muted)}\n\n  /* per-block SQL console */\n  .storm-tut .sqlhead{display:none;align-items:center;gap:9px;height:34px;padding:0 18px;border-top:1px solid var(--border-soft);background:var(--statusbg);\n    font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}\n  .storm-tut .sqlhead .ar{color:var(--accent)}\n  .storm-tut .sqlconsole{display:none;border-top:1px solid var(--border-soft);background:var(--statusbg)}\n  .storm-tut .editor.show-sql .sqlconsole,.storm-tut .editor.show-sql .sqlhead{display:flex}\n  .storm-tut .sqlpanel{margin:0;padding:14px 18px 18px;font-family:var(--mono);font-size:12.5px;line-height:1.75;white-space:pre;overflow-x:auto;color:var(--plain);flex:1}\n  .storm-tut .sqlconsole .sqlpane{display:none}\n  .storm-tut .sqlconsole .sqlpane.on{display:block}\n  .storm-tut .sqlk{color:var(--accent)}.storm-tut .sqlq{color:var(--num)}.storm-tut .sqlc{color:var(--com)}\n\n  /* at-a-glance two-pane strip */\n  .storm-tut .glance{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:26px 0 0}\n  @media(max-width:720px){.storm-tut .glance{grid-template-columns:1fr}}\n  /* On phones the title bar can't fit filename + language tag + selector, so\n     the language tag yields when a variant selector is present. */\n  @media(max-width:600px){.storm-tut .ebar:has(.varsel) .langtag{display:none}}\n  .storm-tut .gpane{border:1px solid var(--border-soft);border-radius:12px;background:var(--panel-2);overflow:hidden}\n  .storm-tut .gpane .glabel{display:flex;align-items:center;height:34px;padding:0 14px;border-bottom:1px solid var(--border-soft);\n    font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}\n  .storm-tut .gpane.storm .glabel{color:var(--accent)}\n  .storm-tut .gpane pre{margin:0;padding:14px 16px;font-family:var(--mono);font-size:13px;line-height:22px;white-space:pre;overflow-x:auto}\n\n  /* callout */\n  .storm-tut .note{margin:26px 0 0;border:1px solid var(--border-soft);border-left:3px solid var(--accent);border-radius:10px;\n    background:var(--panel-2);padding:15px 18px;font-size:14.5px;line-height:1.7;color:var(--body)}\n  .storm-tut .note a{color:var(--accent)}\n  .storm-tut .note a:hover{text-decoration:underline}\n\n  /* comparison table \u2014 framed card, horizontal rules only (no boxy grid),\n     the outer border closes the bottom so the last row never looks unfinished.\n     Explicitly zeroes cell borders so Infima's default grid does not bleed\n     through. */\n  .storm-tut table.cmp{width:100%;border-collapse:separate;border-spacing:0;margin:26px 0 0;font-size:14px;\n    background:var(--panel-2);border:1px solid var(--border);border-radius:12px;overflow:hidden}\n  /* A wide matrix (the /comparison page's six columns) cannot shrink to a phone\n     and stay readable, and the overflow:hidden above \u2014 which is what rounds the\n     c
1ard's corners \u2014 clips the right-hand columns out of sight instead of\n     letting them scroll. Where a table is wrapped in .cmp-scroll the wrapper\n     takes the card chrome and does the scrolling, and the table keeps a legible\n     minimum width. Narrow .cmp tables (two or three columns, used in the\n     tutorials) are left alone: they fit, and wrapping them would add a\n     scrollbar for nothing. */\n  .storm-tut .cmp-scroll{margin:26px 0 0;border:1px solid var(--border);border-radius:12px;\n    background:var(--panel-2);overflow-x:auto;-webkit-overflow-scrolling:touch}\n  .storm-tut .cmp-scroll:focus-visible{outline:2px solid var(--accent);outline-offset:3px}\n  .storm-tut .cmp-scroll table.cmp{margin:0;border:0;border-radius:0;min-width:820px}\n  .storm-tut .cmp th,.storm-tut .cmp td{border:0;text-align:left;padding:13px 16px;vertical-align:top;line-height:1.55}\n  .storm-tut .cmp thead th{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;\n    color:var(--muted);background:rgba(255,255,255,.022);border-bottom:1px solid var(--border);white-space:nowrap}\n  .storm-tut .cmp tbody td{border-top:1px solid var(--border-soft);color:var(--body)}\n  .storm-tut .cmp tbody tr:first-child td{border-top:0}\n  .storm-tut .cmp tbody tr:nth-child(even) td{background:rgba(255,255,255,.014)}\n  .storm-tut .cmp td:first-child{color:var(--text);font-weight:600}\n  /* The brand gradient on the Storm column is reserved for the .cmp-brand\n     matrix on the /comparison page, so it stays a distinctive flourish there.\n     Tutorial comparison tables use plain .cmp and keep column 2 unstyled.\n     Clipped into the header text (like .grad) and washed at low alpha across\n     the cells, so the column reads as one highlighted band. */\n  .storm-tut .cmp-brand thead th:nth-child(2){background:linear-gradient(100deg,#a78bfa,#818cf8 50%,#7dd3fc);-webkit-background-clip:text;background-clip:text;color:transparent}\n  .storm-tut .cmp-brand tbody tr td:nth-child(2){background:linear-gradient(100deg,rgba(129,140,248,.30),rgba(96,165,250,.26) 50%,rgba(125,211,252,.34));color:var(--text);font-weight:500}\n  /* let long inline code wrap inside cells instead of forcing the table wider\n     than its container (which the rounded overflow:hidden would then clip) */\n  .storm-tut .cmp th code,.storm-tut .cmp td code{white-space:normal;word-break:break-word}\n\n  /* cta + doc refs */\n  .storm-tut .cta{display:flex;gap:14px;margin-top:36px;flex-wrap:wrap;align-items:center}\n  /* Star ask sharing the closing CTA row: centered against the button height,\n     wraps below it on narrow screens. */\n  .storm-tut .cta .starline{margin:0;color:var(--muted);font-size:15px}\n  .storm-tut .refs{display:flex;gap:8px;margin-top:18px;flex-wrap:wrap}\n  .storm-tut .refs a{font-family:var(--mono);font-size:12px;color:var(--muted);border:1px solid var(--border-soft);border-radius:999px;padding:6px 14px;transition:.16s}\n  .storm-tut .refs a:hover{color:var(--accent);border-color:rgba(129,140,248,.4)}\n\n  /* One hero for every hub page: same centred measure, same horizontal padding,\n     same vertical rhythm, so heading position and spacing match as you move\n     between them. The proportions follow the comparison and benchmarks heroes,\n     which read best. */\n  /* Height follows the hero's own copy: the artwork is absolutely positioned and\n     overflows on its own, so reserving a minimum here only opened a gap under\n     the shorter heroes. */\n  .storm-tut .pagehero{position:relative;max-width:1080px;margin:0 auto;padding:56px 24px 14px}\n  .storm-tut .pagehero>*:not(.heroart){position:relative;z-index:1}\n  /* Hub pages share one measure end to end: the hero, the sections under it and\n     the article body all sit on the same lane, so the headline starts on the\n     site's left edge on every page and nothing is wider than what follows it.\n     .art keeps its narrower reading measure for the long-form tutorial and blog\n     posts; the hub pages opt into the wide lane. */\n  .storm-tut .art-wide{max-width:1080px}\n  /* Descriptive copy is held near 62ch: comfortable to read and narrow enough\n     that the artwork behind it stays clear of the text column. */\n  .storm-tut .pagehero .sub,.storm-tut .pagehero .dek{max-width:62ch}\n  .storm-tut .pagehero .dek{color:var(--muted);font-size:17.5px;line-height:1.66;margin:20px 0 0}\n  .storm-tut .pagehero .sub{color:var(--muted);font-size:17.5px;line-height:1.66;margin:20px 0 0}\n  .storm-tut .pagehero h1{text-wrap:balance}\n\n  /* The artwork sits to the right of the hero copy, sized and placed to the same\n     values on every page: one right edge, one height, one crop. The radial layer\n     dissolves its outer edges so nothing reads as a rectangle, and the\n     horizontal layer holds it back over the text column and releases it toward\n     the right. A third layer retires it downward, reaching true transparency\n     before the element ends so it dissolves into the page rather than stopping\n     on a straight edge above the content below. The image's black sits on a near-black page, so screen blending\n     drops that black into the background and leaves only the schema light. */\n  .storm-tut .heroart{display:block;position:absolute;top:-60px;right:-60px;width:min(62%,790px);height:560px;\n    pointer-events:none;user-select:none;z-index:0;opacity:.9;mix-blend-mode:screen;\n    -webkit-mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 48%,rgba(0,0,0,.74) 72%,transparent 94%);\n    mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 48%,rgba(0,0,0,.74) 72%,transparent 94%);\n    -webkit-mask-composite:source-in;mask-composite:intersect}\n  .storm-tut .heroart img{width:100%;height:100%;object-fit:cover;object-position:88% center;display:block}\n  /* Per-composition trim: each render places its schema a little differently in\n     the frame, so these settle them onto the same optical line. */\n  /* Its schema runs lower in the frame than the others and its cards sit close\n     behind, so the downward fade starts a little earlier here. */\n  .storm-tut .heroart-examples{top:-30px;\n    -webkit-mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 28%,rgba(0,0,0,.42) 58%,transparent 84%);\n    mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 28%,rgba(0,0,0,.42) 58%,transparent 84%)}\n  /* Sits lowest of the set, so it also takes a bottom fade that finishes above\n     the article cards; without it the art would still be lit where they begin. */\n  .storm-tut .heroart-blog{top:-5px;\n    -webkit-mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 42%,rgba(0,0,0,.7) 68%,transparent 90%);\n    mask-image:radial-gradient(72% 82% at 56% 42%,#000 18%,rgba(0,0,0,.55) 50%,transparent 76%),linear-gradient(to right,transparent 0%,rgba(0,0,0,.15) 38%,rgba(0,0,0,.62) 54%,#000 68%),linear-gradient(to bottom,#000 42%,rgba(0,0,0,.7) 68%,transparent 90%)}\n  /* Positioned art would otherwise paint over the static sections that follow. */\n  .storm-tut .shead,.storm-tut .cards,.storm-tut .art>*{position:relative;z-index:1}\n  /* The hero already carries the leading space, so the article body that follows\n     it starts tight rather than adding its own. */\n  .storm-tut .pagehero + .art{padding-top:6px}\n  /* The artwork needs clear space to the right of the copy. Below this width\n     there is none: it would either sit under the text or take a screen of its\n     own to scroll past, and it carries no information the copy does not. So it\n     is dropped rather than restacked. */\n  @media(max-width:1100px){\n    .storm-tut .heroart{display:none}\n  }\n  @media(prefers-reduced-motion:no-preference){\n    .storm-tut .heroart{animation:storm-hero-fade .9s ease both}\n  }\n  @keyframes storm-hero-fade{from{opacity:0}
1to{opacity:1}}\n\n  /* Filter pills, matching the landing page's scene selector so the two read as\n     the same control: quiet by default, the active one lifted with a brighter\n     edge, a faint glow and a deeper fill. The gradient edge is drawn with a\n     padding-box/border-box background pair, since a border cannot take a\n     gradient on its own. Shared by the tutorials and blog rows. */\n  .storm-tut .catnav,.storm-tut .bfilters{display:flex;gap:8px;margin-top:26px;flex-wrap:wrap;max-width:62ch}\n  .storm-tut .catnav a,.storm-tut .bchip{font-family:var(--mono);font-size:11.5px;line-height:1;color:var(--faint);\n    border:1px solid transparent;border-radius:999px;padding:8px 15px;cursor:pointer;\n    transition:color .2s ease,box-shadow .2s ease;\n    background:linear-gradient(var(--bg),var(--bg)) padding-box,linear-gradient(150deg,#4a4e74 0%,#3c3e62 22%,#2d2e47 46%,#27273d 54%,#383a62 74%,#454877 92%,#313352 100%) border-box}\n  .storm-tut .catnav a:hover,.storm-tut .bchip:hover{color:var(--muted);box-shadow:0 0 8px rgba(129,140,248,.10)}\n  .storm-tut .catnav a:focus-visible,.storm-tut .bchip:focus-visible{outline:2px solid var(--accent);outline-offset:3px;color:var(--muted)}\n  .storm-tut .catnav a.on,.storm-tut .bchip.on{color:#f2ecff;box-shadow:0 0 10px rgba(129,140,248,.14);\n    background:linear-gradient(#131028,#131028) padding-box,linear-gradient(150deg,#7a80be 0%,#6167a2 22%,#4a4f80 46%,#434877 54%,#6167a2 74%,#7c82c0 92%,#545a90 100%) border-box}\n  .storm-tut .catnav a b,.storm-tut .bchip b{color:rgba(178,182,220,.42);font-weight:500;margin-left:7px}\n  .storm-tut .catnav a.on b,.storm-tut .bchip.on b{color:rgba(215,220,255,.5)}\n  .storm-tut .shead{max-width:1080px;margin:0 auto;padding:52px 24px 0;font-family:var(--mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--text);scroll-margin-top:86px}\n  .storm-tut .shead .mark{color:var(--accent);margin-right:10px}\n  .storm-tut .shead .sdesc{display:block;margin-top:9px;font-family:var(--sans);font-size:14.5px;letter-spacing:0;text-transform:none;color:var(--muted);line-height:1.6}\n  .storm-tut .cards{max-width:1080px;margin:0 auto;padding:20px 24px 6px;display:grid;gap:14px;grid-template-columns:repeat(2,minmax(0,1fr))}\n  @media(max-width:840px){.storm-tut .cards{grid-template-columns:1fr}}\n  .storm-tut .tcard{display:block;border:1px solid var(--border-soft);border-radius:14px;padding:20px 22px;background:var(--panel-2);transition:.16s}\n  .storm-tut .tcard:hover{border-color:rgba(129,140,248,.45);transform:translateY(-1px)}\n  .storm-tut .tcard:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-color:rgba(129,140,248,.45)}\n  .storm-tut .tcard .tt{font-size:16.5px;font-weight:650;letter-spacing:-.01em}\n  .storm-tut .tcard .tt .arrow{color:var(--accent);margin-left:8px}\n  .storm-tut .tcard .td{color:var(--muted);font-size:13.5px;line-height:1.62;margin-top:8px}\n  .storm-tut .tcard .tm{display:flex;gap:8px;margin-top:14px;flex-wrap:wrap}\n  /* --muted rather than --faint: at 11px these are normal-size text, and --faint\n     on the card background measures 2.72:1, below the 4.5:1 minimum. --muted\n     clears it at 5.76:1 while staying quieter than the body copy. */\n  .storm-tut .tcard .tm span{font-family:var(--mono);font-size:11px;color:var(--muted);border:1px solid var(--border);border-radius:999px;padding:4px 11px}\n  .storm-tut .soon{max-width:1080px;margin:0 auto;padding:30px 24px 70px}\n  .storm-tut .soon .lbl{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}\n  .storm-tut .soon .chips{display:flex;gap:8px;margin-top:14px;flex-wrap:wrap}\n  .storm-tut .soon .chips span{font-family:var(--mono);font-size:12px;color:var(--muted);background:var(--panel-2);border:1px solid var(--border-soft);border-radius:8px;padding:7px 14px}\n  .storm-tut .soon p{color:var(--faint);font-size:13.5px;margin-top:16px}\n  .storm-tut .soon a{color:var(--muted)}\n  .storm-tut .soon a:hover{color:var(--accent)}\n\n  .storm-tut footer{border-top:1px solid var(--border-soft);margin-top:70px;padding:36px 0;color:var(--faint);font-size:13.5px}\n  .storm-tut .foot{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:14px}\n  .storm-tut .foot .links{display:flex;gap:22px;font-family:var(--mono);flex-wrap:wrap}.storm-tut .foot a{color:var(--muted)}.storm-tut .foot a:hover{color:var(--text)}\n  @media(max-width:1100px){.storm-tut .tech-tag{display:none}}\n  /* Narrow screens: replace the row of links with a hamburger that drops down a\n     full menu (CSS-only via a hidden checkbox, so no JS is needed on any page).\n     The breakpoint is where the full row of links stops fitting beside the\n     brand, not the phone breakpoint. */\n  @media(max-width:1000px){\n    /* The menu is CSS-only (a checkbox drives the drop-down), so the checkbox is\n       the control a keyboard reaches: clipped to 1px rather than display:none,\n       which would take it out of the tab order and leave the mobile menu\n       keyboard-unreachable. Clicks land on the label, which forwards them. */\n    .storm-tut .nav-toggle-cb{display:block;position:absolute;width:1px;height:1px;margin:0;padding:0;opacity:0;pointer-events:none}\n    .storm-tut .nav-toggle{display:flex;flex-direction:column;justify-content:center;gap:5px;width:40px;height:38px;padding:9px 8px;cursor:pointer;border:1px solid var(--border);border-radius:9px;background:var(--panel)}
1\n    .storm-tut .nav-toggle span{display:block;height:2px;width:100%;background:var(--text);border-radius:2px;transition:transform .2s,opacity .2s}\n    .storm-tut .nav-toggle-cb:checked ~ .nav-toggle span:nth-child(1){transform:translateY(7px) rotate(45deg)}\n    .storm-tut .nav-toggle-cb:checked ~ .nav-toggle span:nth-child(2){opacity:0}\n    .storm-tut .nav-toggle-cb:checked ~ .nav-toggle span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}\n    .storm-tut .nav-links{position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;gap:0;background:rgba(7,7,9,.98);backdrop-filter:blur(12px);border-bottom:1px solid var(--border-soft);padding:8px 0;display:none}\n    .storm-tut .nav-toggle-cb:checked ~ .nav-links{display:flex}\n    .storm-tut .nav-links a{padding:13px 24px;font-size:15px}\n    .storm-tut .nav-links a.btn{margin:10px 24px 6px;justify-content:center}\n  }\n"},53293(t,e,a){a.r(e),a.d(e,{default:()=>m});a(96540);var o=a(89756),n=a(74848);const r=[(0,o.C)("// Filtering: type-safe and readable\n"),(0,o.K)("val "),(0,o.P)("atAcme = "),(0,o.F)("transaction"),(0,o.P)(" {\n"),(0,o.P)("    "),(0,o.T)("Users"),(0,o.P)("."),(0,o.F)("selectAll"),(0,o.P)("()\n"),(0,o.P)("        ."),(0,o.F)("where"),(0,o.P)(" { "),(0,o.T)("Users"),(0,o.P)(".email "),(0,o.K)("like"),(0,o.P)(" "),(0,o.S)('"%@acme.io"'),(0,o.P)(" }\n"),(0,o.P)("        ."),(0,o.F)("map"),(0,o.P)(" { it["),(0,o.T)("Users"),(0,o.P)(".email] to it["),(0,o.T)("Users"),(0,o.P)(".name] }\n"),(0,o.P)("}\n\n"),(0,o.C)("// Aggregating: select specific columns, group, map the rows\n"),(0,o.K)("val "),(0,o.P)("perCity = "),(0,o.F)("transaction"),(0,o.P)(" {\n"),(0,o.P)("    "),(0,o.T)("Users"),(0,o.P)("."),(0,o.F)("select"),(0,o.P)("("),(0,o.T)("Users"),(0,o.P)(".cityId, "),(0,o.T)("Users"),(0,o.P)(".id."),(0,o.F)("count"),(0,o.P)("())\n"),(0,o.P)("        ."),(0,o.F)("groupBy"),(0,o.P)("("),(0,o.T)("Users"),(0,o.P)(".cityId)\n"),(0,o.P)("        ."),(0,o.F)("map"),(0,o.P)(" { it["),(0,o.T)("Users"),(0,o.P)(".cityId] to it["),(0,o.T)("Users"),(0,o.P)(".id."),(0,o.F)("count"),(0,o.P)("()] }\n"),(0,o.P)("}")].join(""),s=[(0,o.C)("// Filtering: the same idea, one line, no mapping step\n"),(0,o.K)("val "),(0,o.P)("atAcme = orm."),(0,o.F)("findAll"),(0,o.P)("("),(0,o.T)("User_"),(0,o.P)(".email "),(0,o.K)("like"),(0,o.P)(" "),(0,o.S)('"%@acme.io"'),(0,o.P)(")\n\n"),(0,o.C)("// Aggregating: supply the select clause, keep the typed rest\n"),(0,o.K)("data class "),(0,o.T)("CityCount"),(0,o.P)("("),(0,o.K)("val "),(0,o.P)("city: "),(0,o.T)("City"),(0,o.P)(", "),(0,o.K)("val "),(0,o.P)("count: "),(0,o.T)("Long"),(0,o.P)(")\n\n"),(0,o.K)("val "),(0,o.P)("perCity = orm."),(0,o.F)("entity"),(0,o.P)("<"),(0,o.T)("User"),(0,o.P)(">()\n"),(0,o.P)("    ."),(0,o.F)("select"),(0,o.P)("<"),(0,o.T)("CityCount"),(0,o.P)(", _, _> { "),(0,o.S)('"${City::class}, COUNT(*)"'),(0,o.P)(" }\n"),(0,o.P)("    ."),(0,o.F)("groupBy"),(0,o.P)("("),(0,o.T)("User_"),(0,o.P)(".city)\n"),(0,o.P)("    .resultList")].join(""),i=[(0,o.QC)("-- the aggregate: City expands to its columns, the join comes from the model"),"\n",(0,o.QK)("SELECT")," c.id, c.name, c.population, c.country, ",(0,o.QK)("COUNT"),"(*)\n",(0,o.QK)("FROM"),' "user" u\n',(0,o.QK)("INNER JOIN")," city c ",(0,o.QK)("ON")," u.city_id = c.id\n",(0,o.QK)("GROUP BY")," u.city_id"].join(""),l=[(0,o.C)("// Past the DSL, exec() hands you the raw JDBC ResultSet\n"),(0,o.K)("val "),(0,o.P)("ranked = "),(0,o.F)("transaction"),(0,o.P)(" {\n"),(0,o.P)("    "),(0,o.F)("exec"),(0,o.P)("("),(0,o.S)('"""\n        SELECT name, population, RANK() OVER (ORDER BY population DESC) AS rank\n        FROM city\n    """'),(0,o.P)(") { resultSet ->\n"),(0,o.P)("        "),(0,o.F)("buildList"),(0,o.P)(" {\n"),(0,o.P)("            "),(0,o.K)("while"),(0,o.P)(" (resultSet."),(0,o.F)("next"),(0,o.P)("()) {\n"),(0,o.P)("                "),(0,o.F)("add"),(0,o.P)("("),(0,o.T)("RankedCity"),(0,o.P)("(resultSet."),(0,o.F)("getString"),(0,o.P)("("),(0,o.N)("1"),(0,o.P)("), resultSet."),(0,o.F)("getInt"),(0,o.P)("("),(0,o.N)("2"),(0,o.P)("), resultSet."),(0,o.F)("getLong"),(0,o.P)("("),(0,o.N)("3"),(0,o.P)(")))\n"),(0,o.P)("            }\n"),(0,o.P)("        }\n"),(0,o.P)("    }\n"),(0,o.P)("}")].join(""),p=[(0,o.C)("// Full SQL stays typed: rows map by position, values bind safely\n"),(0,o.K)("data class "),(0,o.T)("RankedCity"),(0,o.P)("("),(0,o.K)("val "),(0,o.P)("name: "),(0,o.T)("String"),(0,o.P)(", "),(0,o.K)("val "),(0,o.P)("population: "),(0,o.T)("Int"),(0,o.P)(", "),(0,o.K)("val "),(0,o.P)("rank: "),(0,o.T)("Long"),(0,o.P)(")\n\n"),(0,o.K)("val "),(0,o.P)("ranked = orm."),(0,o.F)("query"),(0,o.P)(" { "),(0,o.S)('"""'),(0,o.P)("\n"),(0,o.P)("    "),(0,o.K)("SELECT"),(0,o.P)(" name, population, RANK() "),(0,o.K)("OVER"),(0,o.P)(" ("),(0,o.K)("ORDER BY"),(0,o.P)(" population "),(0,o.K)("DESC"),(0,o.P)(")\n"),(0,o.P)("    "),(0,o.K)("FROM"),(0,o.P)(" city\n"),(0,o.P)("    "),(0,o.K)("WHERE"),(0,o.P)(" country = "),(0,o.T)("$country"),(0,o.P)("\n"),(0,o.S)('"""'),(0,o.P)(" }."),(0,o.F)("resultList"),(0,o.P)("<"),(0,o.T)("RankedCity"),(0,o.P)(">()")].join(""),d=[(0,o.QC)("-- $country compiles to a bind parameter"),"\n",(0,o.QK)("SELECT")," name, population, ",(0,o.QK)("RANK"),"() ",(0,o.QK)("OVER")," (",(0,o.QK)("ORDER BY")," population ",(0,o.QK)("DESC"),")\n",(0,o.QK)("FROM")," city\n",(0,o.QK)("WHERE")," country = ",(0,o.QQ)("?")].join(""),c=`\n${(0,o.hz)("tutorials")}\n\n<div class="art">\n  <div class="crumbs"><a href="/tutorials/">Tutorials</a><span class="sep">/</span>Exposed vs Storm: queries</div>\n  <h1>Queries and <span class="grad">full SQL</span></h1>\n  <p class="dek">The everyday queries translate almost line by line, and Exposed's are perfectly pleasant to write. The gap opens past the DSL, where a window function or a vendor feature is involved.</p>\n  <div class="meta"><span>Series \xb7 Exposed to Storm</span><span>5 min read</span><span>Kotlin</span></div>\n\n  <h2><span class="hno">01</span>The task</h2>\n  <p>Three query shapes cover most of a data layer: a filter, an aggregate, and the occasional query that outgrows any DSL, here a window function ranking cities by population.</p>\n\n  <h2><span class="hno">02</span>The everyday queries</h2>\n  <p>Exposed's DSL handles filters and aggregates with compile-checked columns:</p>\n  ${(0,o.EN)({file:"UserService.kt",tag:"Kotlin \xb7 Exposed",code:r})}\n  <p>Storm's versions will read familiarly; the mapping step disappears because results hydrate into your types directly, and the join in the aggregate comes from the model rather than the query:</p>\n  ${(0,o.EN)({file:"UserService.kt",tag:"Kotlin \xb7 Storm",code:s,sql:i})}\n\n  <h2><span class="hno">03</span>Past the DSL</h2>\n  <p>Every DSL has an edge. Exposed's escape hatch is <code>exec()</code>, which runs the SQL and hands you the raw JDBC <code>ResultSet</code> to read yourself:</p>\n  ${(0,o.EN)({file:"ReportService.kt",tag:"Kotlin \xb7 Exposed",code:l})}\n  <p>Storm was built SQL-first, so dropping to full SQL keeps everything the DSL had: rows map positionally onto any data class, and interpolated Kotlin values compile to bind parameters instead of concatenation:</p>\n  ${(0,o.EN)({file:"ReportService.kt",tag:"Kotlin \xb7 Storm",code:p,sql:d})}\n  <p>The template engine goes further than pass-through SQL: type references expand to column lists and joins, and metamodel paths are compile-checked inside the SQL text. The <a class="tlink" href="/tutorials/sql-templates">
1SQL templates tutorial</a> covers that in depth.</p>\n\n  <h2><span class="hno">04</span>The translation table</h2>\n  <table class="cmp">\n    <tr><th>Exposed</th><th>Storm</th></tr>\n    <tr><td><code>selectAll().where { col like x }</code> plus <code>map</code></td><td><code>findAll(User_.email like x)</code></td></tr>\n    <tr><td><code>select(columns).groupBy(col)</code> plus <code>map</code></td><td><code>select&lt;CityCount, _, _&gt; { ... }.groupBy(...)</code>, typed rows</td></tr>\n    <tr><td><code>exec(sql) { resultSet -> ... }</code></td><td><code>query { sql }.resultList&lt;T&gt;()</code></td></tr>\n    <tr><td>Every query inside <code>transaction { }</code></td><td>Repositories manage connections for reads</td></tr>\n  </table>\n\n  <h2><span class="hno">05</span>Keep going</h2>\n  <p>The reference documentation covers the mechanics in depth:</p>\n  <div class="refs">\n    <a href="/docs/queries">Queries</a>\n    <a href="/docs/sql-templates">SQL Templates</a>\n    <a href="/docs/hydration">Hydration</a>\n    <a href="/docs/comparison">Framework Comparison</a>\n  </div>\n  <div class="cta">\n    <a href="/docs/getting-started" class="btn primary">Get started \u2192</a>\n    <a href="https://github.com/storm-orm/storm-framework" target="_blank" rel="noopener" class="btn">Star on GitHub</a>\n  </div>\n</div>\n\n${o.rk}\n`;function m(){return(0,n.jsx)(o.UD,{title:"Queries and Full SQL: Exposed vs Storm",description:"Everyday Exposed queries translate to Storm almost line by line. The gap appears past the DSL: Exposed's exec() hands you a raw ResultSet, while Storm's SQL templates keep typed rows and bind-safe parameters.",slug:"exposed-queries",body:c})}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.