[{"data":1,"prerenderedAt":4264},["ShallowReactive",2],{"navigation_docs":3,"-guide-best-practices-http-routes":587,"-guide-best-practices-http-routes-surround":4259},[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":121,"body":589,"description":4252,"extension":4253,"links":4254,"meta":4255,"navigation":4256,"path":122,"seo":4257,"stem":123,"__hash__":4258},"docs\u002F2.guide\u002F3.best-practices\u002F1.http-routes.md",{"type":590,"value":591,"toc":4233},"minimark",[592,596,615,626,630,635,750,753,767,850,1199,1203,1206,1240,1248,1262,1267,1273,1334,1389,1419,1423,1429,1448,1527,1534,1538,1551,1673,1695,1698,2177,2194,2197,2202,2236,2305,2309,2316,2331,2453,2472,2583,2587,2590,2615,2685,2689,2741,2744,2769,2829,2833,2846,2893,3621,3648,3652,3679,3742,3757,3760,3783,3786,3809,3827,3852,3856,3873,3939,4203,4207,4229],[593,594,595],"p",{},"A client that has called one module's API should be able to guess the next one: its URLs, its errors and the shape of\nits responses. That client may be your app, an AI agent over the Model Context Protocol (MCP) or a test. This page\nlists the conventions every module API follows, and the helpers that implement them.",[597,598,600,604,605,609,610,614],"callout",{"icon":599},"i-lucide-users",[601,602,603],"strong",{},"For:"," module authors. App developers who add API routes to a ",[606,607,608],"a",{"href":87},"host app"," can follow the\nsame conventions; only ",[606,611,613],{"href":612},"#registering-routes","Registering routes"," is module-specific.",[593,616,617,618,620,621,625],{},"The helpers are the server toolkit of ",[606,619,381],{"href":382},", ",[622,623,624],"code",{},"@frigear-nu\u002Fmodule-kit\u002Fserver",".",[627,628,629],"note",{},"Import the toolkit explicitly. It is not auto-imported, so nothing clashes with h3's own helpers.",[631,632,634],"h2",{"id":633},"urls","URLs",[636,637,638,648,668,682,691,697,706,723,736],"ul",{},[639,640,641,644,645,625],"li",{},[601,642,643],{},"Module prefix."," Everything lives under ",[622,646,647],{},"\u002Fapi\u002F\u003Cmodule>",[639,649,650,656,657,660,661,664,665,625],{},[601,651,652,655],{},[606,653,654],{"href":87},"Staff"," routes."," They live under ",[622,658,659],{},"\u002Fapi\u002Fadmin\u002F\u003Cmodule>\u002F...",", with admin first, like\nthe ",[622,662,663],{},"\u002Fadmin\u002F\u003Cmodule>"," pages. So a host app can guard all of them with one ",[622,666,667],{},"routeRules['\u002Fapi\u002Fadmin\u002F**']",[639,669,670,673,674,677,678,681],{},[601,671,672],{},"The main resource."," It is the module path itself: ",[622,675,676],{},"GET \u002Fapi\u002Fadmin\u002Fevents"," lists events, and\n",[622,679,680],{},"GET \u002Fapi\u002Fadmin\u002Fevents\u002F:id"," reads one.",[639,683,684,687,688,625],{},[601,685,686],{},"The caller's own things."," Anything that is implicitly the caller's lives under ",[622,689,690],{},"\u002Fapi\u002F\u003Cmodule>\u002Fme\u002F...",[639,692,693,696],{},[601,694,695],{},"Ids and collections."," Ids are path params, and collections are plural.",[639,698,699,702,703,625],{},[601,700,701],{},"Actions."," An action that is not create, read, update or delete (CRUD) is ",[622,704,705],{},"POST \u002F\u003Cresource>\u002F:id\u002F\u003Cverb>",[639,707,708,711,712,715,716,719,720,625],{},[601,709,710],{},"Methods."," A partial update is ",[622,713,714],{},"PATCH",", and replacing a value is ",[622,717,718],{},"PUT",". A state change is never ",[622,721,722],{},"GET",[639,724,725,728,729,620,732,735],{},[601,726,727],{},"Exceptions."," Spec endpoints (",[622,730,731],{},"\u002F.well-known\u002F*",[622,733,734],{},"\u002Foauth\u002F*","), webhooks and OAuth callbacks registered at a provider\nkeep the paths they need.",[639,737,738,741,742,745,746,625],{},[601,739,740],{},"One place for paths."," Keep the paths as typed builders in ",[622,743,744],{},"src\u002Fruntime\u002Futils\u002Fapi.ts",". The app, tests and docs\nuse them. See ",[606,747,749],{"href":748},"#naming","Naming",[631,751,613],{"id":752},"registering-routes",[593,754,755,756,759,760,763,764,625],{},"A module registers every route from one table in ",[622,757,758],{},"module.ts",", with ",[622,761,762],{},"addModuleRoutes"," from\n",[622,765,766],{},"@frigear-nu\u002Fmodule-kit\u002Futils",[768,769,770,783],"table",{},[771,772,773],"thead",{},[774,775,776,780],"tr",{},[777,778,779],"th",{},"Field",[777,781,782],{},"Meaning",[784,785,786,797,824,837],"tbody",{},[774,787,788,794],{},[789,790,791],"td",{},[622,792,793],{},"method",[789,795,796],{},"The HTTP method, in lower case.",[774,798,799,804],{},[789,800,801],{},[622,802,803],{},"path",[789,805,806,807,810,811,814,815,817,818,821,822,625],{},"Joined onto ",[622,808,809],{},"base",". ",[622,812,813],{},"''"," is ",[622,816,809],{}," itself, and a path that starts with ",[622,819,820],{},"\u002F"," ignores ",[622,823,809],{},[774,825,826,831],{},[789,827,828],{},[622,829,830],{},"file",[789,832,833,834,625],{},"The handler, relative to ",[622,835,836],{},".\u002Fruntime\u002Fserver\u002Froutes\u002F",[774,838,839,844],{},[789,840,841,843],{},[622,842,809],{}," (in the third argument)",[789,845,846,847,849],{},"Required. The prefix for every ",[622,848,803],{}," in the table.",[851,852,857],"pre",{"className":853,"code":854,"language":855,"meta":856,"style":856},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { addModuleRoutes } from '@frigear-nu\u002Fmodule-kit\u002Futils'\n\naddModuleRoutes(resolver, [\n  { method: 'get', path: '', file: 'admin\u002Fevents.get' },\n  { method: 'post', path: '', file: 'admin\u002Fevents.post' },\n  { method: 'get', path: ':id', file: 'admin\u002Fevent.get' },\n  { method: 'patch', path: ':id', file: 'admin\u002Fevent.patch' },\n  { method: 'post', path: ':id\u002Fclone', file: 'admin\u002Fevent-clone.post' },\n  { method: 'get', path: '\u002Fapi\u002Fevents\u002Fme\u002Ftickets', file: 'me\u002Ftickets.get' },\n], { base: '\u002Fapi\u002Fadmin\u002Fevents' })\n","ts","",[622,858,859,891,898,913,961,1000,1043,1086,1129,1172],{"__ignoreMap":856},[860,861,864,868,872,876,879,882,885,888],"span",{"class":862,"line":863},"line",1,[860,865,867],{"class":866},"s7zQu","import",[860,869,871],{"class":870},"sMK4o"," {",[860,873,875],{"class":874},"sTEyZ"," addModuleRoutes",[860,877,878],{"class":870}," }",[860,880,881],{"class":866}," from",[860,883,884],{"class":870}," '",[860,886,766],{"class":887},"sfazB",[860,889,890],{"class":870},"'\n",[860,892,894],{"class":862,"line":893},2,[860,895,897],{"emptyLinePlaceholder":896},true,"\n",[860,899,901,904,907,910],{"class":862,"line":900},3,[860,902,762],{"class":903},"s2Zo4",[860,905,906],{"class":874},"(resolver",[860,908,909],{"class":870},",",[860,911,912],{"class":874}," [\n",[860,914,916,919,923,926,928,931,934,936,939,941,944,946,949,951,953,956,958],{"class":862,"line":915},4,[860,917,918],{"class":870},"  {",[860,920,922],{"class":921},"swJcz"," method",[860,924,925],{"class":870},":",[860,927,884],{"class":870},[860,929,930],{"class":887},"get",[860,932,933],{"class":870},"'",[860,935,909],{"class":870},[860,937,938],{"class":921}," path",[860,940,925],{"class":870},[860,942,943],{"class":870}," ''",[860,945,909],{"class":870},[860,947,948],{"class":921}," file",[860,950,925],{"class":870},[860,952,884],{"class":870},[860,954,955],{"class":887},"admin\u002Fevents.get",[860,957,933],{"class":870},[860,959,960],{"class":870}," },\n",[860,962,964,966,968,970,972,975,977,979,981,983,985,987,989,991,993,996,998],{"class":862,"line":963},5,[860,965,918],{"class":870},[860,967,922],{"class":921},[860,969,925],{"class":870},[860,971,884],{"class":870},[860,973,974],{"class":887},"post",[860,976,933],{"class":870},[860,978,909],{"class":870},[860,980,938],{"class":921},[860,982,925],{"class":870},[860,984,943],{"class":870},[860,986,909],{"class":870},[860,988,948],{"class":921},[860,990,925],{"class":870},[860,992,884],{"class":870},[860,994,995],{"class":887},"admin\u002Fevents.post",[860,997,933],{"class":870},[860,999,960],{"class":870},[860,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023,1026,1028,1030,1032,1034,1036,1039,1041],{"class":862,"line":1002},6,[860,1004,918],{"class":870},[860,1006,922],{"class":921},[860,1008,925],{"class":870},[860,1010,884],{"class":870},[860,1012,930],{"class":887},[860,1014,933],{"class":870},[860,1016,909],{"class":870},[860,1018,938],{"class":921},[860,1020,925],{"class":870},[860,1022,884],{"class":870},[860,1024,1025],{"class":887},":id",[860,1027,933],{"class":870},[860,1029,909],{"class":870},[860,1031,948],{"class":921},[860,1033,925],{"class":870},[860,1035,884],{"class":870},[860,1037,1038],{"class":887},"admin\u002Fevent.get",[860,1040,933],{"class":870},[860,1042,960],{"class":870},[860,1044,1046,1048,1050,1052,1054,1057,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1082,1084],{"class":862,"line":1045},7,[860,1047,918],{"class":870},[860,1049,922],{"class":921},[860,1051,925],{"class":870},[860,1053,884],{"class":870},[860,1055,1056],{"class":887},"patch",[860,1058,933],{"class":870},[860,1060,909],{"class":870},[860,1062,938],{"class":921},[860,1064,925],{"class":870},[860,1066,884],{"class":870},[860,1068,1025],{"class":887},[860,1070,933],{"class":870},[860,1072,909],{"class":870},[860,1074,948],{"class":921},[860,1076,925],{"class":870},[860,1078,884],{"class":870},[860,1080,1081],{"class":887},"admin\u002Fevent.patch",[860,1083,933],{"class":870},[860,1085,960],{"class":870},[860,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105,1107,1109,1112,1114,1116,1118,1120,1122,1125,1127],{"class":862,"line":1088},8,[860,1090,918],{"class":870},[860,1092,922],{"class":921},[860,1094,925],{"class":870},[860,1096,884],{"class":870},[860,1098,974],{"class":887},[860,1100,933],{"class":870},[860,1102,909],{"class":870},[860,1104,938],{"class":921},[860,1106,925],{"class":870},[860,1108,884],{"class":870},[860,1110,1111],{"class":887},":id\u002Fclone",[860,1113,933],{"class":870},[860,1115,909],{"class":870},[860,1117,948],{"class":921},[860,1119,925],{"class":870},[860,1121,884],{"class":870},[860,1123,1124],{"class":887},"admin\u002Fevent-clone.post",[860,1126,933],{"class":870},[860,1128,960],{"class":870},[860,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1155,1157,1159,1161,1163,1165,1168,1170],{"class":862,"line":1131},9,[860,1133,918],{"class":870},[860,1135,922],{"class":921},[860,1137,925],{"class":870},[860,1139,884],{"class":870},[860,1141,930],{"class":887},[860,1143,933],{"class":870},[860,1145,909],{"class":870},[860,1147,938],{"class":921},[860,1149,925],{"class":870},[860,1151,884],{"class":870},[860,1153,1154],{"class":887},"\u002Fapi\u002Fevents\u002Fme\u002Ftickets",[860,1156,933],{"class":870},[860,1158,909],{"class":870},[860,1160,948],{"class":921},[860,1162,925],{"class":870},[860,1164,884],{"class":870},[860,1166,1167],{"class":887},"me\u002Ftickets.get",[860,1169,933],{"class":870},[860,1171,960],{"class":870},[860,1173,1175,1178,1180,1182,1185,1187,1189,1192,1194,1196],{"class":862,"line":1174},10,[860,1176,1177],{"class":874},"]",[860,1179,909],{"class":870},[860,1181,871],{"class":870},[860,1183,1184],{"class":921}," base",[860,1186,925],{"class":870},[860,1188,884],{"class":870},[860,1190,1191],{"class":887},"\u002Fapi\u002Fadmin\u002Fevents",[860,1193,933],{"class":870},[860,1195,878],{"class":870},[860,1197,1198],{"class":874},")\n",[631,1200,1202],{"id":1201},"handler-order","Handler order",[593,1204,1205],{},"Every handler does its work in the same order:",[1207,1208,1209,1218,1234],"ol",{},[639,1210,1211,759,1214,1217],{},[601,1212,1213],{},"Authorize",[622,1215,1216],{},"authorize(event, '\u003Cpermission>')",". It authenticates and checks the permission.",[639,1219,1220,1223,1224,620,1227,1230,1231,625],{},[601,1221,1222],{},"Parse the path params",", then the body or query. Use h3's validated helpers with zod:\n",[622,1225,1226],{},"getValidatedRouterParams(event, idParams().parse)",[622,1228,1229],{},"readValidatedBody(event, schema.parse)"," and\n",[622,1232,1233],{},"getValidatedQuery(event, schema.parse)",[639,1235,1236,1239],{},[601,1237,1238],{},"Load the target",", then authorize again with the record when a policy may refine the permission, then act.",[1241,1242,1243,1244,625],"warning",{},"Never answer 404 before 401 or 403. A route that loads the record first tells a visitor which ids exist. See\n",[606,1245,1247],{"href":1246},"\u002Fguide\u002Fconcepts\u002Fauth-and-scopes#common-mistakes","Common mistakes",[593,1249,1250,1251,620,1254,1257,1258,1261],{},"On JSON routes, use no raw ",[622,1252,1253],{},"readBody",[622,1255,1256],{},"getQuery"," or ",[622,1259,1260],{},"getRouterParam(...)!",", and no wrappers around the validated\nhelpers.",[1263,1264,1266],"h3",{"id":1265},"step-1-who-may-call-the-route","Step 1: who may call the route",[593,1268,1269,1270,925],{},"The first line of a handler decides who may call it, and it is almost always ",[622,1271,1272],{},"authorize",[768,1274,1275,1288],{},[771,1276,1277],{},[774,1278,1279,1282,1285],{},[777,1280,1281],{},"Route",[777,1283,1284],{},"First line",[777,1286,1287],{},"Comes from",[784,1289,1290,1305,1319],{},[774,1291,1292,1295,1300],{},[789,1293,1294],{},"Needs a permission (every route that reads or changes data)",[789,1296,1297],{},[622,1298,1299],{},"await authorize(event, 'events:manage')",[789,1301,1302],{},[622,1303,1304],{},"@frigear-nu\u002Fauth",[774,1306,1307,1310,1315],{},[789,1308,1309],{},"Any signed-in caller, no permission",[789,1311,1312],{},[622,1313,1314],{},"const { user } = await requireAuth(event)",[789,1316,1317],{},[622,1318,1304],{},[774,1320,1321,1324,1329],{},[789,1322,1323],{},"The caller's own user record",[789,1325,1326],{},[622,1327,1328],{},"const user = await requireUser(event)",[789,1330,1331],{},[622,1332,1333],{},"@frigear-nu\u002Fusers",[593,1335,1336,1338,1339,620,1342,620,1345,1348,1349,1352,1353,1356,1357,620,1360,1257,1363,1366,1367,1370,1371,1374,1375,1378,1379,1382,1383,1386,1387,625],{},[622,1337,1272],{}," returns the principal (",[622,1340,1341],{},"user",[622,1343,1344],{},"kind",[622,1346,1347],{},"scopes",") and throws ",[622,1350,1351],{},"401 unauthorized",", or ",[622,1354,1355],{},"403"," with\n",[622,1358,1359],{},"session_required",[622,1361,1362],{},"insufficient_scope",[622,1364,1365],{},"forbidden",". It is auto-imported into server code. The permission is\nregistered once with ",[622,1368,1369],{},"addPermission","; staff routes use the module's own permissions (",[622,1372,1373],{},"events:manage",",\n",[622,1376,1377],{},"forms:manage","), and the caller's own things use ",[622,1380,1381],{},"implicit"," permissions such as ",[622,1384,1385],{},"tickets:read",". Who holds a permission is\ndecided by roles, policies and the host's before hook, never by the route. The whole model is on\n",[606,1388,67],{"href":68},[1390,1391,1392,1395,1396,1399,1400,620,1403,1406,1407,1409,1410,1257,1413,1416,1417,625],"tip",{},[601,1393,1394],{},"sessionOnly is a property of the permission."," Do not pass options to make a route stricter. Money and credential\nactions get their own ",[622,1397,1398],{},"sessionOnly"," permission (",[622,1401,1402],{},"events:refund",[622,1404,1405],{},"users:manage","), so every route that checks it\nrefuses tokens, and a token can never hold it. There is no ",[622,1408,1347],{}," option on ",[622,1411,1412],{},"requireAuth",[622,1414,1415],{},"requireUser",": name the\npermission and call ",[622,1418,1272],{},[1263,1420,1422],{"id":1421},"the-two-step-route","The two-step route",[593,1424,1425,1426,1428],{},"A route that acts on one record calls ",[622,1427,1272],{}," twice:",[1207,1430,1431,1437],{},[639,1432,1433,1436],{},[601,1434,1435],{},"Without the record",", first. It answers 401 or 403 from the permission alone, so a visitor cannot tell which ids\nexist.",[639,1438,1439,1442,1443,1447],{},[601,1440,1441],{},"With the record",", after loading it. It runs the ",[606,1444,1446],{"href":1445},"\u002Fguide\u002Fconcepts\u002Fauth-and-scopes#policies","policies",", such as\n\"only the owner\" or \"not once archived\".",[851,1449,1451],{"className":853,"code":1450,"language":855,"meta":856,"style":856},"await authorize(event, 'events:manage') \u002F\u002F 401\u002F403 before any 404\nconst ev = await loadEvent(id) \u002F\u002F then 404\nawait authorize(event, 'events:manage', ev) \u002F\u002F record-level policies\n",[622,1452,1453,1479,1503],{"__ignoreMap":856},[860,1454,1455,1458,1461,1464,1466,1468,1470,1472,1475],{"class":862,"line":863},[860,1456,1457],{"class":866},"await",[860,1459,1460],{"class":903}," authorize",[860,1462,1463],{"class":874},"(event",[860,1465,909],{"class":870},[860,1467,884],{"class":870},[860,1469,1373],{"class":887},[860,1471,933],{"class":870},[860,1473,1474],{"class":874},") ",[860,1476,1478],{"class":1477},"sHwdD","\u002F\u002F 401\u002F403 before any 404\n",[860,1480,1481,1485,1488,1491,1494,1497,1500],{"class":862,"line":893},[860,1482,1484],{"class":1483},"spNyl","const",[860,1486,1487],{"class":874}," ev ",[860,1489,1490],{"class":870},"=",[860,1492,1493],{"class":866}," await",[860,1495,1496],{"class":903}," loadEvent",[860,1498,1499],{"class":874},"(id) ",[860,1501,1502],{"class":1477},"\u002F\u002F then 404\n",[860,1504,1505,1507,1509,1511,1513,1515,1517,1519,1521,1524],{"class":862,"line":900},[860,1506,1457],{"class":866},[860,1508,1460],{"class":903},[860,1510,1463],{"class":874},[860,1512,909],{"class":870},[860,1514,884],{"class":870},[860,1516,1373],{"class":887},[860,1518,933],{"class":870},[860,1520,909],{"class":870},[860,1522,1523],{"class":874}," ev) ",[860,1525,1526],{"class":1477},"\u002F\u002F record-level policies\n",[593,1528,1529,1530,1533],{},"A route that has no policy on its permission may make the first call only. Use ",[622,1531,1532],{},"allows(event, permission, record?)","\ninstead when you need a yes or no without throwing, for instance to choose which fields to return.",[1263,1535,1537],{"id":1536},"example","Example",[593,1539,1540,1541,1544,1545,1547,1548,1550],{},"A staff route that renames a thing. First, the permission, declared once in the module's ",[622,1542,1543],{},"src\u002Fruntime\u002Faccess.ts"," and\nregistered from ",[622,1546,758],{}," (",[606,1549,67],{"href":542},"):",[851,1552,1554],{"className":853,"code":1553,"filename":1543,"language":855,"meta":856,"style":856},"import { defineAccess } from '@frigear-nu\u002Fauth\u002Faccess'\n\nexport const thingsAccess = defineAccess('things', {\n  permissions: { update: { label: 'Rename things', grantTo: ['admin'] } }, \u002F\u002F things:update\n})\n",[622,1555,1556,1576,1580,1610,1667],{"__ignoreMap":856},[860,1557,1558,1560,1562,1565,1567,1569,1571,1574],{"class":862,"line":863},[860,1559,867],{"class":866},[860,1561,871],{"class":870},[860,1563,1564],{"class":874}," defineAccess",[860,1566,878],{"class":870},[860,1568,881],{"class":866},[860,1570,884],{"class":870},[860,1572,1573],{"class":887},"@frigear-nu\u002Fauth\u002Faccess",[860,1575,890],{"class":870},[860,1577,1578],{"class":862,"line":893},[860,1579,897],{"emptyLinePlaceholder":896},[860,1581,1582,1585,1588,1591,1593,1595,1598,1600,1603,1605,1607],{"class":862,"line":900},[860,1583,1584],{"class":866},"export",[860,1586,1587],{"class":1483}," const",[860,1589,1590],{"class":874}," thingsAccess ",[860,1592,1490],{"class":870},[860,1594,1564],{"class":903},[860,1596,1597],{"class":874},"(",[860,1599,933],{"class":870},[860,1601,1602],{"class":887},"things",[860,1604,933],{"class":870},[860,1606,909],{"class":870},[860,1608,1609],{"class":870}," {\n",[860,1611,1612,1615,1617,1619,1622,1624,1626,1629,1631,1633,1636,1638,1640,1643,1645,1648,1650,1653,1655,1658,1661,1664],{"class":862,"line":915},[860,1613,1614],{"class":921},"  permissions",[860,1616,925],{"class":870},[860,1618,871],{"class":870},[860,1620,1621],{"class":921}," update",[860,1623,925],{"class":870},[860,1625,871],{"class":870},[860,1627,1628],{"class":921}," label",[860,1630,925],{"class":870},[860,1632,884],{"class":870},[860,1634,1635],{"class":887},"Rename things",[860,1637,933],{"class":870},[860,1639,909],{"class":870},[860,1641,1642],{"class":921}," grantTo",[860,1644,925],{"class":870},[860,1646,1647],{"class":874}," [",[860,1649,933],{"class":870},[860,1651,1652],{"class":887},"admin",[860,1654,933],{"class":870},[860,1656,1657],{"class":874},"] ",[860,1659,1660],{"class":870},"}",[860,1662,1663],{"class":870}," },",[860,1665,1666],{"class":1477}," \u002F\u002F things:update\n",[860,1668,1669,1671],{"class":862,"line":963},[860,1670,1660],{"class":870},[860,1672,1198],{"class":874},[851,1674,1677],{"className":853,"code":1675,"filename":1676,"language":855,"meta":856,"style":856},"registerAccess(nuxt, thingsAccess) \u002F\u002F from @frigear-nu\u002Fauth\u002Futils\n","src\u002Fmodule.ts",[622,1678,1679],{"__ignoreMap":856},[860,1680,1681,1684,1687,1689,1692],{"class":862,"line":863},[860,1682,1683],{"class":903},"registerAccess",[860,1685,1686],{"class":874},"(nuxt",[860,1688,909],{"class":870},[860,1690,1691],{"class":874}," thingsAccess) ",[860,1693,1694],{"class":1477},"\u002F\u002F from @frigear-nu\u002Fauth\u002Futils\n",[593,1696,1697],{},"Then the route, which does the steps in order:",[851,1699,1702],{"className":853,"code":1700,"filename":1701,"language":855,"meta":856,"style":856},"import { db, schema } from '@nuxthub\u002Fdb'\nimport { eq } from 'drizzle-orm'\nimport { idParams, notFound } from '@frigear-nu\u002Fmodule-kit\u002Fserver'\nimport { z } from 'zod'\nimport { thingsAccess } from '..\u002F..\u002F..\u002Faccess'\n\nconst body = z.object({ name: z.string().trim().min(1).max(120) })\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F 1. Who may call: the permission, answered by roles, policies and the host's before hook.\n  await authorize(event, thingsAccess.permissions.update)\n  \u002F\u002F 2. Parse the path params, then the body.\n  const { id } = await getValidatedRouterParams(event, idParams().parse)\n  const input = await readValidatedBody(event, body.parse)\n  \u002F\u002F 3. Load the target and act. A missing record is a 404, only after the caller passed step 1.\n  const [row] = await db.update(schema.things).set(input).where(eq(schema.things.id, id)).returning()\n  if (!row) throw notFound('Thing not found.')\n  \u002F\u002F toThingDto lives in src\u002Fruntime\u002Fserver\u002Futils too. See Responses.\n  return toThingDto(row)\n})\n","src\u002Fruntime\u002Fserver\u002Froutes\u002Fadmin\u002Fthing.patch.ts",[622,1703,1704,1729,1749,1773,1793,1813,1817,1890,1894,1922,1927,1955,1961,1999,2029,2035,2118,2149,2155,2170],{"__ignoreMap":856},[860,1705,1706,1708,1710,1713,1715,1718,1720,1722,1724,1727],{"class":862,"line":863},[860,1707,867],{"class":866},[860,1709,871],{"class":870},[860,1711,1712],{"class":874}," db",[860,1714,909],{"class":870},[860,1716,1717],{"class":874}," schema",[860,1719,878],{"class":870},[860,1721,881],{"class":866},[860,1723,884],{"class":870},[860,1725,1726],{"class":887},"@nuxthub\u002Fdb",[860,1728,890],{"class":870},[860,1730,1731,1733,1735,1738,1740,1742,1744,1747],{"class":862,"line":893},[860,1732,867],{"class":866},[860,1734,871],{"class":870},[860,1736,1737],{"class":874}," eq",[860,1739,878],{"class":870},[860,1741,881],{"class":866},[860,1743,884],{"class":870},[860,1745,1746],{"class":887},"drizzle-orm",[860,1748,890],{"class":870},[860,1750,1751,1753,1755,1758,1760,1763,1765,1767,1769,1771],{"class":862,"line":900},[860,1752,867],{"class":866},[860,1754,871],{"class":870},[860,1756,1757],{"class":874}," idParams",[860,1759,909],{"class":870},[860,1761,1762],{"class":874}," notFound",[860,1764,878],{"class":870},[860,1766,881],{"class":866},[860,1768,884],{"class":870},[860,1770,624],{"class":887},[860,1772,890],{"class":870},[860,1774,1775,1777,1779,1782,1784,1786,1788,1791],{"class":862,"line":915},[860,1776,867],{"class":866},[860,1778,871],{"class":870},[860,1780,1781],{"class":874}," z",[860,1783,878],{"class":870},[860,1785,881],{"class":866},[860,1787,884],{"class":870},[860,1789,1790],{"class":887},"zod",[860,1792,890],{"class":870},[860,1794,1795,1797,1799,1802,1804,1806,1808,1811],{"class":862,"line":963},[860,1796,867],{"class":866},[860,1798,871],{"class":870},[860,1800,1801],{"class":874}," thingsAccess",[860,1803,878],{"class":870},[860,1805,881],{"class":866},[860,1807,884],{"class":870},[860,1809,1810],{"class":887},"..\u002F..\u002F..\u002Faccess",[860,1812,890],{"class":870},[860,1814,1815],{"class":862,"line":1002},[860,1816,897],{"emptyLinePlaceholder":896},[860,1818,1819,1821,1824,1826,1828,1830,1833,1835,1838,1841,1843,1845,1847,1850,1853,1855,1858,1860,1862,1865,1867,1871,1874,1876,1879,1881,1884,1886,1888],{"class":862,"line":1045},[860,1820,1484],{"class":1483},[860,1822,1823],{"class":874}," body ",[860,1825,1490],{"class":870},[860,1827,1781],{"class":874},[860,1829,625],{"class":870},[860,1831,1832],{"class":903},"object",[860,1834,1597],{"class":874},[860,1836,1837],{"class":870},"{",[860,1839,1840],{"class":921}," name",[860,1842,925],{"class":870},[860,1844,1781],{"class":874},[860,1846,625],{"class":870},[860,1848,1849],{"class":903},"string",[860,1851,1852],{"class":874},"()",[860,1854,625],{"class":870},[860,1856,1857],{"class":903},"trim",[860,1859,1852],{"class":874},[860,1861,625],{"class":870},[860,1863,1864],{"class":903},"min",[860,1866,1597],{"class":874},[860,1868,1870],{"class":1869},"sbssI","1",[860,1872,1873],{"class":874},")",[860,1875,625],{"class":870},[860,1877,1878],{"class":903},"max",[860,1880,1597],{"class":874},[860,1882,1883],{"class":1869},"120",[860,1885,1474],{"class":874},[860,1887,1660],{"class":870},[860,1889,1198],{"class":874},[860,1891,1892],{"class":862,"line":1088},[860,1893,897],{"emptyLinePlaceholder":896},[860,1895,1896,1898,1901,1904,1906,1909,1911,1915,1917,1920],{"class":862,"line":1131},[860,1897,1584],{"class":866},[860,1899,1900],{"class":866}," default",[860,1902,1903],{"class":903}," defineEventHandler",[860,1905,1597],{"class":874},[860,1907,1908],{"class":1483},"async",[860,1910,1547],{"class":870},[860,1912,1914],{"class":1913},"sHdIc","event",[860,1916,1873],{"class":870},[860,1918,1919],{"class":1483}," =>",[860,1921,1609],{"class":870},[860,1923,1924],{"class":862,"line":1174},[860,1925,1926],{"class":1477},"  \u002F\u002F 1. Who may call: the permission, answered by roles, policies and the host's before hook.\n",[860,1928,1930,1933,1935,1937,1939,1941,1943,1945,1948,1950,1953],{"class":862,"line":1929},11,[860,1931,1932],{"class":866},"  await",[860,1934,1460],{"class":903},[860,1936,1597],{"class":921},[860,1938,1914],{"class":874},[860,1940,909],{"class":870},[860,1942,1801],{"class":874},[860,1944,625],{"class":870},[860,1946,1947],{"class":874},"permissions",[860,1949,625],{"class":870},[860,1951,1952],{"class":874},"update",[860,1954,1198],{"class":921},[860,1956,1958],{"class":862,"line":1957},12,[860,1959,1960],{"class":1477},"  \u002F\u002F 2. Parse the path params, then the body.\n",[860,1962,1964,1967,1969,1972,1974,1977,1979,1982,1984,1986,1988,1990,1992,1994,1997],{"class":862,"line":1963},13,[860,1965,1966],{"class":1483},"  const",[860,1968,871],{"class":870},[860,1970,1971],{"class":874}," id",[860,1973,878],{"class":870},[860,1975,1976],{"class":870}," =",[860,1978,1493],{"class":866},[860,1980,1981],{"class":903}," getValidatedRouterParams",[860,1983,1597],{"class":921},[860,1985,1914],{"class":874},[860,1987,909],{"class":870},[860,1989,1757],{"class":903},[860,1991,1852],{"class":921},[860,1993,625],{"class":870},[860,1995,1996],{"class":874},"parse",[860,1998,1198],{"class":921},[860,2000,2002,2004,2007,2009,2011,2014,2016,2018,2020,2023,2025,2027],{"class":862,"line":2001},14,[860,2003,1966],{"class":1483},[860,2005,2006],{"class":874}," input",[860,2008,1976],{"class":870},[860,2010,1493],{"class":866},[860,2012,2013],{"class":903}," readValidatedBody",[860,2015,1597],{"class":921},[860,2017,1914],{"class":874},[860,2019,909],{"class":870},[860,2021,2022],{"class":874}," body",[860,2024,625],{"class":870},[860,2026,1996],{"class":874},[860,2028,1198],{"class":921},[860,2030,2032],{"class":862,"line":2031},15,[860,2033,2034],{"class":1477},"  \u002F\u002F 3. Load the target and act. A missing record is a 404, only after the caller passed step 1.\n",[860,2036,2038,2040,2042,2045,2047,2049,2051,2053,2055,2057,2059,2062,2064,2066,2068,2070,2073,2075,2078,2080,2082,2085,2087,2090,2092,2094,2096,2098,2100,2103,2105,2107,2110,2112,2115],{"class":862,"line":2037},16,[860,2039,1966],{"class":1483},[860,2041,1647],{"class":870},[860,2043,2044],{"class":874},"row",[860,2046,1177],{"class":870},[860,2048,1976],{"class":870},[860,2050,1493],{"class":866},[860,2052,1712],{"class":874},[860,2054,625],{"class":870},[860,2056,1952],{"class":903},[860,2058,1597],{"class":921},[860,2060,2061],{"class":874},"schema",[860,2063,625],{"class":870},[860,2065,1602],{"class":874},[860,2067,1873],{"class":921},[860,2069,625],{"class":870},[860,2071,2072],{"class":903},"set",[860,2074,1597],{"class":921},[860,2076,2077],{"class":874},"input",[860,2079,1873],{"class":921},[860,2081,625],{"class":870},[860,2083,2084],{"class":903},"where",[860,2086,1597],{"class":921},[860,2088,2089],{"class":903},"eq",[860,2091,1597],{"class":921},[860,2093,2061],{"class":874},[860,2095,625],{"class":870},[860,2097,1602],{"class":874},[860,2099,625],{"class":870},[860,2101,2102],{"class":874},"id",[860,2104,909],{"class":870},[860,2106,1971],{"class":874},[860,2108,2109],{"class":921},"))",[860,2111,625],{"class":870},[860,2113,2114],{"class":903},"returning",[860,2116,2117],{"class":921},"()\n",[860,2119,2121,2124,2126,2129,2131,2133,2136,2138,2140,2142,2145,2147],{"class":862,"line":2120},17,[860,2122,2123],{"class":866},"  if",[860,2125,1547],{"class":921},[860,2127,2128],{"class":870},"!",[860,2130,2044],{"class":874},[860,2132,1474],{"class":921},[860,2134,2135],{"class":866},"throw",[860,2137,1762],{"class":903},[860,2139,1597],{"class":921},[860,2141,933],{"class":870},[860,2143,2144],{"class":887},"Thing not found.",[860,2146,933],{"class":870},[860,2148,1198],{"class":921},[860,2150,2152],{"class":862,"line":2151},18,[860,2153,2154],{"class":1477},"  \u002F\u002F toThingDto lives in src\u002Fruntime\u002Fserver\u002Futils too. See Responses.\n",[860,2156,2158,2161,2164,2166,2168],{"class":862,"line":2157},19,[860,2159,2160],{"class":866},"  return",[860,2162,2163],{"class":903}," toThingDto",[860,2165,1597],{"class":921},[860,2167,2044],{"class":874},[860,2169,1198],{"class":921},[860,2171,2173,2175],{"class":862,"line":2172},20,[860,2174,1660],{"class":870},[860,2176,1198],{"class":874},[593,2178,2179,620,2182,2185,2186,2189,2190,2193],{},[622,2180,2181],{},"defineEventHandler",[622,2183,2184],{},"getValidatedRouterParams"," and ",[622,2187,2188],{},"readValidatedBody"," are h3's, auto-imported by Nitro. The Events\nmodule's ",[622,2191,2192],{},"admin\u002Faddon.patch.ts"," is a real route built this way.",[631,2195,2196],{"id":2077},"Input",[593,2198,2199,2200,625],{},"Two zod schemas validate ids the same way in every module. Both are for ",[622,2201,2184],{},[768,2203,2204,2214],{},[771,2205,2206],{},[774,2207,2208,2211],{},[777,2209,2210],{},"Schema",[777,2212,2213],{},"Accepts",[784,2215,2216,2226],{},[774,2217,2218,2223],{},[789,2219,2220],{},[622,2221,2222],{},"idParams(name = 'id')",[789,2224,2225],{},"A non-empty string.",[774,2227,2228,2233],{},[789,2229,2230],{},[622,2231,2232],{},"intIdParams(name = 'id')",[789,2234,2235],{},"A positive integer, coerced from the path.",[851,2237,2239],{"className":853,"code":2238,"language":855,"meta":856,"style":856},"const { id } = await getValidatedRouterParams(event, idParams().parse)\nconst { id: userId } = await getValidatedRouterParams(event, intIdParams().parse)\n",[622,2240,2241,2271],{"__ignoreMap":856},[860,2242,2243,2245,2247,2250,2252,2254,2256,2258,2260,2262,2264,2266,2268],{"class":862,"line":863},[860,2244,1484],{"class":1483},[860,2246,871],{"class":870},[860,2248,2249],{"class":874}," id ",[860,2251,1660],{"class":870},[860,2253,1976],{"class":870},[860,2255,1493],{"class":866},[860,2257,1981],{"class":903},[860,2259,1463],{"class":874},[860,2261,909],{"class":870},[860,2263,1757],{"class":903},[860,2265,1852],{"class":874},[860,2267,625],{"class":870},[860,2269,2270],{"class":874},"parse)\n",[860,2272,2273,2275,2277,2279,2281,2284,2286,2288,2290,2292,2294,2296,2299,2301,2303],{"class":862,"line":893},[860,2274,1484],{"class":1483},[860,2276,871],{"class":870},[860,2278,1971],{"class":921},[860,2280,925],{"class":870},[860,2282,2283],{"class":874}," userId ",[860,2285,1660],{"class":870},[860,2287,1976],{"class":870},[860,2289,1493],{"class":866},[860,2291,1981],{"class":903},[860,2293,1463],{"class":874},[860,2295,909],{"class":870},[860,2297,2298],{"class":903}," intIdParams",[860,2300,1852],{"class":874},[860,2302,625],{"class":870},[860,2304,2270],{"class":874},[631,2306,2308],{"id":2307},"errors","Errors",[593,2310,2311,2312,2315],{},"Errors go through ",[622,2313,2314],{},"apiError(status, code, message, data?)"," and its shortcuts. Every error has the same shape:",[636,2317,2318,2325],{},[639,2319,2320,2321,2324],{},"The message is the ",[622,2322,2323],{},"statusMessage",", which the interface shows.",[639,2326,2327,2330],{},[622,2328,2329],{},"data.code"," is a snake_case code a client can test for.",[768,2332,2333,2347],{},[771,2334,2335],{},[774,2336,2337,2340,2343],{},[777,2338,2339],{},"Helper",[777,2341,2342],{},"Status",[777,2344,2345],{},[622,2346,2329],{},[784,2348,2349,2362,2377,2395,2408,2423,2438],{},[774,2350,2351,2355,2358],{},[789,2352,2353],{},[622,2354,2314],{},[789,2356,2357],{},"Any",[789,2359,2360],{},[622,2361,622],{},[774,2363,2364,2369,2372],{},[789,2365,2366],{},[622,2367,2368],{},"badRequest(message?, data?)",[789,2370,2371],{},"400",[789,2373,2374],{},[622,2375,2376],{},"bad_request",[774,2378,2379,2384,2386],{},[789,2380,2381],{},[622,2382,2383],{},"validationError(issues, message?)",[789,2385,2371],{},[789,2387,2388,2391,2392],{},[622,2389,2390],{},"validation_failed",", with the issues in ",[622,2393,2394],{},"data.issues",[774,2396,2397,2402,2404],{},[789,2398,2399],{},[622,2400,2401],{},"forbidden(message?, data?)",[789,2403,1355],{},[789,2405,2406],{},[622,2407,1365],{},[774,2409,2410,2415,2418],{},[789,2411,2412],{},[622,2413,2414],{},"notFound(message?, data?)",[789,2416,2417],{},"404",[789,2419,2420],{},[622,2421,2422],{},"not_found",[774,2424,2425,2430,2433],{},[789,2426,2427],{},[622,2428,2429],{},"conflict(message?, data?)",[789,2431,2432],{},"409",[789,2434,2435],{},[622,2436,2437],{},"conflict",[774,2439,2440,2445,2448],{},[789,2441,2442],{},[622,2443,2444],{},"notConfigured(message?, data?)",[789,2446,2447],{},"501",[789,2449,2450],{},[622,2451,2452],{},"not_configured",[593,2454,2455,2456,810,2458,2185,2460,2462,2463,620,2466,620,2468,2185,2470,625],{},"Each helper returns an h3 error for you to ",[622,2457,2135],{},[622,2459,1272],{},[622,2461,1412],{}," answer in the same shape, with the codes\n",[622,2464,2465],{},"unauthorized",[622,2467,1359],{},[622,2469,1362],{},[622,2471,1365],{},[851,2473,2475],{"className":853,"code":2474,"language":855,"meta":856,"style":856},"import { conflict, notFound } from '@frigear-nu\u002Fmodule-kit\u002Fserver'\n\nif (!form) throw notFound('Form not found.')\nif (await slugTaken(body.slug)) throw conflict('That slug is taken.', { field: 'slug' })\n",[622,2476,2477,2500,2504,2531],{"__ignoreMap":856},[860,2478,2479,2481,2483,2486,2488,2490,2492,2494,2496,2498],{"class":862,"line":863},[860,2480,867],{"class":866},[860,2482,871],{"class":870},[860,2484,2485],{"class":874}," conflict",[860,2487,909],{"class":870},[860,2489,1762],{"class":874},[860,2491,878],{"class":870},[860,2493,881],{"class":866},[860,2495,884],{"class":870},[860,2497,624],{"class":887},[860,2499,890],{"class":870},[860,2501,2502],{"class":862,"line":893},[860,2503,897],{"emptyLinePlaceholder":896},[860,2505,2506,2509,2511,2513,2516,2518,2520,2522,2524,2527,2529],{"class":862,"line":900},[860,2507,2508],{"class":866},"if",[860,2510,1547],{"class":874},[860,2512,2128],{"class":870},[860,2514,2515],{"class":874},"form) ",[860,2517,2135],{"class":866},[860,2519,1762],{"class":903},[860,2521,1597],{"class":874},[860,2523,933],{"class":870},[860,2525,2526],{"class":887},"Form not found.",[860,2528,933],{"class":870},[860,2530,1198],{"class":874},[860,2532,2533,2535,2537,2539,2542,2545,2547,2550,2552,2554,2556,2558,2561,2563,2565,2567,2570,2572,2574,2577,2579,2581],{"class":862,"line":915},[860,2534,2508],{"class":866},[860,2536,1547],{"class":874},[860,2538,1457],{"class":866},[860,2540,2541],{"class":903}," slugTaken",[860,2543,2544],{"class":874},"(body",[860,2546,625],{"class":870},[860,2548,2549],{"class":874},"slug)) ",[860,2551,2135],{"class":866},[860,2553,2485],{"class":903},[860,2555,1597],{"class":874},[860,2557,933],{"class":870},[860,2559,2560],{"class":887},"That slug is taken.",[860,2562,933],{"class":870},[860,2564,909],{"class":870},[860,2566,871],{"class":870},[860,2568,2569],{"class":921}," field",[860,2571,925],{"class":870},[860,2573,884],{"class":870},[860,2575,2576],{"class":887},"slug",[860,2578,933],{"class":870},[860,2580,878],{"class":870},[860,2582,1198],{"class":874},[1263,2584,2586],{"id":2585},"field-issues","Field issues",[593,2588,2589],{},"Use this when a client needs an issue per field, such as a form that marks each input.",[593,2591,2592,2593,2595,2596,2599,2600,2603,2604,2607,2608,2610,2611,2614],{},"h3's ",[622,2594,1229],{}," answers a failed parse with a 400. Its ",[622,2597,2598],{},"data"," carries the zod error only\nas text. So parse the body yourself with ",[622,2601,2602],{},"schema.safeParse",", and throw ",[622,2605,2606],{},"validationError(issues)",". The issues arrive in\n",[622,2609,2394],{},". The Forms ",[622,2612,2613],{},"submit"," route is the example:",[851,2616,2618],{"className":853,"code":2617,"language":855,"meta":856,"style":856},"const result = definition.schema.safeParse(await readBody(event))\nif (!result.success) throw validationError(result.error.issues)\n",[622,2619,2620,2651],{"__ignoreMap":856},[860,2621,2622,2624,2627,2629,2632,2634,2636,2638,2641,2643,2645,2648],{"class":862,"line":863},[860,2623,1484],{"class":1483},[860,2625,2626],{"class":874}," result ",[860,2628,1490],{"class":870},[860,2630,2631],{"class":874}," definition",[860,2633,625],{"class":870},[860,2635,2061],{"class":874},[860,2637,625],{"class":870},[860,2639,2640],{"class":903},"safeParse",[860,2642,1597],{"class":874},[860,2644,1457],{"class":866},[860,2646,2647],{"class":903}," readBody",[860,2649,2650],{"class":874},"(event))\n",[860,2652,2653,2655,2657,2659,2662,2664,2667,2669,2672,2675,2677,2680,2682],{"class":862,"line":893},[860,2654,2508],{"class":866},[860,2656,1547],{"class":874},[860,2658,2128],{"class":870},[860,2660,2661],{"class":874},"result",[860,2663,625],{"class":870},[860,2665,2666],{"class":874},"success) ",[860,2668,2135],{"class":866},[860,2670,2671],{"class":903}," validationError",[860,2673,2674],{"class":874},"(result",[860,2676,625],{"class":870},[860,2678,2679],{"class":874},"error",[860,2681,625],{"class":870},[860,2683,2684],{"class":874},"issues)\n",[631,2686,2688],{"id":2687},"responses","Responses",[768,2690,2691,2702],{},[771,2692,2693],{},[774,2694,2695,2697,2699],{},[777,2696,1281],{},[777,2698,2342],{},[777,2700,2701],{},"Return",[784,2703,2704,2717,2728],{},[774,2705,2706,2709,2712],{},[789,2707,2708],{},"Create",[789,2710,2711],{},"201",[789,2713,2714],{},[622,2715,2716],{},"created(event, dto)",[774,2718,2719,2722,2725],{},[789,2720,2721],{},"Update or action",[789,2723,2724],{},"200",[789,2726,2727],{},"The DTO",[774,2729,2730,2733,2736],{},[789,2731,2732],{},"Delete or revoke",[789,2734,2735],{},"204",[789,2737,2738],{},[622,2739,2740],{},"noContent(event)",[593,2742,2743],{},"Every response is a data transfer object (DTO) that lists its fields explicitly, never a database row:",[636,2745,2746,2757,2763],{},[639,2747,2748,2749,2752,2753,2756],{},"A ",[622,2750,2751],{},"to\u003CEntity>Dto()"," function in ",[622,2754,2755],{},"server\u002Futils"," builds it.",[639,2758,2759,2760,625],{},"Dates are ISO strings, made with ",[622,2761,2762],{},"isoDate",[639,2764,2765,2766,625],{},"The DTO types live in ",[622,2767,2768],{},"src\u002Ftypes.ts",[768,2770,2771,2780],{},[771,2772,2773],{},[774,2774,2775,2777],{},[777,2776,2339],{},[777,2778,2779],{},"Purpose",[784,2781,2782,2794,2807],{},[774,2783,2784,2788],{},[789,2785,2786],{},[622,2787,2716],{},[789,2789,2790,2791,625],{},"Sets 201 and returns ",[622,2792,2793],{},"dto",[774,2795,2796,2800],{},[789,2797,2798],{},[622,2799,2740],{},[789,2801,2802,2803,2806],{},"Sets 204 and returns ",[622,2804,2805],{},"null",", for deletes and revokes.",[774,2808,2809,2814],{},[789,2810,2811],{},[622,2812,2813],{},"isoDate(value)",[789,2815,2816,2817,2820,2821,1257,2823,2826,2827,625],{},"Turns a ",[622,2818,2819],{},"Date",", epoch milliseconds or a date string into an ISO string, and ",[622,2822,2805],{},[622,2824,2825],{},"undefined"," into ",[622,2828,2805],{},[631,2830,2832],{"id":2831},"pagination","Pagination",[593,2834,2835,2836,2839,2840,2185,2843,625],{},"Admin lists are paginated. They answer ",[622,2837,2838],{},"{ items, total, page, pageSize }",", built with ",[622,2841,2842],{},"pageQuery()",[622,2844,2845],{},"paginate()",[636,2847,2848,2881,2887],{},[639,2849,2850,2853,2854,2857,2858,2860,2861,620,2864,1374,2867,620,2870,620,2873,2876,2877,2880],{},[601,2851,2852],{},"Query keys."," They match the table composable ",[622,2855,2856],{},"useTableQuery"," of ",[606,2859,326],{"href":327},": ",[622,2862,2863],{},"page",[622,2865,2866],{},"pageSize",[622,2868,2869],{},"q",[622,2871,2872],{},"sort",[622,2874,2875],{},"order"," and the list's ",[622,2878,2879],{},"filters"," keys.",[639,2882,2883,2886],{},[601,2884,2885],{},"Page size."," The default is 20, and the most a client may ask for is 100.",[639,2888,2889,2892],{},[601,2890,2891],{},"Small lists."," Small bounded lists, such as tokens, roles and plans, stay plain arrays.",[851,2894,2897],{"className":853,"code":2895,"filename":2896,"language":855,"meta":856,"style":856},"import { db, schema } from '@nuxthub\u002Fdb'\nimport { and, count, eq } from 'drizzle-orm'\nimport { likeContains, orderByQuery, pageQuery, pageRange, paginate } from '@frigear-nu\u002Fmodule-kit\u002Fserver'\nimport { z } from 'zod'\n\nconst listQuery = pageQuery({ sort: ['startsAt', 'slug'], filters: { status: z.enum(['draft', 'published']).optional() } })\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F Staff only: the host's roles decide who holds `events:manage`. See Staff access.\n  await authorize(event, 'events:manage')\n  const query = await getValidatedQuery(event, listQuery.parse)\n  const where = and(\n    query.q ? likeContains(schema.events.slug, query.q) : undefined,\n    query.status ? eq(schema.events.status, query.status) : undefined,\n  )\n  const orderBy = orderByQuery({ startsAt: schema.events.startsAt, slug: schema.events.slug }, query, { tiebreaker: schema.events.id })\n  const { limit, offset } = pageRange(query)\n  const [rows, [totals]] = await Promise.all([\n    db.select().from(schema.events).where(where).orderBy(...orderBy).limit(limit).offset(offset),\n    db.select({ total: count() }).from(schema.events).where(where),\n  ])\n  return paginate({ items: rows.map(toEventDto), total: totals?.total ?? 0, query })\n})\n","src\u002Fruntime\u002Fserver\u002Froutes\u002Fadmin\u002Fevents.get.ts",[622,2898,2899,2921,2949,2988,3006,3010,3106,3110,3132,3137,3157,3186,3200,3242,3281,3286,3360,3387,3422,3497,3548,3554,3614],{"__ignoreMap":856},[860,2900,2901,2903,2905,2907,2909,2911,2913,2915,2917,2919],{"class":862,"line":863},[860,2902,867],{"class":866},[860,2904,871],{"class":870},[860,2906,1712],{"class":874},[860,2908,909],{"class":870},[860,2910,1717],{"class":874},[860,2912,878],{"class":870},[860,2914,881],{"class":866},[860,2916,884],{"class":870},[860,2918,1726],{"class":887},[860,2920,890],{"class":870},[860,2922,2923,2925,2927,2930,2932,2935,2937,2939,2941,2943,2945,2947],{"class":862,"line":893},[860,2924,867],{"class":866},[860,2926,871],{"class":870},[860,2928,2929],{"class":874}," and",[860,2931,909],{"class":870},[860,2933,2934],{"class":874}," count",[860,2936,909],{"class":870},[860,2938,1737],{"class":874},[860,2940,878],{"class":870},[860,2942,881],{"class":866},[860,2944,884],{"class":870},[860,2946,1746],{"class":887},[860,2948,890],{"class":870},[860,2950,2951,2953,2955,2958,2960,2963,2965,2968,2970,2973,2975,2978,2980,2982,2984,2986],{"class":862,"line":900},[860,2952,867],{"class":866},[860,2954,871],{"class":870},[860,2956,2957],{"class":874}," likeContains",[860,2959,909],{"class":870},[860,2961,2962],{"class":874}," orderByQuery",[860,2964,909],{"class":870},[860,2966,2967],{"class":874}," pageQuery",[860,2969,909],{"class":870},[860,2971,2972],{"class":874}," pageRange",[860,2974,909],{"class":870},[860,2976,2977],{"class":874}," paginate",[860,2979,878],{"class":870},[860,2981,881],{"class":866},[860,2983,884],{"class":870},[860,2985,624],{"class":887},[860,2987,890],{"class":870},[860,2989,2990,2992,2994,2996,2998,3000,3002,3004],{"class":862,"line":915},[860,2991,867],{"class":866},[860,2993,871],{"class":870},[860,2995,1781],{"class":874},[860,2997,878],{"class":870},[860,2999,881],{"class":866},[860,3001,884],{"class":870},[860,3003,1790],{"class":887},[860,3005,890],{"class":870},[860,3007,3008],{"class":862,"line":963},[860,3009,897],{"emptyLinePlaceholder":896},[860,3011,3012,3014,3017,3019,3021,3023,3025,3028,3030,3032,3034,3037,3039,3041,3043,3045,3047,3049,3051,3054,3056,3058,3061,3063,3065,3067,3070,3073,3075,3078,3080,3082,3084,3087,3089,3092,3094,3097,3100,3102,3104],{"class":862,"line":1002},[860,3013,1484],{"class":1483},[860,3015,3016],{"class":874}," listQuery ",[860,3018,1490],{"class":870},[860,3020,2967],{"class":903},[860,3022,1597],{"class":874},[860,3024,1837],{"class":870},[860,3026,3027],{"class":921}," sort",[860,3029,925],{"class":870},[860,3031,1647],{"class":874},[860,3033,933],{"class":870},[860,3035,3036],{"class":887},"startsAt",[860,3038,933],{"class":870},[860,3040,909],{"class":870},[860,3042,884],{"class":870},[860,3044,2576],{"class":887},[860,3046,933],{"class":870},[860,3048,1177],{"class":874},[860,3050,909],{"class":870},[860,3052,3053],{"class":921}," filters",[860,3055,925],{"class":870},[860,3057,871],{"class":870},[860,3059,3060],{"class":921}," status",[860,3062,925],{"class":870},[860,3064,1781],{"class":874},[860,3066,625],{"class":870},[860,3068,3069],{"class":903},"enum",[860,3071,3072],{"class":874},"([",[860,3074,933],{"class":870},[860,3076,3077],{"class":887},"draft",[860,3079,933],{"class":870},[860,3081,909],{"class":870},[860,3083,884],{"class":870},[860,3085,3086],{"class":887},"published",[860,3088,933],{"class":870},[860,3090,3091],{"class":874},"])",[860,3093,625],{"class":870},[860,3095,3096],{"class":903},"optional",[860,3098,3099],{"class":874},"() ",[860,3101,1660],{"class":870},[860,3103,878],{"class":870},[860,3105,1198],{"class":874},[860,3107,3108],{"class":862,"line":1045},[860,3109,897],{"emptyLinePlaceholder":896},[860,3111,3112,3114,3116,3118,3120,3122,3124,3126,3128,3130],{"class":862,"line":1088},[860,3113,1584],{"class":866},[860,3115,1900],{"class":866},[860,3117,1903],{"class":903},[860,3119,1597],{"class":874},[860,3121,1908],{"class":1483},[860,3123,1547],{"class":870},[860,3125,1914],{"class":1913},[860,3127,1873],{"class":870},[860,3129,1919],{"class":1483},[860,3131,1609],{"class":870},[860,3133,3134],{"class":862,"line":1131},[860,3135,3136],{"class":1477},"  \u002F\u002F Staff only: the host's roles decide who holds `events:manage`. See Staff access.\n",[860,3138,3139,3141,3143,3145,3147,3149,3151,3153,3155],{"class":862,"line":1174},[860,3140,1932],{"class":866},[860,3142,1460],{"class":903},[860,3144,1597],{"class":921},[860,3146,1914],{"class":874},[860,3148,909],{"class":870},[860,3150,884],{"class":870},[860,3152,1373],{"class":887},[860,3154,933],{"class":870},[860,3156,1198],{"class":921},[860,3158,3159,3161,3164,3166,3168,3171,3173,3175,3177,3180,3182,3184],{"class":862,"line":1929},[860,3160,1966],{"class":1483},[860,3162,3163],{"class":874}," query",[860,3165,1976],{"class":870},[860,3167,1493],{"class":866},[860,3169,3170],{"class":903}," getValidatedQuery",[860,3172,1597],{"class":921},[860,3174,1914],{"class":874},[860,3176,909],{"class":870},[860,3178,3179],{"class":874}," listQuery",[860,3181,625],{"class":870},[860,3183,1996],{"class":874},[860,3185,1198],{"class":921},[860,3187,3188,3190,3193,3195,3197],{"class":862,"line":1957},[860,3189,1966],{"class":1483},[860,3191,3192],{"class":874}," where",[860,3194,1976],{"class":870},[860,3196,2929],{"class":903},[860,3198,3199],{"class":921},"(\n",[860,3201,3202,3205,3207,3209,3212,3214,3216,3218,3220,3223,3225,3227,3229,3231,3233,3235,3237,3239],{"class":862,"line":1963},[860,3203,3204],{"class":874},"    query",[860,3206,625],{"class":870},[860,3208,2869],{"class":874},[860,3210,3211],{"class":870}," ?",[860,3213,2957],{"class":903},[860,3215,1597],{"class":921},[860,3217,2061],{"class":874},[860,3219,625],{"class":870},[860,3221,3222],{"class":874},"events",[860,3224,625],{"class":870},[860,3226,2576],{"class":874},[860,3228,909],{"class":870},[860,3230,3163],{"class":874},[860,3232,625],{"class":870},[860,3234,2869],{"class":874},[860,3236,1474],{"class":921},[860,3238,925],{"class":870},[860,3240,3241],{"class":870}," undefined,\n",[860,3243,3244,3246,3248,3251,3253,3255,3257,3259,3261,3263,3265,3267,3269,3271,3273,3275,3277,3279],{"class":862,"line":2001},[860,3245,3204],{"class":874},[860,3247,625],{"class":870},[860,3249,3250],{"class":874},"status",[860,3252,3211],{"class":870},[860,3254,1737],{"class":903},[860,3256,1597],{"class":921},[860,3258,2061],{"class":874},[860,3260,625],{"class":870},[860,3262,3222],{"class":874},[860,3264,625],{"class":870},[860,3266,3250],{"class":874},[860,3268,909],{"class":870},[860,3270,3163],{"class":874},[860,3272,625],{"class":870},[860,3274,3250],{"class":874},[860,3276,1474],{"class":921},[860,3278,925],{"class":870},[860,3280,3241],{"class":870},[860,3282,3283],{"class":862,"line":2031},[860,3284,3285],{"class":921},"  )\n",[860,3287,3288,3290,3293,3295,3297,3299,3301,3304,3306,3308,3310,3312,3314,3316,3318,3321,3323,3325,3327,3329,3331,3333,3335,3337,3339,3341,3344,3346,3348,3350,3352,3354,3356,3358],{"class":862,"line":2037},[860,3289,1966],{"class":1483},[860,3291,3292],{"class":874}," orderBy",[860,3294,1976],{"class":870},[860,3296,2962],{"class":903},[860,3298,1597],{"class":921},[860,3300,1837],{"class":870},[860,3302,3303],{"class":921}," startsAt",[860,3305,925],{"class":870},[860,3307,1717],{"class":874},[860,3309,625],{"class":870},[860,3311,3222],{"class":874},[860,3313,625],{"class":870},[860,3315,3036],{"class":874},[860,3317,909],{"class":870},[860,3319,3320],{"class":921}," slug",[860,3322,925],{"class":870},[860,3324,1717],{"class":874},[860,3326,625],{"class":870},[860,3328,3222],{"class":874},[860,3330,625],{"class":870},[860,3332,2576],{"class":874},[860,3334,1663],{"class":870},[860,3336,3163],{"class":874},[860,3338,909],{"class":870},[860,3340,871],{"class":870},[860,3342,3343],{"class":921}," tiebreaker",[860,3345,925],{"class":870},[860,3347,1717],{"class":874},[860,3349,625],{"class":870},[860,3351,3222],{"class":874},[860,3353,625],{"class":870},[860,3355,2102],{"class":874},[860,3357,878],{"class":870},[860,3359,1198],{"class":921},[860,3361,3362,3364,3366,3369,3371,3374,3376,3378,3380,3382,3385],{"class":862,"line":2120},[860,3363,1966],{"class":1483},[860,3365,871],{"class":870},[860,3367,3368],{"class":874}," limit",[860,3370,909],{"class":870},[860,3372,3373],{"class":874}," offset",[860,3375,878],{"class":870},[860,3377,1976],{"class":870},[860,3379,2972],{"class":903},[860,3381,1597],{"class":921},[860,3383,3384],{"class":874},"query",[860,3386,1198],{"class":921},[860,3388,3389,3391,3393,3396,3398,3400,3403,3406,3408,3410,3414,3416,3419],{"class":862,"line":2151},[860,3390,1966],{"class":1483},[860,3392,1647],{"class":870},[860,3394,3395],{"class":874},"rows",[860,3397,909],{"class":870},[860,3399,1647],{"class":870},[860,3401,3402],{"class":874},"totals",[860,3404,3405],{"class":870},"]]",[860,3407,1976],{"class":870},[860,3409,1493],{"class":866},[860,3411,3413],{"class":3412},"sBMFI"," Promise",[860,3415,625],{"class":870},[860,3417,3418],{"class":903},"all",[860,3420,3421],{"class":921},"([\n",[860,3423,3424,3427,3429,3432,3434,3436,3439,3441,3443,3445,3447,3449,3451,3453,3455,3457,3459,3461,3464,3466,3469,3471,3473,3475,3478,3480,3482,3484,3486,3489,3491,3493,3495],{"class":862,"line":2157},[860,3425,3426],{"class":874},"    db",[860,3428,625],{"class":870},[860,3430,3431],{"class":903},"select",[860,3433,1852],{"class":921},[860,3435,625],{"class":870},[860,3437,3438],{"class":903},"from",[860,3440,1597],{"class":921},[860,3442,2061],{"class":874},[860,3444,625],{"class":870},[860,3446,3222],{"class":874},[860,3448,1873],{"class":921},[860,3450,625],{"class":870},[860,3452,2084],{"class":903},[860,3454,1597],{"class":921},[860,3456,2084],{"class":874},[860,3458,1873],{"class":921},[860,3460,625],{"class":870},[860,3462,3463],{"class":903},"orderBy",[860,3465,1597],{"class":921},[860,3467,3468],{"class":870},"...",[860,3470,3463],{"class":874},[860,3472,1873],{"class":921},[860,3474,625],{"class":870},[860,3476,3477],{"class":903},"limit",[860,3479,1597],{"class":921},[860,3481,3477],{"class":874},[860,3483,1873],{"class":921},[860,3485,625],{"class":870},[860,3487,3488],{"class":903},"offset",[860,3490,1597],{"class":921},[860,3492,3488],{"class":874},[860,3494,1873],{"class":921},[860,3496,1374],{"class":870},[860,3498,3499,3501,3503,3505,3507,3509,3512,3514,3516,3518,3520,3522,3524,3526,3528,3530,3532,3534,3536,3538,3540,3542,3544,3546],{"class":862,"line":2172},[860,3500,3426],{"class":874},[860,3502,625],{"class":870},[860,3504,3431],{"class":903},[860,3506,1597],{"class":921},[860,3508,1837],{"class":870},[860,3510,3511],{"class":921}," total",[860,3513,925],{"class":870},[860,3515,2934],{"class":903},[860,3517,3099],{"class":921},[860,3519,1660],{"class":870},[860,3521,1873],{"class":921},[860,3523,625],{"class":870},[860,3525,3438],{"class":903},[860,3527,1597],{"class":921},[860,3529,2061],{"class":874},[860,3531,625],{"class":870},[860,3533,3222],{"class":874},[860,3535,1873],{"class":921},[860,3537,625],{"class":870},[860,3539,2084],{"class":903},[860,3541,1597],{"class":921},[860,3543,2084],{"class":874},[860,3545,1873],{"class":921},[860,3547,1374],{"class":870},[860,3549,3551],{"class":862,"line":3550},21,[860,3552,3553],{"class":921},"  ])\n",[860,3555,3557,3559,3561,3563,3565,3568,3570,3573,3575,3578,3580,3583,3585,3587,3589,3591,3594,3597,3600,3603,3606,3608,3610,3612],{"class":862,"line":3556},22,[860,3558,2160],{"class":866},[860,3560,2977],{"class":903},[860,3562,1597],{"class":921},[860,3564,1837],{"class":870},[860,3566,3567],{"class":921}," items",[860,3569,925],{"class":870},[860,3571,3572],{"class":874}," rows",[860,3574,625],{"class":870},[860,3576,3577],{"class":903},"map",[860,3579,1597],{"class":921},[860,3581,3582],{"class":874},"toEventDto",[860,3584,1873],{"class":921},[860,3586,909],{"class":870},[860,3588,3511],{"class":921},[860,3590,925],{"class":870},[860,3592,3593],{"class":874}," totals",[860,3595,3596],{"class":870},"?.",[860,3598,3599],{"class":874},"total",[860,3601,3602],{"class":870}," ??",[860,3604,3605],{"class":1869}," 0",[860,3607,909],{"class":870},[860,3609,3163],{"class":874},[860,3611,878],{"class":870},[860,3613,1198],{"class":921},[860,3615,3617,3619],{"class":862,"line":3616},23,[860,3618,1660],{"class":870},[860,3620,1198],{"class":874},[593,3622,3623,3625,3626,3628,3629,3632,3633,3636,3637,3640,3641,1257,3644,3647],{},[622,3624,3582],{}," is the Events module's DTO function, in its ",[622,3627,2755],{}," (see ",[606,3630,2688],{"href":3631},"#responses",").\n",[622,3634,3635],{},"likeContains"," searches case-insensitively on both ",[606,3638,3639],{"href":87},"dialects",". A ",[622,3642,3643],{},"%",[622,3645,3646],{},"_"," in the search\ndoes not act as a wildcard.",[631,3649,3651],{"id":3650},"staff-access","Staff access",[593,3653,3654,3655,1257,3657,3659,3660,3662,3663,3666,3667,3670,3671,3674,3675,625],{},"There is no separate staff check. A staff route is a route whose permission only staff roles hold, such as\n",[622,3656,1373],{},[622,3658,1377],{},". A module registers its permissions with ",[622,3661,1369],{}," and gives them to a role by\nname with ",[622,3664,3665],{},"grantTo",", and the host app gives them to its own roles in ",[622,3668,3669],{},"authorization.roles",". Your host app decides who is\nstaff by who holds the role, and can refine with a ",[606,3672,3673],{"href":1445},"policy"," or the\n",[606,3676,3678],{"href":3677},"\u002Fguide\u002Fconcepts\u002Fauth-and-scopes#the-before-hook","before hook",[768,3680,3681,3690],{},[771,3682,3683],{},[774,3684,3685,3687],{},[777,3686,1281],{},[777,3688,3689],{},"Check",[784,3691,3692,3702,3719,3731],{},[774,3693,3694,3697],{},[789,3695,3696],{},"A read or an ordinary edit",[789,3698,3699,3701],{},[622,3700,1299],{},". API tokens whose scopes cover it work.",[774,3703,3704,3707],{},[789,3705,3706],{},"Credentials, money and billing",[789,3708,3709,3712,3713,3715,3716,625],{},[622,3710,3711],{},"await authorize(event, 'events:refund')",", a ",[622,3714,1398],{}," permission. Tokens are refused with ",[622,3717,3718],{},"403 session_required",[774,3720,3721,3724],{},[789,3722,3723],{},"One record",[789,3725,3726,3727,625],{},"The ",[606,3728,3730],{"href":3729},"#the-two-step-route","two-step route",[774,3732,3733,3736],{},[789,3734,3735],{},"A yes or no, such as an MCP tool or a menu",[789,3737,3738,3741],{},[622,3739,3740],{},"await allows(event, 'events:manage')",". Never throws.",[593,3743,3744,3746,3747,3750,3751,3753,3754,3756],{},[622,3745,1272],{}," returns the principal; load the full user with ",[622,3748,3749],{},"loadAuthUser(principal.user.id)"," when a route needs more\nthan the principal holds. The name of anything a module exports from ",[622,3752,2755],{}," must start with the module's name,\nbecause every module's server utils share one namespace (see ",[606,3755,749],{"href":748},").",[631,3758,749],{"id":3759},"naming",[593,3761,3762,3765,3766,3768,3769,3772,3773,1230,3776,3779,3780,625],{},[601,3763,3764],{},"Server utils."," Every module's ",[622,3767,2755],{}," is auto-imported into one ",[606,3770,3771],{"href":87},"Nitro","\nnamespace. So an exported name must be unique across all modules. Prefix it with the module: ",[622,3774,3775],{},"toFormTokenDto",[622,3777,3778],{},"toAuthTokenDto",", never a generic ",[622,3781,3782],{},"toTokenDto",[1241,3784,3785],{},"Two modules that export the same name clash, and one of them silently wins.",[593,3787,3788,3791,3792,3794,3795,620,3798,3801,3802,3805,3806,625],{},[601,3789,3790],{},"Path builders."," Each module keeps typed path builders in ",[622,3793,744],{},", such as ",[622,3796,3797],{},"authApi",[622,3799,3800],{},"eventsApi","\nand ",[622,3803,3804],{},"formsApi",". It exports them as ",[622,3807,3808],{},"@frigear-nu\u002F\u003Cpkg>\u002Fapi",[636,3810,3811,3814,3824],{},[639,3812,3813],{},"Import them from there in app code and tests.",[639,3815,3816,3817,3820,3821,625],{},"Do not import them from ",[622,3818,3819],{},".\u002Futils",", which also holds build-time helpers that pull in ",[622,3822,3823],{},"@nuxt\u002Fkit",[639,3825,3826],{},"Relative imports inside the package stay relative.",[851,3828,3830],{"className":853,"code":3829,"language":855,"meta":856,"style":856},"import { authApi } from '@frigear-nu\u002Fauth\u002Fapi'\n",[622,3831,3832],{"__ignoreMap":856},[860,3833,3834,3836,3838,3841,3843,3845,3847,3850],{"class":862,"line":863},[860,3835,867],{"class":866},[860,3837,871],{"class":870},[860,3839,3840],{"class":874}," authApi",[860,3842,878],{"class":870},[860,3844,881],{"class":866},[860,3846,884],{"class":870},[860,3848,3849],{"class":887},"@frigear-nu\u002Fauth\u002Fapi",[860,3851,890],{"class":870},[631,3853,3855],{"id":3854},"testing-routes","Testing routes",[593,3857,3858,3861,3862,3865,3866,3869,3870,625],{},[622,3859,3860],{},"@frigear-nu\u002Fmodule-kit\u002Ftesting"," has two helpers for end-to-end tests. They run against a\n",[606,3863,3864],{"href":87},"test fixture"," started with ",[622,3867,3868],{},"setup()"," from ",[622,3871,3872],{},"@nuxt\u002Ftest-utils\u002Fe2e",[768,3874,3875,3884],{},[771,3876,3877],{},[774,3878,3879,3881],{},[777,3880,2339],{},[777,3882,3883],{},"What it does",[784,3885,3886,3896,3909,3923],{},[774,3887,3888,3893],{},[789,3889,3890],{},[622,3891,3892],{},"apiClient({ cookie?, bearer? })",[789,3894,3895],{},"Acts as one caller.",[774,3897,3898,3903],{},[789,3899,3900],{},[622,3901,3902],{},".json\u003CT>(method, path, body?)",[789,3904,3905,3906,3908],{},"Resolves with the parsed body of a 2xx response, or ",[622,3907,2805],{}," for 204. Rejects otherwise.",[774,3910,3911,3916],{},[789,3912,3913],{},[622,3914,3915],{},".fetch(method, path, body?)",[789,3917,3918,3919,3922],{},"Resolves with the raw ",[622,3920,3921],{},"Response",", whatever its status.",[774,3924,3925,3930],{},[789,3926,3927],{},[622,3928,3929],{},"expectApiError(promise, status, code?)",[789,3931,3932,3933,3935,3936,3938],{},"Asserts the request failed with ",[622,3934,3250],{},", and with ",[622,3937,2329],{}," when given. Resolves with the error body.",[851,3940,3942],{"className":853,"code":3941,"language":855,"meta":856,"style":856},"import { apiClient, expectApiError } from '@frigear-nu\u002Fmodule-kit\u002Ftesting'\n\nconst staff = apiClient({ cookie: staffCookie })\nconst created = await staff.json\u003C{ id: string }>('POST', '\u002Fapi\u002Fadmin\u002Fevents', { slug: 'party' })\nawait expectApiError(apiClient({ cookie: memberCookie }).json('DELETE', `\u002Fapi\u002Fadmin\u002Fevents\u002F${created.id}`), 403, 'forbidden')\nexpect((await staff.fetch('DELETE', `\u002Fapi\u002Fadmin\u002Fevents\u002F${created.id}`)).status).toBe(204)\n",[622,3943,3944,3968,3972,3999,4067,4144],{"__ignoreMap":856},[860,3945,3946,3948,3950,3953,3955,3958,3960,3962,3964,3966],{"class":862,"line":863},[860,3947,867],{"class":866},[860,3949,871],{"class":870},[860,3951,3952],{"class":874}," apiClient",[860,3954,909],{"class":870},[860,3956,3957],{"class":874}," expectApiError",[860,3959,878],{"class":870},[860,3961,881],{"class":866},[860,3963,884],{"class":870},[860,3965,3860],{"class":887},[860,3967,890],{"class":870},[860,3969,3970],{"class":862,"line":893},[860,3971,897],{"emptyLinePlaceholder":896},[860,3973,3974,3976,3979,3981,3983,3985,3987,3990,3992,3995,3997],{"class":862,"line":900},[860,3975,1484],{"class":1483},[860,3977,3978],{"class":874}," staff ",[860,3980,1490],{"class":870},[860,3982,3952],{"class":903},[860,3984,1597],{"class":874},[860,3986,1837],{"class":870},[860,3988,3989],{"class":921}," cookie",[860,3991,925],{"class":870},[860,3993,3994],{"class":874}," staffCookie ",[860,3996,1660],{"class":870},[860,3998,1198],{"class":874},[860,4000,4001,4003,4006,4008,4010,4013,4015,4018,4021,4023,4025,4028,4031,4033,4035,4038,4040,4042,4044,4046,4048,4050,4052,4054,4056,4058,4061,4063,4065],{"class":862,"line":915},[860,4002,1484],{"class":1483},[860,4004,4005],{"class":874}," created ",[860,4007,1490],{"class":870},[860,4009,1493],{"class":866},[860,4011,4012],{"class":874}," staff",[860,4014,625],{"class":870},[860,4016,4017],{"class":903},"json",[860,4019,4020],{"class":870},"\u003C{",[860,4022,1971],{"class":921},[860,4024,925],{"class":870},[860,4026,4027],{"class":3412}," string",[860,4029,4030],{"class":870}," }>",[860,4032,1597],{"class":874},[860,4034,933],{"class":870},[860,4036,4037],{"class":887},"POST",[860,4039,933],{"class":870},[860,4041,909],{"class":870},[860,4043,884],{"class":870},[860,4045,1191],{"class":887},[860,4047,933],{"class":870},[860,4049,909],{"class":870},[860,4051,871],{"class":870},[860,4053,3320],{"class":921},[860,4055,925],{"class":870},[860,4057,884],{"class":870},[860,4059,4060],{"class":887},"party",[860,4062,933],{"class":870},[860,4064,878],{"class":870},[860,4066,1198],{"class":874},[860,4068,4069,4071,4073,4075,4078,4080,4082,4084,4086,4089,4091,4093,4095,4097,4099,4101,4104,4106,4108,4111,4114,4117,4120,4122,4124,4127,4129,4131,4134,4136,4138,4140,4142],{"class":862,"line":963},[860,4070,1457],{"class":866},[860,4072,3957],{"class":903},[860,4074,1597],{"class":874},[860,4076,4077],{"class":903},"apiClient",[860,4079,1597],{"class":874},[860,4081,1837],{"class":870},[860,4083,3989],{"class":921},[860,4085,925],{"class":870},[860,4087,4088],{"class":874}," memberCookie ",[860,4090,1660],{"class":870},[860,4092,1873],{"class":874},[860,4094,625],{"class":870},[860,4096,4017],{"class":903},[860,4098,1597],{"class":874},[860,4100,933],{"class":870},[860,4102,4103],{"class":887},"DELETE",[860,4105,933],{"class":870},[860,4107,909],{"class":870},[860,4109,4110],{"class":870}," `",[860,4112,4113],{"class":887},"\u002Fapi\u002Fadmin\u002Fevents\u002F",[860,4115,4116],{"class":870},"${",[860,4118,4119],{"class":874},"created",[860,4121,625],{"class":870},[860,4123,2102],{"class":874},[860,4125,4126],{"class":870},"}`",[860,4128,1873],{"class":874},[860,4130,909],{"class":870},[860,4132,4133],{"class":1869}," 403",[860,4135,909],{"class":870},[860,4137,884],{"class":870},[860,4139,1365],{"class":887},[860,4141,933],{"class":870},[860,4143,1198],{"class":874},[860,4145,4146,4149,4152,4154,4156,4158,4161,4163,4165,4167,4169,4171,4173,4175,4177,4179,4181,4183,4185,4187,4189,4192,4194,4197,4199,4201],{"class":862,"line":1002},[860,4147,4148],{"class":903},"expect",[860,4150,4151],{"class":874},"((",[860,4153,1457],{"class":866},[860,4155,4012],{"class":874},[860,4157,625],{"class":870},[860,4159,4160],{"class":903},"fetch",[860,4162,1597],{"class":874},[860,4164,933],{"class":870},[860,4166,4103],{"class":887},[860,4168,933],{"class":870},[860,4170,909],{"class":870},[860,4172,4110],{"class":870},[860,4174,4113],{"class":887},[860,4176,4116],{"class":870},[860,4178,4119],{"class":874},[860,4180,625],{"class":870},[860,4182,2102],{"class":874},[860,4184,4126],{"class":870},[860,4186,2109],{"class":874},[860,4188,625],{"class":870},[860,4190,4191],{"class":874},"status)",[860,4193,625],{"class":870},[860,4195,4196],{"class":903},"toBe",[860,4198,1597],{"class":874},[860,4200,2735],{"class":1869},[860,4202,1198],{"class":874},[631,4204,4206],{"id":4205},"next-steps","Next steps",[636,4208,4209,4214,4219,4224],{},[639,4210,4211,4213],{},[606,4212,482],{"href":483}," – apply the first step of the handler order to your own route.",[639,4215,4216,4218],{},[606,4217,67],{"href":68}," – permissions, roles, policies and what fails closed.",[639,4220,4221,4223],{},[606,4222,381],{"href":382}," – the whole toolkit, including bearer CORS and same-origin URL helpers.",[639,4225,4226,4228],{},[606,4227,537],{"href":538}," – set up a database-backed test fixture for these tests.",[4230,4231,4232],"style",{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":856,"searchDepth":893,"depth":893,"links":4234},[4235,4236,4237,4242,4243,4246,4247,4248,4249,4250,4251],{"id":633,"depth":893,"text":634},{"id":752,"depth":893,"text":613},{"id":1201,"depth":893,"text":1202,"children":4238},[4239,4240,4241],{"id":1265,"depth":900,"text":1266},{"id":1421,"depth":900,"text":1422},{"id":1536,"depth":900,"text":1537},{"id":2077,"depth":893,"text":2196},{"id":2307,"depth":893,"text":2308,"children":4244},[4245],{"id":2585,"depth":900,"text":2586},{"id":2687,"depth":893,"text":2688},{"id":2831,"depth":893,"text":2832},{"id":3650,"depth":893,"text":3651},{"id":3759,"depth":893,"text":749},{"id":3854,"depth":893,"text":3855},{"id":4205,"depth":893,"text":4206},"The URL, handler, error, response and test conventions every module API follows, and the server toolkit that implements them.","md",null,{},{"icon":124},{"title":121,"description":4252},"tQQQHFNWKG2Q94MMuRP8fOIKTNKQ8cg8Y2lSlWe0fDA",[4260,4262],{"title":111,"path":112,"stem":113,"description":4261,"icon":114,"children":-1},"How a screen words its strings, formats dates and money and builds links, plus a checklist for a new page.",{"title":131,"path":132,"stem":133,"description":4263,"icon":134,"children":-1},"Sign a Frigear mobile or browser app in at any OpenID Connect issuer, such as OAuth Server, frigear.nu, Keycloak, Auth0 or Zitadel.",1794503151360]