doing-a-simple-two-sta…
Use when analyzing a large corpus of text, code, or data that exceeds a single agent's effective context - orchestrates parallel Worker subagents, Critic…
Use when writing database access code, creating schemas, or managing transactions with PostgreSQL - enforces transaction safety with TX_ naming, read-write separation, type safety for UUIDs/JSONB, and snake_case conventions to prevent data corruption and type errors
$ npx -y skills add ed3dai/ed3d-plugins --skill howto-develop-with-postgres --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/howto-develop-with-postgresContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing database access code, creating schemas, or managing transactions with PostgreSQL - enforces transaction safety with TX_ naming, read-write separation, type safety for UUIDs/JSONB, and snake_case conventions to prevent data corruption and type errors
name: howto-develop-with-postgres description: Use when writing database access code, creating schemas, or managing transactions with PostgreSQL - enforces transaction safety with TX_ naming, read-write separation, type safety for UUIDs/JSONB, and snake_case conventions to prevent data corruption and type errors user-invocable: false
Enforce transaction safety, type safety, and naming conventions to prevent data corruption and runtime errors.
**Core principles:**
**For TypeScript/Drizzle implementations:** See [typescript-drizzle.md](./typescript-drizzle.md) for concrete patterns.
**Methods that START transactions:**
**Methods that PARTICIPATE in transactions:**
// GOOD: Starts transaction, has TX_ prefix, no executor parameter
async TX_createUserWithProfile(userData: UserData, profileData: ProfileData): Promise<User> {
return this.db.transaction(async (tx) => {
const user = await this.createUser(userData, tx);
await this.createProfile(user.id, profileData, tx);
return user;
});
}
// GOOD: Participates in transaction, no TX_ prefix, takes executor
async createUser(userData: UserData, executor: Drizzle = this.db): Promise<User> {
return executor.insert(USERS).values(userData).returning();
}
// BAD: Starts transaction but missing TX_ prefix
async createUserWithProfile(userData: UserData, profileData: ProfileData): Promise<User> {
return this.db.transaction(async (tx) => { /* ... */ });
}
// BAD: Has TX_ prefix but takes executor parameter (allows nesting)
async TX_createUser(userData: UserData, executor: Drizzle = this.db): Promise<User> {
return executor.transaction(async (tx) => { /* ... */ });
}**What DOES NOT count as "starting a transaction":**
**Default: ULID stored as UUID**
**Exceptions (context-dependent):**
**Rule:** If unsure whether data will be user-visible, use ULID.
**Use exact decimal types (numeric/decimal) for monetary values:**
**Why:** Floating-point types accumulate rounding errors. Exact decimal types prevent financial discrepancies.
**ALWAYS type JSONB columns in your ORM/schema:**
**Why:** Prevents runtime errors from accessing undefined properties or wrong types.
**Maintain separate client types at compile time:**
**Why:** Prevents accidental writes to replica, enforces deliberate mutation choices.
**All database objects use snake_case:**
**Application code:** Map to idiomatic case (camelCase in TypeScript, etc.)
**Standard mixins:**
**Proactive indexing:**
**Default isolation (Read Committed) for most operations.**
**Use stricter isolation when:**
**Always use generate + migrate workflow:** 1. Change schema in code 2. Generate migration file 3. Review migration SQL 4. Apply migration to database
**Never use auto-push workflow in production.**
| Mistake | Reality | Fix | |---------|---------|-----| | "This is one operation, doesn't need transaction" | Multi-step operations without transactions cause partial updates and data corruption | Wrap in transaction with TX_ prefix | | "Single atomic operation needs TX_ prefix" | TX_ is for explicit transaction blocks, not atomic operations | No TX_ for single INSERT/UPDATE/DELETE | | "UUID is just a string" | Type confusion causes runtime errors (wrong ID formats, failed lookups) | Use strict UUID type in language | | "I'll type JSONB later when schema stabilizes" | Untyped JSONB leads to undefined property access and type errors | Type immediately with known fields or Record<string, unknown> | | "Read client vs write client doesn't matter" | Using wrong client bypasses separation, allows accidental mutations | Use read-
Ed's repo of Claude Code plugins, centered around a research-plan-implement workflow. Only a tiny bit cursed. If you're lucky.
Repo: ed3dai/ed3d-plugins
Use when analyzing a large corpus of text, code, or data that exceeds a single agent's effective context - orchestrates parallel Worker subagents, Critic…
Use when creating a new Claude Code plugin or setting up plugin structure - provides complete file organization, manifest format, and component definitions for…
Use when creating specialized subagents for Claude Code plugins or the Task tool - covers description writing for auto-delegation, tool selection, prompt…
Use when creating, releasing, or maintaining a Claude Code Plugin Marketplace - covers marketplace.json schema, version management, release checklists,…
Use when completing development phases or branches to identify and update CLAUDE.md or AGENTS.md files that may have become stale - analyzes what changed,…