Get the FREE Ultimate OpenClaw Setup Guide →

db

Scanned
npx machina-cli add skill mrsknetwork/supernova/db --openclaw
Files (1)
SKILL.md
7.7 KB

Database Engineering

Default Stack (Ask First)

Before applying anything, ask:

"Can I use the Supernova database stack? PostgreSQL 16 via SQLAlchemy 2.0 async for server-side, and RxDB v15 for client-side offline-first apps. Or do you have an existing database setup I should match?"

If a DATABASE_URL is already in .env, or an existing migration history exists, detect and match it.

Progressive Disclosure

  • Load references/postgres-advanced.md for complex scenarios: JSONB indexing, full-text search, row-level security, CTEs.
  • Load references/rxdb.md for offline-first client schema, replication protocol, and conflict resolution setup.

SOP: PostgreSQL + SQLAlchemy 2.0 Async

Step 1 - SQLAlchemy ORM Model Conventions

# models/base.py
from sqlalchemy.orm import DeclarativeBase, MappedColumn, mapped_column
from sqlalchemy import func
from uuid import UUID, uuid4
from datetime import datetime

class Base(DeclarativeBase):
    pass

# models/user.py
from sqlalchemy.orm import Mapped
from sqlalchemy import String, DateTime, text

class User(Base):
    __tablename__ = "users"

    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4, server_default=text("gen_random_uuid()"))
    email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False, index=True)
    display_name: Mapped[str] = mapped_column(String(100), nullable=False)
    hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)

Model Conventions:

  • Every table has id (UUID PK), created_at, updated_at as standard columns.
  • Use Mapped[type] and mapped_column() (SQLAlchemy 2.0 style). Never use the 1.x Column() style.
  • FK column naming: {entity}_id (e.g., user_id, order_id).
  • Use String with explicit max lengths, not Text, unless unbounded text is genuinely needed.

Step 2 - Relationships

from sqlalchemy.orm import relationship

class User(Base):
    # ...
    orders: Mapped[list["Order"]] = relationship("Order", back_populates="user", cascade="all, delete-orphan")

class Order(Base):
    # ...
    user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
    user: Mapped["User"] = relationship("User", back_populates="orders")

Always define ondelete behavior on FKs. CASCADE for child data, SET NULL for optional references, RESTRICT (default) when the parent must not be deleted while children exist.

Step 3 - Alembic Migration Workflow

# 1. Generate migration (always auto-generate, never write migrations manually)
alembic revision --autogenerate -m "add_users_table"

# 2. Review the generated file in alembic/versions/. Always read it before applying.
# Check: are the correct columns added? Is upgrade() reversible by downgrade()?

# 3. Apply to development
alembic upgrade head

# 4. Apply to production (in CI/CD, not manually)
alembic upgrade head

Migration rules:

  • Every upgrade() must have a working downgrade().
  • Never edit a migration that has been applied in production. Create a new migration instead.
  • Migration filenames auto-generate a hash. Add a descriptive suffix: 2024_add_users_table.py.

Step 4 - Index Strategy

Add indexes based on actual query patterns. The default indexes to add:

ScenarioIndex TypeExample
FK column (always)B-Treeindex=True on user_id FK
Email lookup / unique constraintB-Treeunique=True on email
Status column with low cardinalityPartial B-TreeCREATE INDEX ON orders (status) WHERE status = 'pending'
Full-text search on title/descriptionGINCREATE INDEX ON articles USING GIN (to_tsvector('english', title))
JSONB field lookupGINCREATE INDEX ON products USING GIN (metadata jsonb_path_ops)

Never add indexes blindly on every column. Indexes slow down writes. Add them where EXPLAIN ANALYZE shows a sequential scan on a table with >10k rows.

Step 5 - Repository Pattern

# repositories/user_repo.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from uuid import UUID

class UserRepository:
    async def get_by_id(self, db: AsyncSession, user_id: UUID) -> User | None:
        result = await db.execute(select(User).where(User.id == user_id))
        return result.scalar_one_or_none()

    async def get_by_email(self, db: AsyncSession, email: str) -> User | None:
        result = await db.execute(select(User).where(User.email == email))
        return result.scalar_one_or_none()

    async def create(self, db: AsyncSession, user: User) -> User:
        db.add(user)
        await db.commit()
        await db.refresh(user)
        return user

All DB queries live exclusively in repository files. Services call repositories. Routers never import AsyncSession or select.

