[{"data":1,"prerenderedAt":1767},["ShallowReactive",2],{"navigation_docs":3,"-guide-concepts-database-extensions":587,"-guide-concepts-database-extensions-surround":1762},[4,50,155,441,504],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":49},"Get Started","i-lucide-rocket","\u002Fget-started","1.get-started",[10,15,20,25,30,35,40,45],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fget-started\u002Fintroduction","1.get-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fget-started\u002Finstallation","1.get-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Project structure","\u002Fget-started\u002Fproject-structure","1.get-started\u002F3.project-structure","i-lucide-folder-tree",{"title":26,"path":27,"stem":28,"icon":29},"Configuration","\u002Fget-started\u002Fconfiguration","1.get-started\u002F4.configuration","i-lucide-settings",{"title":31,"path":32,"stem":33,"icon":34},"Authentication","\u002Fget-started\u002Fauthentication","1.get-started\u002F5.authentication","i-lucide-lock",{"title":36,"path":37,"stem":38,"icon":39},"Adding modules","\u002Fget-started\u002Fadding-modules","1.get-started\u002F6.adding-modules","i-lucide-puzzle",{"title":41,"path":42,"stem":43,"icon":44},"Database","\u002Fget-started\u002Fdatabase","1.get-started\u002F7.database","i-lucide-database",{"title":46,"path":47,"stem":48,"icon":6},"Deployment","\u002Fget-started\u002Fdeployment","1.get-started\u002F8.deployment",false,{"title":51,"icon":52,"path":53,"stem":54,"children":55,"page":49},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[56,90,115,125],{"title":57,"icon":49,"path":58,"stem":59,"children":60,"page":49},"Concepts","\u002Fguide\u002Fconcepts","2.guide\u002F1.concepts",[61,66,71,76,80,85],{"title":62,"path":63,"stem":64,"icon":65},"Architecture","\u002Fguide\u002Fconcepts\u002Farchitecture","2.guide\u002F1.concepts\u002F1.architecture","i-lucide-blocks",{"title":67,"path":68,"stem":69,"icon":70},"Access control","\u002Fguide\u002Fconcepts\u002Fauth-and-scopes","2.guide\u002F1.concepts\u002F2.auth-and-scopes","i-lucide-shield-check",{"title":72,"path":73,"stem":74,"icon":75},"Extension points","\u002Fguide\u002Fconcepts\u002Fextension-points","2.guide\u002F1.concepts\u002F3.extension-points","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":44},"Database extensions","\u002Fguide\u002Fconcepts\u002Fdatabase-extensions","2.guide\u002F1.concepts\u002F4.database-extensions",{"title":81,"path":82,"stem":83,"icon":84},"Identity","\u002Fguide\u002Fconcepts\u002Fidentity","2.guide\u002F1.concepts\u002F5.identity","i-lucide-fingerprint",{"title":86,"path":87,"stem":88,"icon":89},"Glossary","\u002Fguide\u002Fconcepts\u002Fglossary","2.guide\u002F1.concepts\u002F6.glossary","i-lucide-book-a",{"title":91,"icon":49,"path":92,"stem":93,"children":94,"page":49},"UX patterns","\u002Fguide\u002Fux-patterns","2.guide\u002F2.ux-patterns",[95,100,105,110],{"title":96,"path":97,"stem":98,"icon":99},"Layouts and navigation","\u002Fguide\u002Fux-patterns\u002Flayouts-and-navigation","2.guide\u002F2.ux-patterns\u002F1.layouts-and-navigation","i-lucide-layout-template",{"title":101,"path":102,"stem":103,"icon":104},"Pages","\u002Fguide\u002Fux-patterns\u002Fpages","2.guide\u002F2.ux-patterns\u002F2.pages","i-lucide-table",{"title":106,"path":107,"stem":108,"icon":109},"Forms and actions","\u002Fguide\u002Fux-patterns\u002Fforms-and-actions","2.guide\u002F2.ux-patterns\u002F3.forms-and-actions","i-lucide-square-pen",{"title":111,"path":112,"stem":113,"icon":114},"Text and formatting","\u002Fguide\u002Fux-patterns\u002Ftext-and-formatting","2.guide\u002F2.ux-patterns\u002F4.text-and-formatting","i-lucide-type",{"title":116,"icon":49,"path":117,"stem":118,"children":119,"page":49},"Best practices","\u002Fguide\u002Fbest-practices","2.guide\u002F3.best-practices",[120],{"title":121,"path":122,"stem":123,"icon":124},"HTTP routes","\u002Fguide\u002Fbest-practices\u002Fhttp-routes","2.guide\u002F3.best-practices\u002F1.http-routes","i-lucide-route",{"title":126,"icon":49,"path":127,"stem":128,"children":129,"page":49},"Going further","\u002Fguide\u002Fgoing-further","2.guide\u002F4.going-further",[130,135,140,145,150],{"title":131,"path":132,"stem":133,"icon":134},"Use your own OAuth server","\u002Fguide\u002Fgoing-further\u002Fown-oauth-server","2.guide\u002F4.going-further\u002F1.own-oauth-server","i-lucide-key-round",{"title":136,"path":137,"stem":138,"icon":139},"Create an OAuth application","\u002Fguide\u002Fgoing-further\u002Foauth-application","2.guide\u002F4.going-further\u002F2.oauth-application","i-lucide-app-window",{"title":141,"path":142,"stem":143,"icon":144},"Signing keys","\u002Fguide\u002Fgoing-further\u002Fsigning-keys","2.guide\u002F4.going-further\u002F3.signing-keys","i-lucide-key-square",{"title":146,"path":147,"stem":148,"icon":149},"Stripe setup","\u002Fguide\u002Fgoing-further\u002Fstripe-setup","2.guide\u002F4.going-further\u002F4.stripe-setup","i-lucide-credit-card",{"title":151,"path":152,"stem":153,"icon":154},"Migrate to the permission model","\u002Fguide\u002Fgoing-further\u002Fmigrate-access-control","2.guide\u002F4.going-further\u002F5.migrate-access-control","i-lucide-arrow-right-left",{"title":156,"icon":157,"path":158,"stem":159,"children":160},"Modules","i-lucide-boxes","\u002Fmodules","3.modules",[161,163,244,273,320,375],{"title":156,"path":158,"stem":162},"3.modules\u002Findex",{"title":81,"icon":49,"path":164,"stem":165,"children":166,"page":49},"\u002Fmodules\u002Fidentity","3.modules\u002F1.identity",[167,190,194,198,220,224,240],{"title":168,"path":169,"stem":170,"children":171},"Auth","\u002Fmodules\u002Fidentity\u002Fauth","3.modules\u002F1.identity\u002F1.auth\u002Findex",[172,174,178,182,186],{"title":173,"path":169,"stem":170},"Overview",{"title":175,"path":176,"stem":177},"Server API","\u002Fmodules\u002Fidentity\u002Fauth\u002Fserver","3.modules\u002F1.identity\u002F1.auth\u002F2.server",{"title":179,"path":180,"stem":181},"Reference","\u002Fmodules\u002Fidentity\u002Fauth\u002Freference","3.modules\u002F1.identity\u002F1.auth\u002F3.reference",{"title":183,"path":184,"stem":185},"Client mode","\u002Fmodules\u002Fidentity\u002Fauth\u002Fclient-mode","3.modules\u002F1.identity\u002F1.auth\u002F4.client-mode",{"title":187,"path":188,"stem":189},"OAuth providers","\u002Fmodules\u002Fidentity\u002Fauth\u002Foauth-providers","3.modules\u002F1.identity\u002F1.auth\u002F5.oauth-providers",{"title":191,"path":192,"stem":193},"Users","\u002Fmodules\u002Fidentity\u002Fusers","3.modules\u002F1.identity\u002F2.users",{"title":195,"path":196,"stem":197},"Account","\u002Fmodules\u002Fidentity\u002Faccount","3.modules\u002F1.identity\u002F3.account",{"title":199,"path":200,"stem":201,"children":202},"Authorization","\u002Fmodules\u002Fidentity\u002Fauthorization","3.modules\u002F1.identity\u002F4.authorization\u002Findex",[203,204,208,212,216],{"title":173,"path":200,"stem":201},{"title":205,"path":206,"stem":207},"Roles and requirements","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Froles-and-requirements","3.modules\u002F1.identity\u002F4.authorization\u002F2.roles-and-requirements",{"title":209,"path":210,"stem":211},"Policies","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fpolicies","3.modules\u002F1.identity\u002F4.authorization\u002F3.policies",{"title":213,"path":214,"stem":215},"Client","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fclient","3.modules\u002F1.identity\u002F4.authorization\u002F4.client",{"title":217,"path":218,"stem":219},"Admin","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fadmin","3.modules\u002F1.identity\u002F4.authorization\u002F5.admin",{"title":221,"path":222,"stem":223},"Preferences","\u002Fmodules\u002Fidentity\u002Fpreferences","3.modules\u002F1.identity\u002F5.preferences",{"title":225,"path":226,"stem":227,"children":228},"OAuth Server","\u002Fmodules\u002Fidentity\u002Foauth-server","3.modules\u002F1.identity\u002F6.oauth-server\u002Findex",[229,230,233,237],{"title":173,"path":226,"stem":227},{"title":175,"path":231,"stem":232},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver","3.modules\u002F1.identity\u002F6.oauth-server\u002F2.server",{"title":234,"path":235,"stem":236},"Hooks","\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fhooks","3.modules\u002F1.identity\u002F6.oauth-server\u002F3.hooks",{"title":179,"path":238,"stem":239},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients","3.modules\u002F1.identity\u002F6.oauth-server\u002F4.routes-and-clients",{"title":241,"path":242,"stem":243},"OAuth Resource","\u002Fmodules\u002Fidentity\u002Foauth-resource","3.modules\u002F1.identity\u002F8.oauth-resource",{"title":245,"icon":49,"path":246,"stem":247,"children":248,"page":49},"Commerce","\u002Fmodules\u002Fcommerce","3.modules\u002F2.commerce",[249,253,257],{"title":250,"path":251,"stem":252},"Stripe","\u002Fmodules\u002Fcommerce\u002Fstripe","3.modules\u002F2.commerce\u002F1.stripe",{"title":254,"path":255,"stem":256},"Payments","\u002Fmodules\u002Fcommerce\u002Fpayments","3.modules\u002F2.commerce\u002F2.payments",{"title":258,"path":259,"stem":260,"children":261},"Subscriptions","\u002Fmodules\u002Fcommerce\u002Fsubscriptions","3.modules\u002F2.commerce\u002F3.subscriptions\u002Findex",[262,263,266,270],{"title":173,"path":259,"stem":260},{"title":175,"path":264,"stem":265},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fserver","3.modules\u002F2.commerce\u002F3.subscriptions\u002F2.server",{"title":267,"path":268,"stem":269},"App","\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fapp","3.modules\u002F2.commerce\u002F3.subscriptions\u002F3.app",{"title":179,"path":271,"stem":272},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Freference","3.modules\u002F2.commerce\u002F3.subscriptions\u002F4.reference",{"title":274,"icon":49,"path":275,"stem":276,"children":277,"page":49},"Features","\u002Fmodules\u002Ffeatures","3.modules\u002F3.features",[278,293,312,316],{"title":279,"path":280,"stem":281,"children":282},"Events","\u002Fmodules\u002Ffeatures\u002Fevents","3.modules\u002F3.features\u002F1.events\u002Findex",[283,284,287,290],{"title":173,"path":280,"stem":281},{"title":175,"path":285,"stem":286},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fserver","3.modules\u002F3.features\u002F1.events\u002F2.server",{"title":267,"path":288,"stem":289},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fapp","3.modules\u002F3.features\u002F1.events\u002F3.app",{"title":179,"path":291,"stem":292},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Freference","3.modules\u002F3.features\u002F1.events\u002F4.reference",{"title":294,"path":295,"stem":296,"children":297},"Forms","\u002Fmodules\u002Ffeatures\u002Fforms","3.modules\u002F3.features\u002F2.forms\u002Findex",[298,299,303,306,309],{"title":173,"path":295,"stem":296},{"title":300,"path":301,"stem":302},"Definition","\u002Fmodules\u002Ffeatures\u002Fforms\u002Fdefinition","3.modules\u002F3.features\u002F2.forms\u002F2.definition",{"title":175,"path":304,"stem":305},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fserver","3.modules\u002F3.features\u002F2.forms\u002F3.server",{"title":267,"path":307,"stem":308},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fapp","3.modules\u002F3.features\u002F2.forms\u002F4.app",{"title":179,"path":310,"stem":311},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Freference","3.modules\u002F3.features\u002F2.forms\u002F5.reference",{"title":313,"path":314,"stem":315},"Cookie Consent","\u002Fmodules\u002Ffeatures\u002Fcookie-consent","3.modules\u002F3.features\u002F3.cookie-consent",{"title":317,"path":318,"stem":319},"MCP","\u002Fmodules\u002Ffeatures\u002Fmcp","3.modules\u002F3.features\u002F4.mcp",{"title":321,"icon":49,"path":322,"stem":323,"children":324,"page":49},"Interface","\u002Fmodules\u002Finterface","3.modules\u002F4.interface",[325,350,353,357,361],{"title":326,"path":327,"stem":328,"children":329},"UI","\u002Fmodules\u002Finterface\u002Fui","3.modules\u002F4.interface\u002F1.ui\u002Findex",[330,331,335,339,343,347],{"title":173,"path":327,"stem":328},{"title":332,"path":333,"stem":334},"Page components","\u002Fmodules\u002Finterface\u002Fui\u002Fpage-components","3.modules\u002F4.interface\u002F1.ui\u002F2.page-components",{"title":336,"path":337,"stem":338},"App shell","\u002Fmodules\u002Finterface\u002Fui\u002Fapp-shell","3.modules\u002F4.interface\u002F1.ui\u002F3.app-shell",{"title":340,"path":341,"stem":342},"Form inputs","\u002Fmodules\u002Finterface\u002Fui\u002Fform-inputs","3.modules\u002F4.interface\u002F1.ui\u002F4.form-inputs",{"title":344,"path":345,"stem":346},"Composables and helpers","\u002Fmodules\u002Finterface\u002Fui\u002Fcomposables","3.modules\u002F4.interface\u002F1.ui\u002F5.composables",{"title":179,"path":348,"stem":349},"\u002Fmodules\u002Finterface\u002Fui\u002Freference","3.modules\u002F4.interface\u002F1.ui\u002F6.reference",{"title":217,"path":351,"stem":352},"\u002Fmodules\u002Finterface\u002Fadmin","3.modules\u002F4.interface\u002F2.admin",{"title":354,"path":355,"stem":356},"Menu","\u002Fmodules\u002Finterface\u002Fmenu","3.modules\u002F4.interface\u002F3.menu",{"title":358,"path":359,"stem":360},"Widget","\u002Fmodules\u002Finterface\u002Fwidget","3.modules\u002F4.interface\u002F4.widget",{"title":362,"path":363,"stem":364,"children":365},"Mobile","\u002Fmodules\u002Finterface\u002Fmobile","3.modules\u002F4.interface\u002F5.mobile\u002Findex",[366,367,371],{"title":173,"path":363,"stem":364},{"title":368,"path":369,"stem":370},"CLI and devices","\u002Fmodules\u002Finterface\u002Fmobile\u002Fcli-and-devices","3.modules\u002F4.interface\u002F5.mobile\u002F2.cli-and-devices",{"title":372,"path":373,"stem":374},"Splash screen","\u002Fmodules\u002Finterface\u002Fmobile\u002Fsplash-screen","3.modules\u002F4.interface\u002F5.mobile\u002F3.splash-screen",{"title":376,"icon":49,"path":377,"stem":378,"children":379,"page":49},"Platform","\u002Fmodules\u002Fplatform","3.modules\u002F5.platform",[380,402,406,423],{"title":381,"path":382,"stem":383,"children":384},"Module Kit","\u002Fmodules\u002Fplatform\u002Fmodule-kit","3.modules\u002F5.platform\u002F1.module-kit\u002Findex",[385,386,390,394,398],{"title":173,"path":382,"stem":383},{"title":387,"path":388,"stem":389},"Server toolkit","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit","3.modules\u002F5.platform\u002F1.module-kit\u002F2.server-toolkit",{"title":391,"path":392,"stem":393},"Registries and menus","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fregistries-and-menus","3.modules\u002F5.platform\u002F1.module-kit\u002F3.registries-and-menus",{"title":395,"path":396,"stem":397},"Translations","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fi18n","3.modules\u002F5.platform\u002F1.module-kit\u002F4.i18n",{"title":399,"path":400,"stem":401},"Database helpers","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fdatabase","3.modules\u002F5.platform\u002F1.module-kit\u002F5.database",{"title":403,"path":404,"stem":405},"Notify","\u002Fmodules\u002Fplatform\u002Fnotify","3.modules\u002F5.platform\u002F2.notify",{"title":407,"path":408,"stem":409,"children":410},"Email Templates","\u002Fmodules\u002Fplatform\u002Femail-templates","3.modules\u002F5.platform\u002F3.email-templates\u002Findex",[411,412,416,419],{"title":173,"path":408,"stem":409},{"title":413,"path":414,"stem":415},"Email components","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fcomponents","3.modules\u002F5.platform\u002F3.email-templates\u002F2.components",{"title":175,"path":417,"stem":418},"\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fserver","3.modules\u002F5.platform\u002F3.email-templates\u002F3.server",{"title":420,"path":421,"stem":422},"Module emails","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fmodule-emails","3.modules\u002F5.platform\u002F3.email-templates\u002F4.module-emails",{"title":424,"path":425,"stem":426,"children":427},"Base Layer","\u002Fmodules\u002Fplatform\u002Fbase-layer","3.modules\u002F5.platform\u002F4.base-layer\u002Findex",[428,429,433,437],{"title":173,"path":425,"stem":426},{"title":430,"path":431,"stem":432},"Content collections","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcontent-collections","3.modules\u002F5.platform\u002F4.base-layer\u002F2.content-collections",{"title":434,"path":435,"stem":436},"Layouts and routing","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Flayouts-and-routing","3.modules\u002F5.platform\u002F4.base-layer\u002F3.layouts-and-routing",{"title":438,"path":439,"stem":440},"Composables and utils","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcomposables-and-utils","3.modules\u002F5.platform\u002F4.base-layer\u002F4.composables-and-utils",{"title":442,"icon":443,"path":444,"stem":445,"children":446,"page":49},"Examples","i-lucide-code-xml","\u002Fexamples","4.examples",[447,466,476],{"title":448,"icon":49,"path":449,"stem":450,"children":451,"page":49},"Starters","\u002Fexamples\u002Fstarters","4.examples\u002F1.starters",[452,456,461],{"title":453,"path":454,"stem":455,"icon":6},"Workspace starter","\u002Fexamples\u002Fstarters\u002Fworkspace","4.examples\u002F1.starters\u002F1.workspace",{"title":457,"path":458,"stem":459,"icon":460},"Mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile","4.examples\u002F1.starters\u002F2.mobile","i-lucide-smartphone",{"title":462,"path":463,"stem":464,"icon":465},"Standalone mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile-standalone","4.examples\u002F1.starters\u002F3.mobile-standalone","i-lucide-tablet-smartphone",{"title":467,"icon":49,"path":468,"stem":469,"children":470,"page":49},"Apps","\u002Fexamples\u002Fapps","4.examples\u002F2.apps",[471],{"title":472,"path":473,"stem":474,"icon":475},"Frigear website app","\u002Fexamples\u002Fapps\u002Ffrigear-nu-website","4.examples\u002F2.apps\u002F1.frigear-nu-website","i-lucide-layout-dashboard",{"title":477,"icon":49,"path":478,"stem":479,"children":480,"page":49},"Tasks","\u002Fexamples\u002Ftasks","4.examples\u002F3.tasks",[481,485,489,494,499],{"title":482,"path":483,"stem":484,"icon":70},"Protect a route","\u002Fexamples\u002Ftasks\u002Fprotect-a-route","4.examples\u002F3.tasks\u002F1.protect-a-route",{"title":486,"path":487,"stem":488,"icon":475},"Add an admin page","\u002Fexamples\u002Ftasks\u002Fadd-an-admin-page","4.examples\u002F3.tasks\u002F2.add-an-admin-page",{"title":490,"path":491,"stem":492,"icon":493},"Sell event tickets","\u002Fexamples\u002Ftasks\u002Fsell-event-tickets","4.examples\u002F3.tasks\u002F3.sell-event-tickets","i-lucide-ticket",{"title":495,"path":496,"stem":497,"icon":498},"Build a form","\u002Fexamples\u002Ftasks\u002Fbuild-a-form","4.examples\u002F3.tasks\u002F4.build-a-form","i-lucide-clipboard-list",{"title":500,"path":501,"stem":502,"icon":503},"Send an email","\u002Fexamples\u002Ftasks\u002Fsend-an-email","4.examples\u002F3.tasks\u002F5.send-an-email","i-lucide-mail",{"title":505,"icon":506,"path":507,"stem":508,"children":509,"page":49},"Contribute","i-lucide-git-pull-request","\u002Fcontribute","5.contribute",[510,513,518,544,549,552,556,571],{"title":173,"path":511,"stem":512,"icon":506},"\u002Fcontribute\u002Foverview","5.contribute\u002F1.overview",{"title":514,"path":515,"stem":516,"icon":517},"Local development","\u002Fcontribute\u002Flocal-development","5.contribute\u002F2.local-development","i-lucide-terminal",{"title":519,"icon":49,"path":520,"stem":521,"children":522,"page":49},"Writing a module","\u002Fcontribute\u002Fwriting-a-module","5.contribute\u002F3.writing-a-module",[523,527,532,536,541],{"title":524,"path":525,"stem":526,"icon":39},"Scaffold a module","\u002Fcontribute\u002Fwriting-a-module\u002Fscaffold","5.contribute\u002F3.writing-a-module\u002F1.scaffold",{"title":528,"path":529,"stem":530,"icon":531},"Setup checklist","\u002Fcontribute\u002Fwriting-a-module\u002Fsetup-checklist","5.contribute\u002F3.writing-a-module\u002F2.setup-checklist","i-lucide-list-checks",{"title":533,"path":534,"stem":535,"icon":44},"Database tables","\u002Fcontribute\u002Fwriting-a-module\u002Fdatabase-tables","5.contribute\u002F3.writing-a-module\u002F3.database-tables",{"title":537,"path":538,"stem":539,"icon":540},"Testing","\u002Fcontribute\u002Fwriting-a-module\u002Ftesting","5.contribute\u002F3.writing-a-module\u002F4.testing","i-lucide-flask-conical",{"title":67,"path":542,"stem":543,"icon":70},"\u002Fcontribute\u002Fwriting-a-module\u002Faccess-control","5.contribute\u002F3.writing-a-module\u002F5.access-control",{"title":545,"path":546,"stem":547,"icon":548},"Publishing","\u002Fcontribute\u002Fpublishing","5.contribute\u002F4.publishing","i-lucide-package-check",{"title":448,"path":550,"stem":551,"icon":6},"\u002Fcontribute\u002Fstarters","5.contribute\u002F5.starters",{"title":553,"path":554,"stem":555,"icon":52},"Documentation","\u002Fcontribute\u002Fdocumentation","5.contribute\u002F6.documentation",{"title":557,"icon":49,"path":558,"stem":559,"children":560,"page":49},"Tooling","\u002Fcontribute\u002Ftooling","5.contribute\u002F7.tooling",[561,566],{"title":562,"path":563,"stem":564,"icon":565},"CLI","\u002Fcontribute\u002Ftooling\u002Fcli","5.contribute\u002F7.tooling\u002F1.cli","i-lucide-square-terminal",{"title":567,"path":568,"stem":569,"icon":570},"ESLint config","\u002Fcontribute\u002Ftooling\u002Feslint-config","5.contribute\u002F7.tooling\u002F2.eslint-config","i-lucide-spell-check",{"title":572,"icon":49,"path":573,"stem":574,"children":575},"Decisions","\u002Fcontribute\u002Fdecisions","5.contribute\u002F8.decisions\u002Findex",[576,578,583],{"title":572,"path":573,"stem":574,"icon":577},"i-lucide-scale",{"title":579,"path":580,"stem":581,"icon":582},"Session lifetimes per OAuth client","\u002Fcontribute\u002Fdecisions\u002Foauth-token-lifetimes","5.contribute\u002F8.decisions\u002F1.oauth-token-lifetimes","i-lucide-timer",{"title":584,"path":585,"stem":586,"icon":70},"One permission model","\u002Fcontribute\u002Fdecisions\u002Fpermission-model","5.contribute\u002F8.decisions\u002F2.permission-model",{"id":588,"title":77,"body":589,"description":1755,"extension":1756,"links":1757,"meta":1758,"navigation":1759,"path":78,"seo":1760,"stem":79,"__hash__":1761},"docs\u002F2.guide\u002F1.concepts\u002F4.database-extensions.md",{"type":590,"value":591,"toc":1743},"minimark",[592,605,614,619,721,725,766,770,775,792,800,804,808,815,903,909,912,915,918,973,1091,1094,1222,1225,1242,1245,1249,1252,1255,1280,1283,1299,1409,1415,1554,1567,1582,1585,1591,1606,1612,1670,1685,1688,1702,1706,1712,1718,1722,1739],[593,594,595,596,600,601,604],"p",{},"Your app needs one more column on a table a module owns, such as a ",[597,598,599],"code",{},"locale"," on users. A module may need one too, such\nas the ",[597,602,603],{},"role"," that Authorization adds to users. Neither can edit the owner's schema. After this page you can decide\nwhere to declare the column, how to keep it working on SQLite and PostgreSQL, and which migration to commit.",[606,607,609,613],"callout",{"icon":608},"i-lucide-users",[610,611,612],"strong",{},"For:"," app developers (extending a table) and module authors (owning or extending one).",[615,616,618],"h2",{"id":617},"which-one-do-i-use","Which one do I use?",[620,621,622,638],"table",{},[623,624,625],"thead",{},[626,627,628,632,635],"tr",{},[629,630,631],"th",{},"You want to",[629,633,634],{},"Use",[629,636,637],{},"Read",[639,640,641,659,673,689,707],"tbody",{},[626,642,643,647,652],{},[644,645,646],"td",{},"Add a column to a module's table from your app",[644,648,649],{},[597,650,651],{},"server\u002Fdb\u002Fextend.\u003Cdialect>.ts",[644,653,654],{},[655,656,658],"a",{"href":657},"#extending-a-table","Extending a table",[626,660,661,664,669],{},[644,662,663],{},"Add a column to another module's table from your module",[644,665,666],{},[597,667,668],{},"extendDatabaseTable",[644,670,671],{},[655,672,658],{"href":657},[626,674,675,678,683],{},[644,676,677],{},"Give your module a table of its own",[644,679,680],{},[597,681,682],{},"registerDatabaseTable",[644,684,685],{},[655,686,688],{"href":687},"#registering-a-table","Registering a table",[626,690,691,694,701],{},[644,692,693],{},"Run on SQLite and PostgreSQL",[644,695,696,697,700],{},"A ",[597,698,699],{},".postgresql.ts"," twin of every schema file",[644,702,703],{},[655,704,706],{"href":705},"#one-schema-file-per-dialect","One schema file per dialect",[626,708,709,712,715],{},[644,710,711],{},"Get the new column into the database",[644,713,714],{},"A committed migration",[644,716,717],{},[655,718,720],{"href":719},"#migrations-are-committed-history","Migrations are committed history",[615,722,724],{"id":723},"terms","Terms",[726,727,728,735,741,750,758],"ul",{},[729,730,731,734],"li",{},[610,732,733],{},"Owning module."," The module that registers a table and ships its base schema file.",[729,736,737,740],{},[610,738,739],{},"Extension file."," A file that adds columns to a table it does not own, exported under the table's name.",[729,742,743,749],{},[610,744,745,748],{},[655,746,747],{"href":87},"Migration","."," A generated SQL (Structured Query Language) file that changes the database\nschema. You commit one whenever a module adds or changes tables.",[729,751,752,757],{},[610,753,754,748],{},[655,755,756],{"href":87},"Dialect"," The database type a schema is written for: SQLite or PostgreSQL.",[729,759,760,765],{},[610,761,762,748],{},[655,763,764],{"href":87},"Drizzle"," The TypeScript object-relational mapper (ORM) that modules use to define\ntables and generate migrations.",[615,767,769],{"id":768},"how-it-works","How it works",[593,771,772,774],{},[655,773,381],{"href":382}," lets one module own a Drizzle table while others extend it, without the owner\nknowing:",[726,776,777,782,787],{},[729,778,779,780,748],{},"The owning module registers a plain Drizzle schema file with ",[597,781,682],{},[729,783,784,785,748],{},"Other modules add columns with ",[597,786,668],{},[729,788,789,790,748],{},"The host app adds columns from its own ",[597,791,651],{},[593,793,794,795,799],{},"All three work on the generated NuxtHub schema. They change the ",[796,797,798],"em",{},"schema",", not the database.",[801,802,803],"warning",{},"Nothing alters an existing database until you generate and apply a migration.",[805,806,688],"h3",{"id":807},"registering-a-table",[593,809,810,811,814],{},"The owning module ships a schema file and registers it. Omit ",[597,812,813],{},"name",": every table export of the file is registered.",[816,817,822],"pre",{"className":818,"code":819,"language":820,"meta":821,"style":821},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F src\u002Fmodule.ts, in setup()\nregisterDatabaseTable(nuxt, {\n  resolver,\n  schema: dialect => `.\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.${dialect}`,\n})\n","ts","",[597,823,824,833,850,859,894],{"__ignoreMap":821},[825,826,829],"span",{"class":827,"line":828},"line",1,[825,830,832],{"class":831},"sHwdD","\u002F\u002F src\u002Fmodule.ts, in setup()\n",[825,834,836,839,843,847],{"class":827,"line":835},2,[825,837,682],{"class":838},"s2Zo4",[825,840,842],{"class":841},"sTEyZ","(nuxt",[825,844,846],{"class":845},"sMK4o",",",[825,848,849],{"class":845}," {\n",[825,851,853,856],{"class":827,"line":852},3,[825,854,855],{"class":841},"  resolver",[825,857,858],{"class":845},",\n",[825,860,862,865,868,872,876,879,883,886,889,892],{"class":827,"line":861},4,[825,863,864],{"class":838},"  schema",[825,866,867],{"class":845},":",[825,869,871],{"class":870},"sHdIc"," dialect",[825,873,875],{"class":874},"spNyl"," =>",[825,877,878],{"class":845}," `",[825,880,882],{"class":881},"sfazB",".\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.",[825,884,885],{"class":845},"${",[825,887,888],{"class":841},"dialect",[825,890,891],{"class":845},"}`",[825,893,858],{"class":845},[825,895,897,900],{"class":827,"line":896},5,[825,898,899],{"class":845},"}",[825,901,902],{"class":841},")\n",[593,904,905,906,908],{},"Pass ",[597,907,813],{}," only to restrict which tables are registered. Auth does this, because which of its tables exist depends on\nthe enabled features.",[805,910,658],{"id":911},"extending-a-table",[593,913,914],{},"An extension file exports a table definition, or a columns object, under the table's name. It is merged into every\nregistered table it has a matching export for, and only those. When the owner is not installed, nothing matches and the\ncall has no effect.",[593,916,917],{},"From a module:",[816,919,921],{"className":818,"code":920,"language":820,"meta":821,"style":821},"\u002F\u002F src\u002Fmodule.ts, in setup()\nextendDatabaseTable(nuxt, {\n  resolver,\n  file: dialect => `.\u002Fruntime\u002Fdb\u002Fusers.${dialect}`,\n})\n",[597,922,923,927,937,943,967],{"__ignoreMap":821},[825,924,925],{"class":827,"line":828},[825,926,832],{"class":831},[825,928,929,931,933,935],{"class":827,"line":835},[825,930,668],{"class":838},[825,932,842],{"class":841},[825,934,846],{"class":845},[825,936,849],{"class":845},[825,938,939,941],{"class":827,"line":852},[825,940,855],{"class":841},[825,942,858],{"class":845},[825,944,945,948,950,952,954,956,959,961,963,965],{"class":827,"line":861},[825,946,947],{"class":838},"  file",[825,949,867],{"class":845},[825,951,871],{"class":870},[825,953,875],{"class":874},[825,955,878],{"class":845},[825,957,958],{"class":881},".\u002Fruntime\u002Fdb\u002Fusers.",[825,960,885],{"class":845},[825,962,888],{"class":841},[825,964,891],{"class":845},[825,966,858],{"class":845},[825,968,969,971],{"class":827,"line":896},[825,970,899],{"class":845},[825,972,902],{"class":841},[816,974,976],{"className":818,"code":975,"language":820,"meta":821,"style":821},"\u002F\u002F src\u002Fruntime\u002Fdb\u002Fusers.sqlite.ts\nimport { sqliteTable, text } from 'drizzle-orm\u002Fsqlite-core'\n\nexport const users = sqliteTable('users', {\n  locale: text().default('da'),\n})\n",[597,977,978,983,1015,1021,1052,1084],{"__ignoreMap":821},[825,979,980],{"class":827,"line":828},[825,981,982],{"class":831},"\u002F\u002F src\u002Fruntime\u002Fdb\u002Fusers.sqlite.ts\n",[825,984,985,989,992,995,997,1000,1003,1006,1009,1012],{"class":827,"line":835},[825,986,988],{"class":987},"s7zQu","import",[825,990,991],{"class":845}," {",[825,993,994],{"class":841}," sqliteTable",[825,996,846],{"class":845},[825,998,999],{"class":841}," text",[825,1001,1002],{"class":845}," }",[825,1004,1005],{"class":987}," from",[825,1007,1008],{"class":845}," '",[825,1010,1011],{"class":881},"drizzle-orm\u002Fsqlite-core",[825,1013,1014],{"class":845},"'\n",[825,1016,1017],{"class":827,"line":852},[825,1018,1020],{"emptyLinePlaceholder":1019},true,"\n",[825,1022,1023,1026,1029,1032,1035,1037,1040,1043,1046,1048,1050],{"class":827,"line":861},[825,1024,1025],{"class":987},"export",[825,1027,1028],{"class":874}," const",[825,1030,1031],{"class":841}," users ",[825,1033,1034],{"class":845},"=",[825,1036,994],{"class":838},[825,1038,1039],{"class":841},"(",[825,1041,1042],{"class":845},"'",[825,1044,1045],{"class":881},"users",[825,1047,1042],{"class":845},[825,1049,846],{"class":845},[825,1051,849],{"class":845},[825,1053,1054,1058,1060,1062,1065,1067,1070,1072,1074,1077,1079,1082],{"class":827,"line":896},[825,1055,1057],{"class":1056},"swJcz","  locale",[825,1059,867],{"class":845},[825,1061,999],{"class":838},[825,1063,1064],{"class":841},"()",[825,1066,748],{"class":845},[825,1068,1069],{"class":838},"default",[825,1071,1039],{"class":841},[825,1073,1042],{"class":845},[825,1075,1076],{"class":881},"da",[825,1078,1042],{"class":845},[825,1080,1081],{"class":841},")",[825,1083,858],{"class":845},[825,1085,1087,1089],{"class":827,"line":1086},6,[825,1088,899],{"class":845},[825,1090,902],{"class":841},[593,1092,1093],{},"From the host app, one file can extend several tables. Only the export that matches the table being built is used:",[816,1095,1097],{"className":818,"code":1096,"language":820,"meta":821,"style":821},"\u002F\u002F server\u002Fdb\u002Fextend.sqlite.ts\nimport { text } from 'drizzle-orm\u002Fsqlite-core'\nimport { defineTableColumns } from '@frigear-nu\u002Fmodule-kit\u002Futils'\n\nexport const users = defineTableColumns({ locale: text().default('da') })\nexport const posts = defineTableColumns({ summary: text() })\n",[597,1098,1099,1104,1122,1142,1146,1191],{"__ignoreMap":821},[825,1100,1101],{"class":827,"line":828},[825,1102,1103],{"class":831},"\u002F\u002F server\u002Fdb\u002Fextend.sqlite.ts\n",[825,1105,1106,1108,1110,1112,1114,1116,1118,1120],{"class":827,"line":835},[825,1107,988],{"class":987},[825,1109,991],{"class":845},[825,1111,999],{"class":841},[825,1113,1002],{"class":845},[825,1115,1005],{"class":987},[825,1117,1008],{"class":845},[825,1119,1011],{"class":881},[825,1121,1014],{"class":845},[825,1123,1124,1126,1128,1131,1133,1135,1137,1140],{"class":827,"line":852},[825,1125,988],{"class":987},[825,1127,991],{"class":845},[825,1129,1130],{"class":841}," defineTableColumns",[825,1132,1002],{"class":845},[825,1134,1005],{"class":987},[825,1136,1008],{"class":845},[825,1138,1139],{"class":881},"@frigear-nu\u002Fmodule-kit\u002Futils",[825,1141,1014],{"class":845},[825,1143,1144],{"class":827,"line":861},[825,1145,1020],{"emptyLinePlaceholder":1019},[825,1147,1148,1150,1152,1154,1156,1158,1160,1163,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1187,1189],{"class":827,"line":896},[825,1149,1025],{"class":987},[825,1151,1028],{"class":874},[825,1153,1031],{"class":841},[825,1155,1034],{"class":845},[825,1157,1130],{"class":838},[825,1159,1039],{"class":841},[825,1161,1162],{"class":845},"{",[825,1164,1165],{"class":1056}," locale",[825,1167,867],{"class":845},[825,1169,999],{"class":838},[825,1171,1064],{"class":841},[825,1173,748],{"class":845},[825,1175,1069],{"class":838},[825,1177,1039],{"class":841},[825,1179,1042],{"class":845},[825,1181,1076],{"class":881},[825,1183,1042],{"class":845},[825,1185,1186],{"class":841},") ",[825,1188,899],{"class":845},[825,1190,902],{"class":841},[825,1192,1193,1195,1197,1200,1202,1204,1206,1208,1211,1213,1215,1218,1220],{"class":827,"line":1086},[825,1194,1025],{"class":987},[825,1196,1028],{"class":874},[825,1198,1199],{"class":841}," posts ",[825,1201,1034],{"class":845},[825,1203,1130],{"class":838},[825,1205,1039],{"class":841},[825,1207,1162],{"class":845},[825,1209,1210],{"class":1056}," summary",[825,1212,867],{"class":845},[825,1214,999],{"class":838},[825,1216,1217],{"class":841},"() ",[825,1219,899],{"class":845},[825,1221,902],{"class":841},[593,1223,1224],{},"Columns are merged in this order. A column that appears again replaces the earlier one.",[1226,1227,1228,1231,1237],"ol",{},[729,1229,1230],{},"The base schema file of the owning module.",[729,1232,1233,1234,1236],{},"Files registered with ",[597,1235,668],{},", in registration order.",[729,1238,1239,1240,748],{},"For each Nuxt layer, from the layers the app extends towards the host app itself: the shared\n",[597,1241,651],{},[593,1243,1244],{},"The host app generates and commits its own migration for what it adds.",[1246,1247,1248],"note",{},"Schema and extension files are parsed as text, not executed. Keep them to imports and table calls. Top-level helpers\nand constants are not carried over.",[801,1250,1251],{},"The third argument of an extension table (indexes) is dropped with a warning. Put indexes on extension columns in the\nbase file or in a migration.",[805,1253,706],{"id":1254},"one-schema-file-per-dialect",[593,1256,1257,1258,1261,1262,1265,1266,1269,1270,1269,1273,1276,1277,1279],{},"NuxtHub resolves schema files for the host app's dialect, ",[597,1259,1260],{},"sqlite"," or ",[597,1263,1264],{},"postgresql",". Every schema and extension file\n(",[597,1267,1268],{},"schema.sqlite.ts",", ",[597,1271,1272],{},"users.sqlite.ts",[597,1274,1275],{},"server\u002Fdb\u002Fextend.sqlite.ts",", ...) ships with a ",[597,1278,699],{}," twin next to\nit. The twin exports the same tables with the same table, column and index names.",[801,1281,1282],{},"Change both twins in the same commit. A twin that drifts breaks the app on the other dialect.",[593,1284,1285,1286,1269,1289,1269,1292,1269,1295,1298],{},"Runtime code must work on both dialects. Do not use ",[597,1287,1288],{},"db.run",[597,1290,1291],{},"db.batch",[597,1293,1294],{},"PRAGMA",[597,1296,1297],{},"rowid"," or SQLite-only SQL without\na PostgreSQL branch.",[620,1300,1301,1311],{},[623,1302,1303],{},[626,1304,1305,1308],{},[629,1306,1307],{},"SQLite",[629,1309,1310],{},"PostgreSQL",[639,1312,1313,1331,1343,1355,1367,1379,1392],{},[626,1314,1315,1323],{},[644,1316,1317,1320,1321,1081],{},[597,1318,1319],{},"sqliteTable"," (",[597,1322,1011],{},[644,1324,1325,1320,1328,1081],{},[597,1326,1327],{},"pgTable",[597,1329,1330],{},"drizzle-orm\u002Fpg-core",[626,1332,1333,1338],{},[644,1334,1335],{},[597,1336,1337],{},"integer().primaryKey({ autoIncrement: true })",[644,1339,1340],{},[597,1341,1342],{},"serial().primaryKey()",[626,1344,1345,1350],{},[644,1346,1347],{},[597,1348,1349],{},"integer({ mode: 'timestamp' })",[644,1351,1352],{},[597,1353,1354],{},"timestamp({ withTimezone: true })",[626,1356,1357,1362],{},[644,1358,1359],{},[597,1360,1361],{},"integer({ mode: 'boolean' })",[644,1363,1364],{},[597,1365,1366],{},"boolean()",[626,1368,1369,1374],{},[644,1370,1371],{},[597,1372,1373],{},"text({ mode: 'json' })",[644,1375,1376],{},[597,1377,1378],{},"jsonb()",[626,1380,1381,1387],{},[644,1382,1383,1386],{},[597,1384,1385],{},"integer()"," holding epoch milliseconds",[644,1388,1389],{},[597,1390,1391],{},"bigint({ mode: 'number' })",[626,1393,1394,1399],{},[644,1395,1396],{},[597,1397,1398],{},"customType({ dataType: () => 'text' })",[644,1400,1401,1402,1405,1406],{},"the same ",[597,1403,1404],{},"customType"," from ",[597,1407,1408],{},"pg-core",[593,1410,1411,1412,867],{},"A test keeps the twins in sync. Every package that owns a schema has ",[597,1413,1414],{},"test\u002Fschema-parity.test.ts",[816,1416,1418],{"className":818,"code":1417,"language":820,"meta":821,"style":821},"import { it } from 'vitest'\nimport { expectSchemaParity } from '@frigear-nu\u002Fmodule-kit\u002Ftesting'\nimport * as sqlite from '..\u002Fsrc\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.sqlite'\nimport * as postgresql from '..\u002Fsrc\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.postgresql'\n\nit('schema.postgresql matches schema.sqlite', () => {\n  expectSchemaParity(sqlite, postgresql)\n})\n",[597,1419,1420,1440,1460,1483,1503,1507,1530,1547],{"__ignoreMap":821},[825,1421,1422,1424,1426,1429,1431,1433,1435,1438],{"class":827,"line":828},[825,1423,988],{"class":987},[825,1425,991],{"class":845},[825,1427,1428],{"class":841}," it",[825,1430,1002],{"class":845},[825,1432,1005],{"class":987},[825,1434,1008],{"class":845},[825,1436,1437],{"class":881},"vitest",[825,1439,1014],{"class":845},[825,1441,1442,1444,1446,1449,1451,1453,1455,1458],{"class":827,"line":835},[825,1443,988],{"class":987},[825,1445,991],{"class":845},[825,1447,1448],{"class":841}," expectSchemaParity",[825,1450,1002],{"class":845},[825,1452,1005],{"class":987},[825,1454,1008],{"class":845},[825,1456,1457],{"class":881},"@frigear-nu\u002Fmodule-kit\u002Ftesting",[825,1459,1014],{"class":845},[825,1461,1462,1464,1467,1470,1473,1476,1478,1481],{"class":827,"line":852},[825,1463,988],{"class":987},[825,1465,1466],{"class":845}," *",[825,1468,1469],{"class":987}," as",[825,1471,1472],{"class":841}," sqlite ",[825,1474,1475],{"class":987},"from",[825,1477,1008],{"class":845},[825,1479,1480],{"class":881},"..\u002Fsrc\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.sqlite",[825,1482,1014],{"class":845},[825,1484,1485,1487,1489,1491,1494,1496,1498,1501],{"class":827,"line":861},[825,1486,988],{"class":987},[825,1488,1466],{"class":845},[825,1490,1469],{"class":987},[825,1492,1493],{"class":841}," postgresql ",[825,1495,1475],{"class":987},[825,1497,1008],{"class":845},[825,1499,1500],{"class":881},"..\u002Fsrc\u002Fruntime\u002Fserver\u002Fdb\u002Fschema.postgresql",[825,1502,1014],{"class":845},[825,1504,1505],{"class":827,"line":896},[825,1506,1020],{"emptyLinePlaceholder":1019},[825,1508,1509,1512,1514,1516,1519,1521,1523,1526,1528],{"class":827,"line":1086},[825,1510,1511],{"class":838},"it",[825,1513,1039],{"class":841},[825,1515,1042],{"class":845},[825,1517,1518],{"class":881},"schema.postgresql matches schema.sqlite",[825,1520,1042],{"class":845},[825,1522,846],{"class":845},[825,1524,1525],{"class":845}," ()",[825,1527,875],{"class":874},[825,1529,849],{"class":845},[825,1531,1533,1536,1538,1540,1542,1545],{"class":827,"line":1532},7,[825,1534,1535],{"class":838},"  expectSchemaParity",[825,1537,1039],{"class":1056},[825,1539,1260],{"class":841},[825,1541,846],{"class":845},[825,1543,1544],{"class":841}," postgresql",[825,1546,902],{"class":1056},[825,1548,1550,1552],{"class":827,"line":1549},8,[825,1551,899],{"class":845},[825,1553,902],{"class":841},[593,1555,1556,1559,1560,1563,1564,748],{},[597,1557,1558],{},"expectSchemaParity"," compares tables, columns, indexes and foreign keys. For columns it checks the Drizzle data type,\nnot null, primary key, unique, defaults, ",[597,1561,1562],{},"$defaultFn"," and ",[597,1565,1566],{},"$onUpdateFn",[726,1568,1569,1572],{},[729,1570,1571],{},"Add a case when you add a schema file.",[729,1573,1574,1575,1578,1579,1581],{},"A Module Kit test fails when a ",[597,1576,1577],{},".sqlite.ts"," schema has no ",[597,1580,699],{}," twin.",[805,1583,720],{"id":1584},"migrations-are-committed-history",[593,1586,1587,1588,748],{},"Extensions only change the generated schema. So every app with a database needs a migration for them, including every\n",[655,1589,1590],{"href":87},"test fixture",[726,1592,1593,1600,1603],{},[729,1594,1595,1596,1599],{},"Generate migrations from the app's ",[610,1597,1598],{},"complete"," module configuration, including every extending module.",[729,1601,1602],{},"Commit the SQL, the Drizzle snapshot and the migration journal together. They are versioned schema history, not\nbuild artifacts.",[729,1604,1605],{},"Keep existing history intact.",[593,1607,1608,1609,867],{},"After changing a schema or an extension, check each test fixture that has committed migrations. Run this from the\nrepository root, with an absolute ",[597,1610,1611],{},"--cwd",[816,1613,1617],{"className":1614,"code":1615,"language":1616,"meta":821,"style":821},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm --filter @frigear-nu\u002F\u003Cpkg> exec nuxt db generate --cwd \"$PWD\u002Fpackages\u002F\u003Cpkg>\u002Ftest\u002Ffixtures\u002F\u003Cfixture>\"\n","bash",[597,1618,1619],{"__ignoreMap":821},[825,1620,1621,1625,1628,1631,1634,1637,1640,1643,1646,1649,1652,1655,1658,1661,1664,1667],{"class":827,"line":828},[825,1622,1624],{"class":1623},"sBMFI","pnpm",[825,1626,1627],{"class":881}," --filter",[825,1629,1630],{"class":881}," @frigear-nu\u002F",[825,1632,1633],{"class":845},"\u003C",[825,1635,1636],{"class":881},"pk",[825,1638,1639],{"class":841},"g",[825,1641,1642],{"class":845},">",[825,1644,1645],{"class":881}," exec",[825,1647,1648],{"class":881}," nuxt",[825,1650,1651],{"class":881}," db",[825,1653,1654],{"class":881}," generate",[825,1656,1657],{"class":881}," --cwd",[825,1659,1660],{"class":845}," \"",[825,1662,1663],{"class":841},"$PWD",[825,1665,1666],{"class":881},"\u002Fpackages\u002F\u003Cpkg>\u002Ftest\u002Ffixtures\u002F\u003Cfixture>",[825,1668,1669],{"class":845},"\"\n",[606,1671,1674,1677,1678,1681,1682,748],{"icon":1672,"color":1673},"i-lucide-check","success",[610,1675,1676],{},"What you should see:"," ",[597,1679,1680],{},"No schema changes, nothing to migrate",", and a clean ",[597,1683,1684],{},"git status",[593,1686,1687],{},"If it creates a migration, the test fixture needs one. Delete any stray migration you do not intend to commit.",[593,1689,1690,1691,1261,1694,1697,1698,1701],{},"A module that adds routes using ",[597,1692,1693],{},"requireAuth",[597,1695,1696],{},"requireUser"," needs the ",[597,1699,1700],{},"auth_user_tokens"," migration in every test\nfixture that includes it.",[615,1703,1705],{"id":1704},"common-mistakes","Common mistakes",[801,1707,1708,1711],{},[610,1709,1710],{},"Generating migrations from the owning module alone."," A migration generated from only the base schema misses every\nextension column. Generate it from the complete configuration.",[801,1713,1714,1717],{},[610,1715,1716],{},"Reading a missing session cookie as disabled sign-in."," It can be a database error. A column added by an extending\nmodule, without a matching test fixture migration, makes every write to that table fail.",[615,1719,1721],{"id":1720},"next-steps","Next steps",[726,1723,1724,1729,1734],{},[729,1725,1726,1728],{},[655,1727,41],{"href":42}," – set up the database and generate your first migration in an app.",[729,1730,1731,1733],{},[655,1732,533],{"href":534}," – add a table to a module step by step.",[729,1735,1736,1738],{},[655,1737,537],{"href":538}," – boot database-backed test fixtures.",[1740,1741,1742],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":821,"searchDepth":835,"depth":835,"links":1744},[1745,1746,1747,1753,1754],{"id":617,"depth":835,"text":618},{"id":723,"depth":835,"text":724},{"id":768,"depth":835,"text":769,"children":1748},[1749,1750,1751,1752],{"id":807,"depth":852,"text":688},{"id":911,"depth":852,"text":658},{"id":1254,"depth":852,"text":706},{"id":1584,"depth":852,"text":720},{"id":1704,"depth":835,"text":1705},{"id":1720,"depth":835,"text":1721},"How your app or a module adds columns to a table another module owns, why every schema ships once per dialect, and which migrations to commit.","md",null,{},{"icon":44},{"title":77,"description":1755},"_VZviAfwWz8hgTDDFlzphtIkAzPtdYJKQBo73IsWyjw",[1763,1765],{"title":72,"path":73,"stem":74,"description":1764,"icon":75,"children":-1},"How a module adds menu items, dashboard widgets, tabs, admin pages, permissions, roles, scopes and MCP tools to other modules without importing them.",{"title":81,"path":82,"stem":83,"description":1766,"icon":84,"children":-1},"Which module each of your apps needs to sign people in across a web app, a mobile app and the services they call.",1794503150867]