# Pathrule Pattern: Drizzle ORM (1.0.0)
# ::pathrule:package:drizzle-orm

### [RULE] Schema is the single source of truth  (path: /src/db/schema)
<!-- scope: folder | priority: high | strict -->

Treat the Drizzle TypeScript schema as the only place a table shape is defined.

- Edit column and table definitions in `src/db/schema`, then run `drizzle-kit generate` to produce the SQL migration.
- Never alter columns directly in the database or hand-edit a generated migration's SQL after it is committed.
- Export inferred types with `typeof users.$inferSelect` and `typeof users.$inferInsert` instead of redeclaring row shapes by hand.
- Define foreign keys, indexes, and constraints in the schema so `drizzle-kit` can diff and version them.

---

### [RULE] Generate then migrate, never push to production  (path: /drizzle)
<!-- scope: folder | priority: high | advisory -->

Use the `generate` + `migrate` workflow for anything that touches a shared or production database.

- Run `drizzle-kit generate` and commit the resulting file in `drizzle/` alongside the schema change in the same PR.
- Apply migrations with `drizzle-kit migrate` (or `migrate()` at deploy time), and reserve `drizzle-kit push` for throwaway local prototyping only.
- Hand-write the reverse SQL for destructive changes (dropping or retyping a column) and test it on staging first.
- Add a CI check that fails when a schema diff exists with no matching migration file, so drift cannot ship.

---

### [MEMORY] Drizzle versions and config baseline (2026)  (path: /src/db)

Pin to the current Drizzle line and use the dialect-based config introduced in the v1 betas.

- Runtime is `drizzle-orm` and the CLI is `drizzle-kit`; the stable line is the 0.4x releases with v1.0.0 in beta as of mid-2026, so confirm the exact pinned version in `package.json` before relying on v1-only APIs.
- `drizzle.config.ts` uses `dialect` (`"postgresql"`, `"mysql"`, or `"sqlite"`) plus `schema` and `out` paths; the older `driver` key is gone in the v1 config.
- Relations v2 centralizes relations: define them once with `defineRelations` in a dedicated file and pass that object to `drizzle()` instead of attaching `relations()` per table.
- Keep one shared `db` client instance per process; pass it (or a `tx`) into functions rather than constructing new connections per call.

---

### [MEMORY] Querying patterns: relations, transactions, prepared statements  (path: /src/db)

Prefer Drizzle's higher-level query APIs over hand-rolled joins and per-row loops.

- Load nested data with `db.query.users.findMany({ with: { posts: true } })` so related rows come back in one round trip instead of an N+1 loop.
- Wrap multi-statement writes in `db.transaction(async (tx) => { ... })` and use only `tx` inside; throwing rolls everything back, and nested calls become savepoints.
- For hot paths, build a prepared statement once with `.prepare()` and bind values via `sql.placeholder('name')`, then call `.execute({ name })`.
- Reach for the `sql` template tag for expressions Drizzle has no builder for, and keep user input as parameters rather than interpolated strings.

---

### [SKILL] drizzle-orm-review  (path: /)

---
name: drizzle-orm-review
description: Review a Drizzle ORM change before merge - schema source of truth, drizzle-kit migration hygiene, inferred types, relations, and safe transactions.
---

# Drizzle ORM review

- [ ] Schema change lives in `src/db/schema` and the database was not edited directly.
- [ ] A `drizzle-kit generate` migration is committed in `drizzle/` for this schema diff.
- [ ] `drizzle-kit push` is not used against any shared or production database.
- [ ] Destructive changes (drop or retype a column) have tested reverse SQL and a staging plan.
- [ ] Row types come from `$inferSelect` / `$inferInsert`, not hand-written interfaces.
- [ ] `drizzle.config.ts` sets `dialect`, `schema`, and `out` (no legacy `driver` key).
- [ ] Relations use `defineRelations` (relations v2) in one place, not per-table `relations()`.
- [ ] Nested reads use the relational query API or a single join, not per-row loops.
- [ ] Multi-statement writes run inside `db.transaction` and use only `tx` internally.
- [ ] Hot-path queries use `.prepare()` with `sql.placeholder` and user input stays parameterized.
