Skip to content
Back to skills

Hamster Database

ASecurity

Supabase database patterns for Hamster Studio. Use when working with schemas, migrations, RLS policies, SQL functions, or database queries. Essential for any database-related work.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 27, 2026
databasestypescriptbashsqlexpresstestingdatabasesecurity

Works with

  • cli

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned September 27, 2026

npx -y skills add gethamster/skills --skill hamster-database --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hamster Database?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hamster Database
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gethamster-hamster-database/badge)](https://www.skillsdirectory.com/skills/gethamster-hamster-database)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hamster-database
description: "Supabase database patterns for Hamster Studio. Use when working with schemas, migrations, RLS policies, SQL functions, or database queries. Essential for any database-related work."
version: "1.1.0"
---

# Hamster Database Patterns

## Overview

Database conventions for the Hamster Studio multi-tenant SaaS. Covers Supabase schema design, RLS policies, migrations, and SQL functions.

## When to Apply

- Creating or modifying database tables
- Writing RLS policies
- Creating database functions or triggers
- Running migrations
- Writing SQL queries

## Quick Reference

| Task | Pattern | Reference |
| --- | --- | --- |
| Create table | Enable RLS, add `account_id`, use triggers | `references/rules/schema-patterns.md` |
| Add RLS policy | Use helper functions, test all operations | `references/rules/rls-policies.md` |
| Create migration | Use `supabase:db:diff`, never copy manually | `references/rules/migration-workflow.md` |
| Write function | SECURITY INVOKER, set `search_path = ''` | `references/rules/sql-functions.md` |
| SQL style | Lowercase keywords, snake_case names | `references/rules/sql-style-guide.md` |

## Core Architecture

### Multi-Tenant Data Model

```
accounts (personal + team)
    ↓ account_id FK
feature_tables (all user data)
    ↓ RLS policies
has_role_on_account() / has_permission()
```

**Rule**: ALL feature data MUST have `account_id` foreign key to `accounts`.

### RLS Helper Functions

| Function | Use Case | Example |
| --- | --- | --- |
| `has_role_on_account(account_id)` | Team membership | SELECT, basic access |
| `has_permission(user_id, account_id, permission)` | Specific capability | INSERT, UPDATE, DELETE |
| `is_account_owner(account_id)` | Owner-only actions | Destructive operations |

### Standard Table Template

```sql
create table if not exists public.my_feature (
  id uuid unique not null default extensions.uuid_generate_v4(),
  account_id uuid references public.accounts(id) on delete cascade not null,
  created_at timestamptz default now() not null,
  updated_at timestamptz default now() not null,
  created_by uuid references auth.users(id),
  updated_by uuid references auth.users(id),
  -- feature columns
  primary key (id)
);

-- CRITICAL: Always enable RLS
alter table public.my_feature enable row level security;

-- Revoke defaults, grant specific
revoke all on public.my_feature from authenticated, service_role;
grant select, insert, update, delete on public.my_feature to authenticated;

-- Add standard triggers
create trigger set_timestamps
  before insert or update on public.my_feature
  for each row execute function public.trigger_set_timestamps();

create trigger set_user_tracking
  before insert or update on public.my_feature
  for each row execute function public.trigger_set_user_tracking();
```

## Migration Workflow (CRITICAL)

**ALWAYS** use the diff tool, never copy schema files:

```bash
# 1. Create/edit schema file
apps/web/supabase/schemas/NN-my-feature.sql

# 2. Reset database to apply schemas
pnpm supabase:web:reset

# 3. Generate migration from diff
pnpm --filter web supabase:db:diff -f my-feature-name

# 4. Generate TypeScript types
pnpm supabase:web:typegen
```

> Migration filenames follow `YYYYMMDDHHmmss_description.sql` in UTC. `db:diff` writes the prefix automatically; if a CI ordering check rejects your migration, rename with `date -u +%Y%m%d%H%M%S` (the `-u` flag is required). See `references/rules/migration-workflow.md`.

## RLS Policy Patterns

### Personal + Team Access (Most Common)

```sql
create policy "read" on public.my_feature for select
  to authenticated using (
    account_id = (select auth.uid()) or
    public.has_role_on_account(account_id)
  );
```

### Permission-Based Write

```sql
create policy "write" on public.my_feature for insert
  to authenticated with check (
    public.has_permission(auth.uid(), account_id, 'feature.manage'::app_permissions)
  );
```

### UPDATE Requires Both USING and WITH CHECK

```sql
create policy "update" on public.my_feature for update
  to authenticated
  using (public.has_permission(auth.uid(), account_id, 'feature.manage'::app_permissions))
  with check (public.has_permission(auth.uid(), account_id, 'feature.manage'::app_permissions));
```

## Function Security

```sql
create or replace function public.my_function(param uuid)
returns setof public.my_table
language plpgsql
security invoker  -- Inherit caller's RLS (preferred)
set search_path = ''  -- Prevent injection
as $$
begin
  return query select * from public.my_table where id = param;
end;
$$;

grant execute on function public.my_function(uuid) to authenticated;
```

**Rule**: Use `security definer` ONLY when bypassing RLS is necessary, with explicit auth checks.

## Data Access Patterns

Choose the right approach: **Queries** (default), **Views** (computed), or **Functions** (complex).

| Pattern | Use When | Maintenance Cost |
| --- | --- | --- |
| **Query** | Simple CRUD, FK relations, filters | Low (code change) |
| **View** | Read-only aggregations, UNIONs | Medium (migration) |
| **Function** | Atomic transactions, complex joins, business logic | High (migration) |

### Use Supabase Queries (Default)

Queries live in code, are easy to modify, and handle FK relationships automatically.

```typescript
// FK relationships work - no function needed
const { data } = await client
  .from('brief')
  .select(`*, document:document!inner(*)`)
  .eq('account_id', accountId);

// Join through FK for related data
const { data } = await client
  .from('task_dependencies')
  .select(`
    depends_on_task_id,
    tasks!task_dependencies_depends_on_task_id_fkey (id, title, status)
  `)
  .eq('task_id', taskId);
```

### Use Views

For read-only computed data, UNIONs, and pre-joined data.

> :warning: **CRITICAL: Views and RLS**
> 
> Views run with the privileges of the view *creator* by default, **bypassing RLS** on underlying tables. This is a major security risk that has caused data leaks.
> 
> **ALWAYS** use `security_invoker = true` on views to inherit the *caller's* RLS policies.

```sql
-- UNION of multiple sources
-- MUST use security_invoker to enforce RLS on underlying tables
create view public.waitlist_merged with (security_invoker = true) as
select ... from waitlist
union all
select ... from accounts where not exists (...);

-- Pre-joined workspace data
create view public.user_accounts with (security_invoker = true) as
select account.id, account.name, membership.account_role
from accounts account
join accounts_memberships membership on account.id = membership.account_id
where membership.user_id = auth.uid();
```

**Why `security_invoker = true` is mandatory:**

- Without it: View runs as creator → RLS bypassed → data leak
- With it: View runs as caller → RLS enforced → secure

### Use Functions

For atomic transactions, complex multi-table joins, or business logic.

```sql
-- Atomic multi-table update (status + positions must succeed together)
create function public.change_status_and_reorder(
  p_account_id uuid, p_task_id uuid, p_new_status text,
  p_task_updates jsonb, p_updated_by uuid
) returns void as $$
begin
  update tasks set status = p_new_status::task_status where id = p_task_id;
  for rec in select * from jsonb_array_elements(p_task_updates) loop
    update tasks set position = (rec ->> 'position')::integer
    where id = (rec ->> 'taskId')::uuid;
  end loop;
end;
$$ language plpgsql;
```

Call functions with `.rpc()`:

```typescript
const { data } = await client.rpc('reorder_briefs', {
  p_account_id: accountId,
  p_brief_id: briefId,
  p_new_position: newPosition,
});
```

### Decision Checklist

1. Can Supabase query builder express this? → **Use query**
2. Is it read-only aggregation/UNION? → **Use view**
3. Needs atomic multi-table updates? → **Use function**
4. Needs recursive CTEs or complex joins? → **Use function**
5. Enforces business invariants? → **Use function (often as trigger)**

**Default to queries. Functions require migrations to change—avoid overusing them.**

## Testing

For database testing patterns, see the [hamster-database-testing](../hamster-database-testing/SKILL.md) skill. It covers:

- **Vitest** — RLS policies, constraints (via Supabase client, multi-user scenarios)
- **pgTAP** — Triggers, functions, schema assertions (via SQL)

## Key Files

| File | Purpose |
| --- | --- |
| `apps/web/supabase/schemas/` | Schema definitions (numbered) |
| `apps/web/supabase/migrations/` | Generated migrations |
| `apps/web/supabase/CLAUDE.md` | Detailed database workflow |
| `packages/supabase/src/database.types.ts` | Generated TypeScript types |

## References

- `references/rules/rls-policies.md` - RLS policy guidelines and examples
- `references/rules/migration-workflow.md` - Migration file conventions
- `references/rules/schema-patterns.md` - Table creation patterns
- `references/rules/sql-functions.md` - Database function patterns
- `references/rules/sql-style-guide.md` - SQL naming and formatting

Files in this skill

  • SKILL.md8.8 KB
  • references/rules/migration-workflow.md5.5 KB
  • references/rules/rls-policies.md8.1 KB
  • references/rules/schema-patterns.md4.4 KB
  • references/rules/sql-functions.md4.5 KB
  • references/rules/sql-style-guide.md4.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…