# Architectural Design - Supabase Implementation

Supabase-specific implementation of the principles defined in [ARCHITECTURAL_DESIGN.md](../core/ARCHITECTURAL_DESIGN.md).

## Principles

- **Law of Demeter in Supabase:** A database function should only access the tables it directly needs. An Edge Function processing an order should not reach into user preferences, inventory, analytics, and notifications in a single handler. If it needs data from multiple sources, decompose into focused functions or use database views to present the needed shape.
- **Dependency Injection in Edge Functions:** Do not instantiate Supabase clients or external service clients inline within handler functions. Initialize them at the module level or pass them as parameters. This enables testing with mock clients and swapping implementations without changing business logic.
- **Polymorphism Over Conditionals:** When an Edge Function or database function handles different entity types, use a handler registry or strategy pattern instead of long `if/else` chains. In SQL, use PostgreSQL's table inheritance, JSONB dispatch, or separate typed tables rather than a single table with a `type` column driving all logic through conditionals.
- **Boundary Encapsulation:** Encapsulate all external service interactions (Stripe, SendGrid, Twilio) behind interface abstractions in Edge Functions. Business logic calls `paymentService.charge()`, not `fetch('https://api.stripe.com/...')`. This isolates vendor coupling and simplifies testing.

## Objects & Data Structures

- **Immutable Data Patterns:** Use database constraints and RLS policies to enforce immutability where appropriate. Audit logs, financial transactions, and event records should be append-only (`INSERT` allowed, `UPDATE` and `DELETE` denied via RLS). Use `GENERATED ALWAYS AS` columns for derived fields.
- **Small, Focused Tables:** Design tables around a single entity or concept. A table that stores user profile data, subscription details, billing address, and notification preferences should be normalized into separate tables. Each table should have a clear purpose and a small number of columns.
- **Database Views as Contracts:** Use PostgreSQL views to present stable, curated interfaces to the client. The underlying table schema can evolve (add columns, restructure) while the view maintains a consistent shape for the PostgREST API. Grant client roles access to views, not raw tables, for sensitive data.
- **Repository Pattern on the Client:** Abstract Supabase client calls behind repository classes or modules. The repository handles table references, query construction, type conversion, and caching. UI components depend on the repository interface, never on raw `supabase.from()` calls.

## Supabase-Specific Architecture

- **Layered Architecture:**
  ```
  Client App
    ├── UI Layer (components, pages)
    ├── State Management (providers, stores)
    ├── Repository Layer (data access abstraction)
    └── Supabase Client SDK

  Supabase Backend
    ├── Edge Functions (server-side logic, external API calls)
    ├── Database Functions (business logic, computed data)
    ├── RLS Policies (authorization)
    ├── Triggers (reactive side effects)
    ├── Views (API contracts, data shaping)
    └── Tables (data storage)
  ```
- **Database-Centric Architecture:** Leverage PostgreSQL as the application server, not just a data store. Use database functions for business logic, triggers for reactive behavior, RLS for authorization, and views for API shaping. Edge Functions handle what the database cannot: external API calls, file processing, and complex orchestration.
- **Schema Organization:** Use PostgreSQL schemas to organize database objects by domain:
  ```sql
  -- Public schema: client-facing tables and views (exposed via PostgREST)
  CREATE TABLE public.user_profiles (...);
  CREATE VIEW public.order_summaries AS ...;

  -- Private schema: internal tables, functions, triggers (not exposed via API)
  CREATE TABLE private.audit_logs (...);
  CREATE FUNCTION private.process_payment(...);

  -- Auth schema: managed by Supabase Auth (do not modify directly)
  -- Storage schema: managed by Supabase Storage
  ```
- **RLS Policy Architecture:** Structure RLS policies by access pattern. Group policies logically:
  ```sql
  -- Ownership policies
  CREATE POLICY "users_read_own" ON user_profiles FOR SELECT USING (auth.uid() = id);
  CREATE POLICY "users_update_own" ON user_profiles FOR UPDATE USING (auth.uid() = id);

  -- Role-based policies
  CREATE POLICY "admins_read_all" ON user_profiles FOR SELECT USING (is_admin());
  CREATE POLICY "admins_update_all" ON user_profiles FOR UPDATE USING (is_admin());

  -- Public access policies
  CREATE POLICY "public_read_published" ON blog_posts FOR SELECT USING (status = 'published');
  ```
- **Edge Function Architecture:**
  ```
  supabase/functions/
    _shared/                  (shared modules: auth, validation, errors, types)
      cors.ts
      auth.ts
      validation.ts
      supabase-client.ts
    send-welcome-email/
      index.ts
    process-payment/
      index.ts
    generate-report/
      index.ts
  ```
- **Separation of Public and Private APIs:** Use the `public` schema for tables and views exposed to the client via PostgREST. Use a `private` or `internal` schema for tables and functions that should only be accessed by Edge Functions, triggers, or the `service_role`. Revoke `anon` and `authenticated` role access to private schemas.
- **Event-Driven Patterns:** Design the system as a chain of database events: client inserts a row -> trigger validates and enriches -> webhook fires Edge Function -> Edge Function calls external API -> result written back to database -> Realtime pushes update to client. Each step is independently testable and deployable.
