Project not found.

scopedDb helper: westside-admin

arch-scoped-db-westside-admin Architecture

architecture active

scopedDb helper: westside-admin

Sub-component focus on the scopedDb / systemDb query layer. Sits inside arch-dataflow-westside-admin as the Postgres access seam — every server route reads/writes through this helper, never directly through the unscoped Drizzle db.

Diagram

Components

Component arch: label Purpose Notes
<code>scopedDb</code> <code>arch:scoped-db</code> Tenant-scoped query builder. Auto-injects <code>WHERE tenant_id = TENANT_ID</code> for every <code>select</code>/<code>update</code>/<code>insert</code>. The ONLY exported DB entry point for tenant-scoped tables. Lives in <code>src/lib/server/scopedDb.ts</code>.
<code>systemDb</code> <code>arch:scoped-db</code> Unscoped builder for system tables that have no <code>tenant_id</code> column. Compile-time error if used on a tenant-scoped table. Tables: <code>alembic_version</code>, <code>player_teams</code>.
<code>db</code> (internal) Raw Drizzle client. Marked <code>@internal</code>; only <code>scopedDb.ts</code> may import. Lint rule + CI grep enforce single entry point.
<code>tenant.ts</code> Source of <code>TENANT_ID</code>. v1: hardcoded constant <code>1</code>. Documented swap point for multi-tenant future (read from JWT claim instead).
Drizzle <code>schema.ts</code> Type information consumed by both helpers. Generated by <code>drizzle-kit pull</code>. The TS types tell <code>scopedDb</code> which tables have <code>tenant_id</code>.

Key Decisions

  • Single entry point, lint-enforced. Any import { db } from '$lib/server/db' outside scopedDb.ts fails CI. Prevents the "forgot the WHERE clause" class of bug at the type system level rather than at code review level.
  • Type system distinguishes tenant tables from system tables. Misusing scopedDb.select(systemTable) or systemDb.select(tenantTable) is a TypeScript error, not a runtime guard. Drizzle's column metadata makes this provable at compile time.
  • tenant_id sourced from constant, not JWT. v1 has one tenant. The constant + helper makes the future swap to claim-sourced trivial — change one file, not 50 call sites.
  • Helper composes with arbitrary where/and/or. Tenant scoping is added via and(eq(tenant_id, TENANT_ID), ...userClauses) — not a string concat. Drizzle's typed builders make this safe.
  • This is a deliberate sub-component. The helper is logically part of the dataflow described in arch-dataflow-westside-admin, but its surface area (two exports, one lint rule) is large enough to warrant its own arch note. Tickets touching this layer carry arch:scoped-db.

Implementation Tickets

  • forgejo_admin/westside-admin#1 (board #1089) — creates the unscoped db, generates schema.ts
  • forgejo_admin/westside-admin#3 (board #1091) — creates scopedDb + systemDb + lint rule + tenant.ts
  • arch-dataflow-westside-admin — parent dataflow note (this is the DB-access seam within it)
  • arch-domain-westside-admin — table inventory; tenant_id presence drives scope decisions
  • project-westside-admin — project page
  • story-westside-admin-admin-row-crud — driving user story (this helper is a Safety Constraint guarantee)
  • convention-architecture-ids — labeling convention