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