Overview
The @trendingsociety/db package provides a type-safe Supabase client with auto-generated types from the database schema.
Types are auto-generated from the 127-table schema. Run pnpm db:generate-types after schema changes.
Installation
pnpm add @trendingsociety/db
Quick Start
import { createClient, createServiceClient } from '@trendingsociety/db';
// Client-side (respects RLS)
const supabase = createClient();
// Server-side (bypasses RLS)
const adminSupabase = createServiceClient();
API Reference
createClient()
Creates a Supabase client for client-side use. Respects RLS policies.
import { createClient } from '@trendingsociety/db';
const supabase = createClient();
const { data, error } = await supabase
.from('content')
.select('*');
createServiceClient()
Creates a Supabase client with service role. Bypasses all RLS.
Only use server-side. Never expose to client. Never import in client components.
import { createServiceClient } from '@trendingsociety/db';
const supabase = createServiceClient();
// Can access all data, any tenant
const { data } = await supabase
.from('system.api_keys')
.select('*');
createServerClient(cookieStore)
Creates a server-side client for Next.js App Router.
import { createServerClient } from '@trendingsociety/db';
import { cookies } from 'next/headers';
export async function getData() {
const cookieStore = await cookies();
const supabase = createServerClient(cookieStore);
const { data } = await supabase
.from('content')
.select('*');
return data;
}
Type Exports
// Generated from schema
import type { Database } from '@trendingsociety/db';
import type { Tables, TablesInsert, TablesUpdate } from '@trendingsociety/db';
// Specific table types
type Content = Tables<'content'>;
type ContentInsert = TablesInsert<'content'>;
type ContentUpdate = TablesUpdate<'content'>;
Query Patterns
Basic Select
const { data, error } = await supabase
.from('content')
.select('*')
.eq('status', 'published')
.order('created_at', { ascending: false })
.limit(10);
With Relations
const { data } = await supabase
.from('publisher_posts')
.select(`
id,
title,
vertical:publisher_verticals(name, slug),
analytics:publisher_post_analytics(page_views, revenue)
`)
.eq('status', 'published');
Insert
const { data, error } = await supabase
.from('content')
.insert({
title: 'New Article',
slug: 'new-article',
tenant_id: tenantId // Required for tenant-scoped tables
})
.select()
.single();
Upsert
const { data, error } = await supabase
.from('content')
.upsert({
id: existingId, // If exists, update; otherwise insert
title: 'Updated Title'
})
.select()
.single();
RPC (Stored Functions)
const { data, error } = await supabase.rpc('calculate_metrics', {
p_tenant_id: tenantId,
p_start_date: '2025-01-01'
});
Multi-Tenant Queries
Always ensure tenant context is set. RLS policies filter data automatically, but explicit tenant_id is safer.
// Recommended: explicit tenant filter
const { data } = await supabase
.from('content')
.select('*')
.eq('tenant_id', tenantId);
// Also works: RLS handles it (but less explicit)
const { data: rlsFiltered } = await supabase
.from('content')
.select('*');
Schema Access
Access different schemas:
// Public schema (default)
supabase.from('content').select()
// System schema (service role only)
supabase.schema('system').from('api_keys').select()
// Events schema (service role only)
supabase.schema('events').from('outbox').select()
Type Generation
Regenerate types after schema changes:
pnpm --filter @trendingsociety/db generate-types
# or
pnpm db:generate-types
This updates packages/db/src/types/database.types.ts.