[{"data":1,"prerenderedAt":379},["ShallowReactive",2],{"navigation_docs":3,"-docs-architecture-multi-tenancy":60,"-docs-architecture-multi-tenancy-surround":374},[4,25,45],{"title":5,"path":6,"stem":7,"children":8,"page":24},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started",[9,14,19],{"title":10,"path":11,"stem":12,"icon":13},"Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":15,"path":16,"stem":17,"icon":18},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F1.getting-started\u002F2.installation","i-lucide-rocket",{"title":20,"path":21,"stem":22,"icon":23},"Configuration","\u002Fdocs\u002Fgetting-started\u002Fconfiguration","docs\u002F1.getting-started\u002F3.configuration","i-lucide-sliders-horizontal",false,{"title":26,"path":27,"stem":28,"children":29,"page":24},"Architecture","\u002Fdocs\u002Farchitecture","docs\u002F2.architecture",[30,35,40],{"title":31,"path":32,"stem":33,"icon":34},"Layers","\u002Fdocs\u002Farchitecture\u002Flayers","docs\u002F2.architecture\u002F1.layers","i-lucide-layers",{"title":36,"path":37,"stem":38,"icon":39},"Multi-tenancy","\u002Fdocs\u002Farchitecture\u002Fmulti-tenancy","docs\u002F2.architecture\u002F2.multi-tenancy","i-lucide-building-2",{"title":41,"path":42,"stem":43,"icon":44},"Testing","\u002Fdocs\u002Farchitecture\u002Ftesting","docs\u002F2.architecture\u002F3.testing","i-lucide-flask-conical",{"title":46,"path":47,"stem":48,"children":49,"page":24},"Reference","\u002Fdocs\u002Freference","docs\u002F3.reference",[50,55],{"title":51,"path":52,"stem":53,"icon":54},"Commands","\u002Fdocs\u002Freference\u002Fcommands","docs\u002F3.reference\u002F1.commands","i-lucide-terminal",{"title":56,"path":57,"stem":58,"icon":59},"Decisions","\u002Fdocs\u002Freference\u002Fdecisions","docs\u002F3.reference\u002F2.decisions","i-lucide-git-branch",{"id":61,"title":36,"body":62,"description":367,"extension":368,"links":369,"meta":370,"navigation":371,"path":37,"seo":372,"stem":38,"__hash__":373},"docs\u002Fdocs\u002F2.architecture\u002F2.multi-tenancy.md",{"type":63,"value":64,"toc":361},"minimark",[65,83,98,103,120,172,176,183,317,324,328,340,343,347,353,357],[66,67,68,69,73,74,78,79,82],"p",{},"GStack uses a ",[70,71,72],"strong",{},"single database with a shared schema",". Every table carries a\n",[75,76,77],"code",{},"tenant_id",", and RLS scopes rows through a ",[75,80,81],{},"current_tenant_id()"," security-definer\nhelper that reads the caller's profile.",[66,84,85,86,89,90,93,94,97],{},"Signup creates a tenant and makes the registrant its admin, via a\n",[75,87,88],{},"handle_new_user"," trigger. ",[75,91,92],{},"profiles.id"," ",[70,95,96],{},"is"," the auth user id — there is no\nsecond identity table to keep in sync.",[99,100,102],"h2",{"id":101},"roles","Roles",[66,104,105,108,109,112,113,115,116,119],{},[75,106,107],{},"member"," and ",[75,110,111],{},"admin",". RLS is the real security layer; the ",[75,114,101],{}," key in\n",[75,117,118],{},"definePageMeta"," is UX only — it stops a member from navigating to a page that\nwould render empty anyway.",[121,122,127],"pre",{"className":123,"code":124,"language":125,"meta":126,"style":126},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","definePageMeta({ roles: ['admin'] })\n","ts","",[75,128,129],{"__ignoreMap":126},[130,131,134,137,141,145,149,152,155,158,161,163,166,169],"span",{"class":132,"line":133},"line",1,[130,135,118],{"class":136},"s2Zo4",[130,138,140],{"class":139},"sTEyZ","(",[130,142,144],{"class":143},"sMK4o","{",[130,146,148],{"class":147},"swJcz"," roles",[130,150,151],{"class":143},":",[130,153,154],{"class":139}," [",[130,156,157],{"class":143},"'",[130,159,111],{"class":160},"sfazB",[130,162,157],{"class":143},[130,164,165],{"class":139},"] ",[130,167,168],{"class":143},"}",[130,170,171],{"class":139},")\n",[99,173,175],{"id":174},"why-app-code-doesnt-scope-queries","Why app code doesn't scope queries",[66,177,178,179,182],{},"The reference CRUD (",[75,180,181],{},"layers\u002Fnotes",") never filters by tenant or owner:",[121,184,186],{"className":123,"code":185,"language":125,"meta":126,"style":126},"const { data: notes } = await useAsyncData('notes', async () =>\n  supabase.from('notes').select('*').order('created_at', { ascending: false }),\n)\n",[75,187,188,238,312],{"__ignoreMap":126},[130,189,190,194,197,200,202,205,207,210,214,217,219,221,224,226,229,232,235],{"class":132,"line":133},[130,191,193],{"class":192},"spNyl","const",[130,195,196],{"class":143}," {",[130,198,199],{"class":147}," data",[130,201,151],{"class":143},[130,203,204],{"class":139}," notes ",[130,206,168],{"class":143},[130,208,209],{"class":143}," =",[130,211,213],{"class":212},"s7zQu"," await",[130,215,216],{"class":136}," useAsyncData",[130,218,140],{"class":139},[130,220,157],{"class":143},[130,222,223],{"class":160},"notes",[130,225,157],{"class":143},[130,227,228],{"class":143},",",[130,230,231],{"class":192}," async",[130,233,234],{"class":143}," ()",[130,236,237],{"class":192}," =>\n",[130,239,241,244,247,250,252,254,256,258,261,263,266,268,270,273,275,277,279,282,284,286,289,291,293,295,298,300,304,307,309],{"class":132,"line":240},2,[130,242,243],{"class":139},"  supabase",[130,245,246],{"class":143},".",[130,248,249],{"class":136},"from",[130,251,140],{"class":139},[130,253,157],{"class":143},[130,255,223],{"class":160},[130,257,157],{"class":143},[130,259,260],{"class":139},")",[130,262,246],{"class":143},[130,264,265],{"class":136},"select",[130,267,140],{"class":139},[130,269,157],{"class":143},[130,271,272],{"class":160},"*",[130,274,157],{"class":143},[130,276,260],{"class":139},[130,278,246],{"class":143},[130,280,281],{"class":136},"order",[130,283,140],{"class":139},[130,285,157],{"class":143},[130,287,288],{"class":160},"created_at",[130,290,157],{"class":143},[130,292,228],{"class":143},[130,294,196],{"class":143},[130,296,297],{"class":147}," ascending",[130,299,151],{"class":143},[130,301,303],{"class":302},"sfNiH"," false",[130,305,306],{"class":143}," }",[130,308,260],{"class":139},[130,310,311],{"class":143},",\n",[130,313,315],{"class":132,"line":314},3,[130,316,171],{"class":139},[66,318,319,320,323],{},"There is no ",[75,321,322],{},".eq('tenant_id', …)"," because the policy already applied it. Scoping in\nboth places means two things to keep correct, and the app-layer one is the one\nthat silently rots.",[99,325,327],{"id":326},"proving-it","Proving it",[66,329,330,331,334,335,339],{},"Claiming isolation is cheap. ",[75,332,333],{},"e2e\u002Ftenant-isolation.spec.ts"," logs in as an admin of\na ",[336,337,338],"em",{},"second"," seeded tenant and asserts the first tenant's rows are invisible through\nthe real HTTP surface — not through a mocked client.",[66,341,342],{},"That test is the reason a widened policy fails in CI instead of in production.",[99,344,346],{"id":345},"scaling-path","Scaling path",[66,348,349,350,352],{},"Reading the tenant from the caller's profile costs a lookup per policy evaluation.\nThe upgrade — moving ",[75,351,77],{}," into a JWT claim — is noted in the migration\nitself, so the person who hits the ceiling finds the note where they're already\nlooking.",[354,355,356],"note",{},"That's deliberate: the fast version is a schema change plus a claim, and doing it\nbefore there's load would be optimising a query nobody has run yet.",[358,359,360],"style",{},"html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":126,"searchDepth":240,"depth":240,"links":362},[363,364,365,366],{"id":101,"depth":240,"text":102},{"id":174,"depth":240,"text":175},{"id":326,"depth":240,"text":327},{"id":345,"depth":240,"text":346},"Single database, shared schema, and a policy that does the work.","md",null,{},{"icon":39},{"title":36,"description":367},"6lZHCEl9FmuZSrOtoOWi8cXr5N2A1Kj0ux-5xflqYdM",[375,377],{"title":31,"path":32,"stem":33,"description":376,"icon":34,"children":-1},"How features are organised, and why ~ never means what you think.",{"title":41,"path":42,"stem":43,"description":378,"icon":44,"children":-1},"Two suites with a hard line between them, and a 100% gate that means something.",1785842751196]