Skip to content

Commit 169821e

Browse files
committed
worklow
1 parent 8889351 commit 169821e

36 files changed

Lines changed: 2467 additions & 161 deletions
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
name: typescript-bun-drizzle-quality
3+
description: Build or review Bun fullstack TypeScript code with Drizzle-backed SQL. Use for backend or cross-layer changes touching API/domain logic, schema or query design, migrations, runtime/type debugging, and boundary validation between contracts, business rules, and persistence.
4+
---
5+
6+
# TypeScript Bun Drizzle Quality
7+
8+
Use this as an umbrella fullstack skill for Bun + TypeScript + Drizzle work.
9+
10+
For slop-reduction/refactor passes focused on deleting custom helpers/types/assertions, use
11+
[`../desloppify/SKILL.md`](../desloppify/SKILL.md) alongside this skill.
12+
13+
Deliver production-grade code by optimizing for:
14+
15+
- type safety and clarity
16+
- predictable runtime behavior in Bun
17+
- correct and reversible data changes with Drizzle
18+
- clean contract boundaries between API, domain logic, and persistence
19+
20+
## Repository Overrides (Required When Present)
21+
22+
Always follow repository-level agent rules (for example `AGENTS.md`) when they are stricter than this skill.
23+
24+
In this repository, enforce these defaults while using this skill:
25+
26+
- keep things in one function unless extraction is clearly reusable
27+
- avoid `try`/`catch` where practical; prefer explicit control flow
28+
- avoid `else`; prefer early returns
29+
- avoid `any` and broad assertions
30+
- prefer single-word names when possible
31+
- avoid unnecessary destructuring; prefer dot access
32+
- prefer functional array methods over loops when practical
33+
- prefer Bun-native APIs like `Bun.file()` where practical
34+
- run tests from package directories, not repository root, when root test is guarded
35+
36+
## Track Selection
37+
38+
Choose the track before coding:
39+
40+
1. App/API logic track
41+
- Use when changing handlers, services, orchestration, auth, or domain rules.
42+
2. Data layer track
43+
- Use when changing Drizzle schema, migrations, indexes, transactions, or query shape.
44+
3. Fullstack integration track
45+
- Use when changes cross boundaries (API contract + domain behavior + database effects).
46+
47+
If multiple tracks apply, run App/API first, then Data layer, then Integration checks.
48+
49+
## 1) Define Constraints First
50+
51+
Before coding, pin down:
52+
53+
- runtime: Bun version and package manager commands
54+
- database: SQL dialect (Postgres/MySQL/SQLite/Turso) and migration flow
55+
- API shape: request/response contracts and error model
56+
- compatibility: expected behavior changes vs strict backward compatibility
57+
58+
If context is missing, ask only for blocking details. Otherwise proceed with explicit assumptions.
59+
60+
## 2) Adapt to the Host Repository
61+
62+
Distill practices into the target repository instead of assuming fixed commands or structure.
63+
64+
Before implementation or review:
65+
66+
- inspect workspace scripts to discover quality gates (typecheck, lint/format, test)
67+
- inspect CI workflows to confirm which checks are required in pull requests
68+
- identify local test taxonomy and map commands to:
69+
- unit tests
70+
- integration tests
71+
- e2e tests
72+
- run only the smallest relevant subset for touched code paths
73+
74+
Do not assume naming conventions like `test:unit` or `test:e2e`; infer from scripts and workflow usage.
75+
76+
## 3) Fullstack Testing with Bun (Required)
77+
78+
Treat test design and test execution as first-class implementation work.
79+
80+
Use this decision flow:
81+
82+
1. classify repository test commands by behavior into unit, integration, and e2e
83+
2. map touched files/modules to the smallest package/service-local commands
84+
3. if root `test` is guarded (for example intentionally fails), do not run tests from root
85+
4. run required layers in order: unit -> integration -> e2e, based on scope/risk
86+
5. ensure CI-required checks for pull requests are represented in local verification
87+
6. if no explicit integration suite exists, treat boundary tests (API + real DB or equivalent) as integration coverage and call it out
88+
89+
Technical expectations for Bun repositories:
90+
91+
- use Bun test runner features intentionally (`bun test`, `--watch`, `--preload`, `--timeout`, path filters)
92+
- keep unit tests deterministic and fast; avoid network and global time randomness
93+
- for integration tests, run real migrations/schema setup and isolate test data
94+
- for e2e, run realistic app wiring and capture artifacts on failure
95+
96+
Do not mark work complete unless one of these is true:
97+
98+
- relevant tests were executed and results were recorded
99+
- execution was blocked by environment constraints and the block + residual risk were stated explicitly
100+
101+
For deeper guidance, read `references/testing-patterns.md`.
102+
For copyable boundary snippets, read `references/examples/integration-boundary-test.md`.
103+
104+
## 4) App/API Logic Track
105+
106+
Prefer:
107+
108+
- narrow, composable functions with explicit inputs and outputs
109+
- inferred types where clear, explicit types at boundaries (public functions, adapters, exported utilities)
110+
- discriminated unions for branching states
111+
- `unknown` + schema validation for untrusted input
112+
- early returns over deeply nested branching
113+
114+
Avoid:
115+
116+
- `any` unless unavoidable and scoped with justification
117+
- broad `as` assertions that bypass type checks
118+
- duplicated business logic between handlers, services, and tests
119+
120+
Use Zod (or equivalent) to enforce runtime contracts at process boundaries.
121+
For copyable handler patterns, read `references/examples/api-boundary.md`.
122+
123+
Use Bun-native patterns when they simplify runtime behavior:
124+
125+
- file I/O: `Bun.file`, `Bun.write`
126+
- scripting and process execution: Bun shell APIs
127+
- tests and fixtures: Bun test runner patterns
128+
129+
Keep Node compatibility shims only when required by dependencies or deployment targets.
130+
131+
## 5) Data Layer Track
132+
133+
When touching schema, models, or queries:
134+
135+
- keep naming stable and consistent across schema and code
136+
- preserve backward compatibility unless migration plan explicitly breaks it
137+
- create reversible migrations where possible
138+
- include indexes/constraints intentionally, not implicitly
139+
- validate nullability/default changes against existing data
140+
141+
For deeper patterns, read `references/drizzle-patterns.md`.
142+
For copyable transaction/query patterns, read `references/examples/drizzle-transaction.md`.
143+
144+
## 6) Fullstack Integration Track
145+
146+
When a change crosses boundaries, explicitly validate:
147+
148+
- API contract to domain mapping (validation, defaults, and error translation)
149+
- domain rules to persistence behavior (transactions, consistency, rollback behavior)
150+
- persistence results to API output shape (including empty and partial data cases)
151+
- backward compatibility for clients and existing data
152+
- corresponding test coverage across unit/integration/e2e layers where applicable
153+
154+
Use the review rubric in `references/quality-checklist.md` to structure findings.
155+
For end-to-end mapping examples, read `references/examples/fullstack-flow.md`.
156+
157+
## Output Format
158+
159+
Use this as the canonical output template for this skill:
160+
161+
1. What changed and why
162+
2. Risk assessment (behavioral, data, performance)
163+
3. Verification performed or intentionally skipped
164+
4. Required fixes vs optional improvements
165+
5. Remaining gaps or follow-ups
166+
167+
Keep output concise and concrete. Avoid generic "looks good" conclusions without evidence.
168+
169+
## Canonical Documentation
170+
171+
Use official docs when clarifying edge behavior:
172+
173+
- Bun docs: https://bun.sh/docs
174+
- Drizzle ORM docs: https://orm.drizzle.team/docs/overview
175+
- Drizzle migrations docs: https://orm.drizzle.team/docs/migrations
176+
- Zod docs: https://zod.dev
177+
- Turbo docs: https://turbo.build/repo/docs
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Drizzle Patterns
2+
3+
Use this reference for schema, migration, and query quality decisions.
4+
5+
## Canonical Documentation
6+
7+
- Drizzle overview: https://orm.drizzle.team/docs/overview
8+
- Drizzle schema declaration: https://orm.drizzle.team/docs/sql-schema-declaration
9+
- Drizzle migrations: https://orm.drizzle.team/docs/migrations
10+
- Drizzle indexes and constraints: https://orm.drizzle.team/docs/indexes-constraints
11+
12+
## Schema Design
13+
14+
- Keep table and column names consistent and predictable.
15+
- Prefer explicit foreign keys and on-delete behavior.
16+
- Add unique constraints only when they represent real domain invariants.
17+
- Avoid overloading nullable columns to represent multiple states.
18+
19+
## Migration Safety
20+
21+
- Split risky migrations into phases:
22+
1. Additive changes first (new columns/tables/indexes).
23+
2. Backfill data in controlled steps.
24+
3. Switch reads/writes.
25+
4. Remove deprecated fields after validation.
26+
- Prefer reversible migrations when possible.
27+
- Document expected lock/performance impact for large tables.
28+
29+
## Query Quality
30+
31+
- Select only required columns on hot paths.
32+
- Encode filters and joins so intent is obvious to reviewers.
33+
- Keep pagination deterministic (stable ordering).
34+
- Treat optional relations explicitly to avoid accidental cartesian or null surprises.
35+
36+
## Transaction Boundaries
37+
38+
- Group related writes in a transaction when partial writes would corrupt state.
39+
- Keep transactions short and side-effect free.
40+
- Avoid calling external systems from inside a DB transaction.
41+
42+
## Data Integrity Checks
43+
44+
- Validate assumptions before destructive updates.
45+
- Include guard conditions in update/delete operations.
46+
- For one-off scripts, produce dry-run output before apply mode.
47+
48+
## Testing Drizzle Changes
49+
50+
- Add integration tests for query behavior and constraints.
51+
- Verify migration up/down (or rollback alternative) in non-prod environments.
52+
- Test with representative edge data: nulls, duplicates, missing relations, and large sets.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# API Boundary Validation Example
2+
3+
```ts
4+
import { z } from "zod";
5+
6+
const in_schema = z.object({
7+
email: z.email(),
8+
role: z.enum(["admin", "member"]).default("member"),
9+
});
10+
11+
const out_schema = z.object({
12+
id: z.string(),
13+
email: z.email(),
14+
role: z.enum(["admin", "member"]),
15+
});
16+
17+
type Ctx = {
18+
req: {
19+
json: () => Promise<unknown>;
20+
};
21+
json: (body: unknown, status?: number) => Response;
22+
};
23+
24+
export async function post_user(ctx: Ctx) {
25+
const raw = await ctx.req.json();
26+
const parsed = in_schema.safeParse(raw);
27+
if (!parsed.success)
28+
return ctx.json({ error: "invalid_input", detail: parsed.error.issues }, 400);
29+
30+
const row = await create_user(parsed.data);
31+
const out = out_schema.parse(row);
32+
return ctx.json(out, 201);
33+
}
34+
35+
async function create_user(input: z.infer<typeof in_schema>) {
36+
return {
37+
id: crypto.randomUUID(),
38+
email: input.email,
39+
role: input.role,
40+
};
41+
}
42+
```
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Drizzle Transaction Example
2+
3+
```ts
4+
import { and, eq, gte, sql } from "drizzle-orm";
5+
6+
type Db = {
7+
transaction: <T>(fn: (tx: Tx) => Promise<T>) => Promise<T>;
8+
};
9+
10+
type Tx = {
11+
query: {
12+
account: {
13+
findFirst: (q: unknown) => Promise<{ id: string; balance: number } | undefined>;
14+
};
15+
};
16+
update: (table: unknown) => {
17+
set: (values: Record<string, unknown>) => {
18+
where: (clause: unknown) => Promise<{ rowsAffected: number }>;
19+
};
20+
};
21+
insert: (table: unknown) => {
22+
values: (row: Record<string, unknown>) => Promise<void>;
23+
};
24+
};
25+
26+
declare const account: unknown;
27+
declare const ledger: unknown;
28+
29+
export async function debit(db: Db, account_id: string, amount: number) {
30+
if (amount <= 0) throw new Error("amount must be positive");
31+
32+
return db.transaction(async (tx) => {
33+
const row = await tx.query.account.findFirst({
34+
where: eq(sql`id`, account_id),
35+
columns: { id: true, balance: true },
36+
});
37+
if (!row) throw new Error("account not found");
38+
if (row.balance < amount) throw new Error("insufficient funds");
39+
40+
const res = await tx
41+
.update(account)
42+
.set({ balance: row.balance - amount })
43+
.where(and(eq(sql`id`, account_id), gte(sql`balance`, amount)));
44+
45+
if (res.rowsAffected !== 1) throw new Error("concurrent update conflict");
46+
47+
await tx.insert(ledger).values({
48+
id: crypto.randomUUID(),
49+
account_id,
50+
amount: -amount,
51+
created_at: Date.now(),
52+
});
53+
54+
return { account_id, debited: amount };
55+
});
56+
}
57+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Fullstack Request-to-Response Mapping Example
2+
3+
```ts
4+
import { z } from "zod";
5+
6+
const in_schema = z.object({
7+
title: z.string().min(1),
8+
done: z.boolean().default(false),
9+
});
10+
11+
const out_schema = z.object({
12+
id: z.string(),
13+
title: z.string(),
14+
done: z.boolean(),
15+
created_at: z.number(),
16+
});
17+
18+
type Db = {
19+
insert: (table: unknown) => {
20+
values: (row: Record<string, unknown>) => {
21+
returning: () => Promise<unknown[]>;
22+
};
23+
};
24+
};
25+
26+
declare const todos: unknown;
27+
28+
export async function post_todo(body: unknown, db: Db) {
29+
const parsed = in_schema.safeParse(body);
30+
if (!parsed.success)
31+
return { status: 400, body: { error: "invalid_input", detail: parsed.error.issues } };
32+
33+
const rows = await db
34+
.insert(todos)
35+
.values({
36+
id: crypto.randomUUID(),
37+
title: parsed.data.title,
38+
done: parsed.data.done,
39+
created_at: Date.now(),
40+
})
41+
.returning();
42+
43+
const row = rows.at(0);
44+
if (!row) return { status: 500, body: { error: "insert_failed" } };
45+
46+
const out = out_schema.parse(row);
47+
return { status: 201, body: out };
48+
}
49+
```

0 commit comments

Comments
 (0)