Database Conventions
SkillDatabases & dataApply when writing or modifying database schema, queries, migrations, transactions, or tenant-scoped persistence.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Database Conventions skill
What this skill tells your AI
The instructions your AI receives, as published by stella/stella in .agents/skills/conventions-db/SKILL.md and read by ahel’s review.
Apply when writing or modifying database schema, queries, migrations, transactions, or tenant-scoped persistence.
Schema
- Schema lives under
apps/api/src/db/schema/; read the owning slice and related foreign keys before editing. - For closed persisted domain values, define one named
as constvalue list and pass it to Drizzle withtext({ enum: VALUES }). Add a databaseCHECKwhen invalid values could compromise lifecycle, authorization, audit, or workflow invariants; Drizzle's enum option is compile-time-only. - Reserve
.$type<T>()for branded or structured types. Use a native PostgreSQL enum only when the value set is genuinely permanent. - Use cascade deletes for workspace-owned dependants and restrict deletes for shared file references. Trace the full FK graph before multi-resource deletion.
- Timestamp columns use the
timestamptzhelper fromapps/api/src/db/columns.ts. Never introduce a naive PostgreSQL timestamp or::timestampcast without explicitly anchoring its time zone. - Add indexes for columns used in
WHERE,ORDER BY, orJOIN; lead composite indexes with the tenant-scoping columns. Treat changes to large tables as lock-sensitive.
Migrations
bun --filter @stll/api db:migrateis the shipped migration path used by CI and deployment.db:pushis a local declarative schema-diff tool; it does not replace committed migrations and must not be described as the deployment path.- Schema changes remain additive across a rollout: add, deploy compatible reads/writes, backfill in bounded batches, switch, then remove the old shape in a later release.
- Migrations run before new API tasks finish rolling out. Old tasks must remain compatible with the migrated schema, and a failed rollout must have a safe forward-fix path.
- Keep irreversible schema operations out of the same release as risky
application changes. Destructive, bulk-backfill, and access-control SQL
requires a statement-scoped acknowledgement enforced by
scripts/check-migration-safety.ts, placed in the comment block directly above the statement:-- stella-migration-safety: reviewed <rule-id> - <why this is safe>. An acknowledgement that clears nothing is an error. Every migration setslock_timeoutandstatement_timeoutfirst. - For large live tables, follow the repository's guarded concurrent-index
protocol: either split and reopen the migrator transaction exactly as
enforced by
migration-concurrent-index.test.ts, or put repairable work inonline-migrations.ts. Keep long backfills outside schema migrations and checkpoint them durably. - Validate migration history two ways: apply every committed migration to a
fresh database, then confirm
bun --filter @stll/api db:push -- --explainreports no schema drift. Do not repair drift by resetting a shared database.
Tenant Scope and Queries
- Workspace data uses the authorized
scopedDbsupplied by safe handlers so PostgreSQL RLS and query-level scope reinforce each other. Raw/root database access needs a demonstrated system-level reason and a deny-by-default RLS posture. - Ownership IDs come from server-validated context, never request bodies. Keep tenant predicates in the database query even when a preceding authorization check exists.
- Prefer Drizzle's relational query API for ordinary relation reads. Use SQL-like syntax for cross-table filtering, aggregation, locking, unions, or mutations where it expresses the invariant more directly.
- Every list query uses a bounded
limitand cursor and returns the standardPage<T>envelope fromapps/api/src/lib/pagination.ts. Offset pagination,totalCount, and unboundedfindManyrequire explicit justification. - Do not filter unindexed JSONB in request paths. Fetch through indexed tenant columns, then narrow structured content with a type guard rather than a cast.
- Batch relation reads and writes. Never issue a query per item when a join,
relation preload,
INquery, or bulk mutation can express the same work.
Concurrency and Transactions
- Keep transactions short; perform S3, network, conversion, and other external I/O outside them.
- Close every read-decide-write race. Lock the decisive row with
SELECT ... FOR UPDATE, or encode the expected state/version in the mutationWHEREclause and check the affected-row count. - Make retries converge. Stable identities, unique constraints, conditional transitions, and idempotency keys are stronger than read-before-insert checks.
- Preserve lock order across call sites. When multiple resources must be locked, define and reuse a deterministic ordering to avoid deadlocks.
Verification
Test behavior that schema inference cannot prove: cross-tenant denial, concurrent transitions, replay/idempotence, migration parity, destructive delete ordering, and cursor stability under inserts. Prefer invariant or integration tests over mocked query-shape tests.
Signals
- GitHub stars
- 240
- Forks
- 51
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
conventions-db- Source
- github.com/stella/stella