Step 6 - Query Anti-Patterns to Avoid

  • SELECT * - always specify columns or use the ORM model to fetch only what is needed.
  • Calling session.execute() in a loop - this produces N+1 queries. Use selectinload() or joinedload() for relationships.
  • Implicit type casting in WHERE clauses (e.g., comparing UUID FK to a string) - will cause full table scans.
  • Not wrapping multi-statement operations in a transaction.

SOP: RxDB v15 (Client-Side Offline-First)

Use RxDB when the frontend application needs to work offline, sync data on reconnect, or provide an instant-response local-first experience.

Step 1 - Schema Definition

// db/schemas/user.schema.ts
import { RxJsonSchema } from "rxdb";

export const UserSchema: RxJsonSchema<{ id: string; email: string; displayName: string }> = {
  version: 0,
  primaryKey: "id",
  type: "object",
  properties: {
    id: { type: "string", maxLength: 36 },
    email: { type: "string" },
    displayName: { type: "string" },
  },
  required: ["id", "email", "displayName"],
};

version starts at 0 and increments on schema changes. Breaking schema changes require a migration strategy.

Step 2 - Database Initialization

// db/index.ts
import { createRxDatabase, addRxPlugin } from "rxdb";
import { getRxStorageDexie } from "rxdb/plugins/storage-dexie";
import { RxDBDevModePlugin } from "rxdb/plugins/dev-mode";

if (process.env.NODE_ENV !== "production") addRxPlugin(RxDBDevModePlugin);

export async function createDb() {
  const db = await createRxDatabase({ name: "myapp", storage: getRxStorageDexie() });
  await db.addCollections({ users: { schema: UserSchema } });
  return db;
}

Step 3 - Replication with Backend

import { replicateRxCollection } from "rxdb/plugins/replication";

replicateRxCollection({
  collection: db.users,
  replicationIdentifier: "user-sync",
  pull: {
    async handler(checkpointOrNull, batchSize) {
      const res = await fetch(`/api/v1/sync/users?checkpoint=${checkpointOrNull}&limit=${batchSize}`);
      return res.json(); // { documents: [], checkpoint: ... }
    },
  },
  push: {
    async handler(rows) {
      await fetch("/api/v1/sync/users", { method: "POST", body: JSON.stringify(rows) });
      return [];
    },
  },
});

The sync endpoint on the FastAPI backend must implement the RxDB checkpoint replication protocol.

Source

git clone https://github.com/mrsknetwork/supernova/blob/main/skills/db/SKILL.mdView on GitHub

Overview

This skill designs relational database schemas, writes optimized SQL, and manages Alembic migrations for PostgreSQL. It also configures RxDB for offline-first client apps and emphasizes confirming the existing stack before applying defaults.

How This Skill Works

It uses SQLAlchemy 2.0 async with DeclarativeBase and mapped_column to model tables and columns, enforcing consistent id, created_at, and updated_at fields. It defines relationships with explicit ondelete behavior and follows an Alembic-driven migration workflow (auto-generated migrations, review, and upgrade in dev and production). It also outlines index strategies driven by actual query patterns.

When to Use It

  • Designing new relational schemas and migration plans
  • Optimizing SQL queries and indexing for performance
  • Managing Alembic migrations in CI/CD
  • Configuring RxDB for offline-first client apps and replication
  • Verifying and matching the existing database stack before defaults

Quick Start

  1. Step 1: Define base model with DeclarativeBase and mapped_column
  2. Step 2: Create relationships with ondelete and back_populates
  3. Step 3: Generate migrations with alembic revision --autogenerate, review, then apply in dev and prod

Best Practices

  • Follow model conventions: id (UUID PK), created_at, updated_at on all tables, using Mapped[type] and mapped_column
  • Define ondelete behavior for all FKs (CASCADE, SET NULL, or RESTRICT)
  • Always run alembic revision --autogenerate and review the file before applying
  • Minimize downtime by testing migrations in a dev environment first
  • Add indexes based on actual query patterns, starting with FK columns and unique constraints

Example Use Cases

  • Users table with UUID PK, email, and timestamp columns
  • User–Order relation using user_id FK with CASCADE on delete
  • Alembic workflow: autogenerate, review, upgrade in dev, then prod
  • Index FK columns and unique constraints like email
  • RxDB client schema setup for offline-first apps with replication

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers