grey-haven-database-conventions
About
This skill provides database conventions for Grey Haven projects using Drizzle (TypeScript) and SQLModel (Python). It enforces snake_case field naming, multi-tenant architecture with tenant_id and RLS, proper indexing, and migration practices. Use it when designing schemas, writing database code, creating migrations, or setting up RLS policies.
Quick Install
Claude Code
Recommended/plugin add https://github.com/greyhaven-ai/claude-code-configgit clone https://github.com/greyhaven-ai/claude-code-config.git ~/.claude/skills/grey-haven-database-conventionsCopy and paste this command in Claude Code to install this skill
Documentation
Grey Haven Database Conventions
Database schema standards for Drizzle ORM (TypeScript) and SQLModel (Python).
Follow these conventions for all Grey Haven multi-tenant database schemas.
Supporting Documentation
- examples/ - Complete schema examples (all files <500 lines)
- drizzle-schemas.md - TypeScript/Drizzle examples
- sqlmodel-schemas.md - Python/SQLModel examples
- migrations.md - Migration patterns
- rls-policies.md - Row Level Security
- reference/ - Detailed references (all files <500 lines)
- field-naming.md - Naming conventions
- indexing.md - Index patterns
- relationships.md - Foreign keys and relations
- templates/ - Copy-paste schema templates
- checklists/ - Schema validation checklists
Critical Rules
1. snake_case Fields (ALWAYS)
Database columns MUST use snake_case, never camelCase.
// ✅ CORRECT
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
created_at: timestamp("created_at").defaultNow().notNull(),
tenant_id: uuid("tenant_id").notNull(),
email_address: text("email_address").notNull(),
});
// ❌ WRONG - Don't use camelCase
export const users = pgTable("users", {
createdAt: timestamp("createdAt"), // WRONG!
tenantId: uuid("tenantId"), // WRONG!
});
2. tenant_id Required (Multi-Tenant)
Every table MUST include tenant_id for data isolation.
// TypeScript - Drizzle
export const organizations = pgTable("organizations", {
id: uuid("id").primaryKey().defaultRandom(),
tenant_id: uuid("tenant_id").notNull(), // REQUIRED
name: text("name").notNull(),
});
# Python - SQLModel
class Organization(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
tenant_id: UUID = Field(foreign_key="tenants.id", index=True) # REQUIRED
name: str = Field(max_length=255)
See examples/drizzle-schemas.md and examples/sqlmodel-schemas.md for complete examples.
3. Standard Timestamps
All tables must have created_at and updated_at.
// TypeScript - Reusable timestamps
export const baseTimestamps = {
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
};
export const teams = pgTable("teams", {
id: uuid("id").primaryKey().defaultRandom(),
...baseTimestamps, // Spread operator
tenant_id: uuid("tenant_id").notNull(),
name: text("name").notNull(),
});
# Python - Mixin pattern
class TimestampMixin:
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow, sa_column_kwargs={"onupdate": datetime.utcnow})
class Team(TimestampMixin, SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
tenant_id: UUID = Field(index=True)
name: str = Field(max_length=255)
4. Row Level Security (RLS)
Enable RLS on all tables with tenant_id.
-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- Tenant isolation policy
CREATE POLICY "tenant_isolation" ON users
FOR ALL TO authenticated
USING (tenant_id = (current_setting('request.jwt.claims')::json->>'tenant_id')::uuid);
See examples/rls-policies.md for complete RLS patterns.
Quick Reference
Field Naming Patterns
Boolean fields: Prefix with is_, has_, can_
is_active: boolean("is_active")
has_access: boolean("has_access")
can_edit: boolean("can_edit")
Timestamp fields: Suffix with _at
created_at: timestamp("created_at")
updated_at: timestamp("updated_at")
deleted_at: timestamp("deleted_at")
last_login_at: timestamp("last_login_at")
Foreign keys: Suffix with _id
tenant_id: uuid("tenant_id")
user_id: uuid("user_id")
organization_id: uuid("organization_id")
See reference/field-naming.md for complete naming guide.
Indexing Patterns
Always index:
tenant_id(for multi-tenant queries)- Foreign keys (for joins)
- Unique constraints (email, slug)
- Frequently queried fields
// Composite index for tenant + lookup
export const usersIndex = index("users_tenant_email_idx").on(
users.tenant_id,
users.email_address
);
See reference/indexing.md for index strategies.
Relationships
One-to-many:
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts), // User has many posts
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.user_id], references: [users.id] }),
}));
See reference/relationships.md for all relationship patterns.
Drizzle ORM (TypeScript)
Installation:
bun add drizzle-orm postgres
bun add -d drizzle-kit
Basic schema:
// db/schema.ts
import { pgTable, uuid, text, timestamp, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
tenant_id: uuid("tenant_id").notNull(),
email_address: text("email_address").notNull().unique(),
is_active: boolean("is_active").default(true).notNull(),
});
Generate migration:
bun run drizzle-kit generate:pg
bun run drizzle-kit push:pg
See examples/migrations.md for migration workflow.
SQLModel (Python)
Installation:
pip install sqlmodel psycopg2-binary
Basic model:
# app/models/user.py
from sqlmodel import Field, SQLModel
from uuid import UUID, uuid4
from datetime import datetime
class User(SQLModel, table=True):
__tablename__ = "users"
id: UUID = Field(default_factory=uuid4, primary_key=True)
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
email_address: str = Field(unique=True, index=True, max_length=255)
is_active: bool = Field(default=True)
Generate migration:
alembic revision --autogenerate -m "Add users table"
alembic upgrade head
See examples/migrations.md for Alembic setup.
When to Apply This Skill
Use this skill when:
- ✅ Designing new database schemas
- ✅ Creating Drizzle or SQLModel models
- ✅ Writing database migrations
- ✅ Setting up RLS policies
- ✅ Adding indexes for performance
- ✅ Defining table relationships
- ✅ Reviewing database code in PRs
- ✅ User mentions: "database", "schema", "Drizzle", "SQLModel", "migration", "RLS", "tenant_id", "snake_case"
Template References
- TypeScript:
cvi-template(Drizzle ORM + PlanetScale) - Python:
cvi-backend-template(SQLModel + PostgreSQL)
Critical Reminders
- snake_case - ALL database fields use snake_case (never camelCase)
- tenant_id - Required on all tables for multi-tenant isolation
- Timestamps - created_at and updated_at on all tables
- RLS policies - Enable on all tables with tenant_id
- Indexing - Index tenant_id, foreign keys, and unique fields
- Migrations - Always use migrations (Drizzle Kit or Alembic)
- Field naming - Booleans use is_/has_/can_ prefix, timestamps use _at suffix
- No raw SQL - Use ORM for queries (prevents SQL injection)
- Soft deletes - Use deleted_at timestamp, not hard deletes
- Foreign keys - Always define relationships explicitly
Next Steps
- Need examples? See examples/ for Drizzle and SQLModel schemas
- Need references? See reference/ for naming, indexing, relationships
- Need templates? See templates/ for copy-paste schema starters
- Need checklists? Use checklists/ for schema validation
GitHub Repository
Related Skills
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
Algorithmic Art Generation
MetaThis skill helps developers create algorithmic art using p5.js, focusing on generative art, computational aesthetics, and interactive visualizations. It automatically activates for topics like "generative art" or "p5.js visualization" and guides you through creating unique algorithms with features like seeded randomness, flow fields, and particle systems. Use it when you need to build reproducible, code-driven artistic patterns.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
