| name | database-skill |
| description | Implement database-related changes in this repository, including schema changes via TypeORM entities, repository/query patterns, request-context transactions, DB tests, and explicit data migrations. Use when working on DB schema updates, repositories or *Db classes, SQL queries, transactions, data backfills, or app logic that reads or writes MySQL data. |
Database Workflow
Apply this skill any time work involves database schema, queries, repositories, or migrations.
Environment
- Assume MySQL 8+ in all target environments.
- Assume Aurora MySQL in staging/production.
- Assume Docker MySQL locally.
Schema Changes
- Implement schema changes through TypeORM entities in
src/entities.
- Keep entity file naming pattern:
- file name prefixed with
I, example: IMyThing.ts
- class name without
I and suffixed with Entity, example: MyThingEntity
- Export every new entity from
src/entities/entities.ts.
- Treat edits to existing entities as high risk and check for possible data loss before changing types or columns.
- Never use foreign keys.
- Prefer UUID primary keys unless sequential IDs are strictly required.
- Prefer
bigint Unix epoch milliseconds for time fields over SQL datetime/date.
- For every new
@Entity(TABLE_NAME), add or use a table constant in src/constants/db-tables.ts.
- Keep entity class/file names singular and table names plural.
Queries And Repositories
- Isolate DB access inside repository-style classes, usually
*Repository or *Db.
- Use caller-level transactions only when work must span multiple repositories:
await sqlExecutor.executeNativeQueriesInTransaction(async (connection) => {
const txCtx: RequestContext = { ...ctx, connection };
await firstRepository.doWork(txCtx);
await secondRepository.doMoreWork(txCtx);
});
- Make
ctx: RequestContext the last argument of new repository functions unless the surrounding class has an established incompatible pattern.
- Time new repository functions:
const timerName = `${this.constructor.name}->methodName`;
try {
ctx.timer?.start(timerName);
} finally {
ctx.timer?.stop(timerName);
}
- Use
ctx.connection when present so operations participate in caller-provided transactions:
const rows = await this.db.execute<MyEntity>(
`select * from ${MY_TABLE} where id = :id`,
{ id },
ctx.connection ? { wrappedConnection: ctx.connection } : undefined
);
For TypeORM transaction blocks, obtain repositories from the transaction manager passed by the transaction callback instead of the global data source.
- Never use generated
Api* classes in repositories.
- Allow callers to use entity classes and repository-defined types.
- Use constants from
src/constants/db-tables.ts instead of hardcoded table names whenever possible.
- Prefer typed queries via
execute<T>() and oneOrNull<T>().
- Always pass an explicit comparator to
Array.prototype.sort, including string sorts.
Data Migrations
- Use db-migrate only for explicit data migrations or view/one-off changes requested by the user.
- Avoid db-migrate for schema changes unless the user explicitly asks for a migration; schema/table changes should normally come from TypeORM entities and
dbMigrationsLoop sync.
- Create migrations with:
npm run migrate:new migration-name
- Edit files created under
migrations/.
- Delete the generated
.down.sql file and leave exports.down present as a no-op; do not implement revert logic.
Tests
Place DB/repository tests next to the file under test and name them with lowercase hyphenated words ending in .test.ts. For DB integration patterns, follow src/profiles/abusiveness-check.db.test.ts.
Validation