/howto-develop-with-postgres
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.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/howto-develop-with-postgres
Context 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
SKILL.md
howto-develop-with-postgres.SKILL.mdname: 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
PostgreSQL Development Patterns
Overview
Enforce transaction safety, type safety, and naming conventions to prevent data corruption and runtime errors.
**Core principles:**
- Transactions prevent partial updates (data corruption)
- Type safety catches errors at compile time
- Naming conventions ensure consistency
- Read-write separation prevents accidental mutations
**For TypeScript/Drizzle implementations:** See [typescript-drizzle.md](./typescript-drizzle.md) for concrete patterns.
Transaction Management
TX_ Prefix Rule (STRICT ENFORCEMENT)
**Methods that START transactions:**
- Prefix method name with `TX_`
- Must NOT accept connection/executor parameter
- Call `connection.transaction()` or `db.transaction()` internally
**Methods that PARTICIPATE in transactions:**
- No `TX_` prefix
- MUST accept connection/executor parameter with default value
- Execute queries using the provided executor
// 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":**
- Single INSERT/UPDATE/DELETE operations
- Atomic operations like `onConflictDoUpdate`
- SELECT queries
Type Safety
Primary Keys
**Default: ULID stored as UUID**
- When in doubt, use ULID: "Most things can leak in some way"
- Prevents ID enumeration attacks
- Time-sortable for indexing efficiency
**Exceptions (context-dependent):**
- Pure join tables (composite PK from both FKs)
- Small lookup tables (serial/identity acceptable)
- Internal-only tables with no user visibility (serial/identity acceptable)
**Rule:** If unsure whether data will be user-visible, use ULID.
Financial Data
**Use exact decimal types (numeric/decimal) for monetary values:**
- Never use float/double for money (causes rounding errors)
- Use numeric/decimal with appropriate precision and scale
- Example: `numeric(19, 4)` for general financial data
**Why:** Floating-point types accumulate rounding errors. Exact decimal types prevent financial discrepancies.
JSONB Columns
**ALWAYS type JSONB columns in your ORM/schema:**
- Use typed schema when structure is known
- Use `Record<string, unknown>` if truly schemaless
- Never leave JSONB untyped
**Why:** Prevents runtime errors from accessing undefined properties or wrong types.
Read-Write Separation
**Maintain separate client types at compile time:**
- Read-write client: Full mutation capabilities
- Read-only client: Mutation methods removed at type level
- Default to read-only for query methods
- Use read-write only when mutations needed
**Why:** Prevents accidental writes to replica, enforces deliberate mutation choices.
Naming Conventions
Database Identifiers
**All database objects use snake_case:**
- Tables: `user_preferences`, `order_items`
- Columns: `created_at`, `user_id`, `is_active`
- Indexes: `idx_tablename_columns` (e.g., `idx_users_email`)
- Foreign keys: `fk_tablename_reftable` (e.g., `fk_orders_users`)
**Application code:** Map to idiomatic case (camelCase in TypeScript, etc.)
Schema Patterns
**Standard mixins:**
- `created_at`, `updated_at` timestamps on all tables
- `deleted_at` for soft deletion when needed
- `tenant_id` for multi-tenant tables (project-dependent)
**Proactive indexing:**
- All foreign key columns
- Columns used in WHERE clauses
- Columns used in JOIN conditions
- Columns used in ORDER BY
Concurrency
**Default isolation (Read Committed) for most operations.**
**Use stricter isolation when:**
- Financial operations: Serializable isolation
- Inventory/count operations: Serializable isolation
- Critical sections: Pessimistic locking (`SELECT ... FOR UPDATE`)
Migrations
**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.**
Common Mistakes
| 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-
Read more
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
PostgreSQL Development Patterns
Overview
Enforce transaction safety, type safety, and naming conventions to prevent data corruption and runtime errors.
**Core principles:**
- Transactions prevent partial updates (data corruption)
- Type safety catches errors at compile time
- Naming conventions ensure consistency
- Read-write separation prevents accidental mutations
**For TypeScript/Drizzle implementations:** See [typescript-drizzle.md](./typescript-drizzle.md) for concrete patterns.
Transaction Management
TX_ Prefix Rule (STRICT ENFORCEMENT)
**Methods that START transactions:**
- Prefix method name with `TX_`
- Must NOT accept connection/executor parameter
- Call `connection.transaction()` or `db.transaction()` internally
**Methods that PARTICIPATE in transactions:**
- No `TX_` prefix
- MUST accept connection/executor parameter with default value
- Execute queries using the provided executor
// 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":**
- Single INSERT/UPDATE/DELETE operations
- Atomic operations like `onConflictDoUpdate`
- SELECT queries
Type Safety
Primary Keys
**Default: ULID stored as UUID**
- When in doubt, use ULID: "Most things can leak in some way"
- Prevents ID enumeration attacks
- Time-sortable for indexing efficiency
**Exceptions (context-dependent):**
- Pure join tables (composite PK from both FKs)
- Small lookup tables (serial/identity acceptable)
- Internal-only tables with no user visibility (serial/identity acceptable)
**Rule:** If unsure whether data will be user-visible, use ULID.
Financial Data
**Use exact decimal types (numeric/decimal) for monetary values:**
- Never use float/double for money (causes rounding errors)
- Use numeric/decimal with appropriate precision and scale
- Example: `numeric(19, 4)` for general financial data
**Why:** Floating-point types accumulate rounding errors. Exact decimal types prevent financial discrepancies.
JSONB Columns
**ALWAYS type JSONB columns in your ORM/schema:**
- Use typed schema when structure is known
- Use `Record<string, unknown>` if truly schemaless
- Never leave JSONB untyped
**Why:** Prevents runtime errors from accessing undefined properties or wrong types.
Read-Write Separation
**Maintain separate client types at compile time:**
- Read-write client: Full mutation capabilities
- Read-only client: Mutation methods removed at type level
- Default to read-only for query methods
- Use read-write only when mutations needed
**Why:** Prevents accidental writes to replica, enforces deliberate mutation choices.
Naming Conventions
Database Identifiers
**All database objects use snake_case:**
- Tables: `user_preferences`, `order_items`
- Columns: `created_at`, `user_id`, `is_active`
- Indexes: `idx_tablename_columns` (e.g., `idx_users_email`)
- Foreign keys: `fk_tablename_reftable` (e.g., `fk_orders_users`)
**Application code:** Map to idiomatic case (camelCase in TypeScript, etc.)
Schema Patterns
**Standard mixins:**
- `created_at`, `updated_at` timestamps on all tables
- `deleted_at` for soft deletion when needed
- `tenant_id` for multi-tenant tables (project-dependent)
**Proactive indexing:**
- All foreign key columns
- Columns used in WHERE clauses
- Columns used in JOIN conditions
- Columns used in ORDER BY
Concurrency
**Default isolation (Read Committed) for most operations.**
**Use stricter isolation when:**
- Financial operations: Serializable isolation
- Inventory/count operations: Serializable isolation
- Critical sections: Pessimistic locking (`SELECT ... FOR UPDATE`)
Migrations
**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.**
Common Mistakes
| 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-
Showing the first part of this file.
This is my collection of plugins that I use on a day-to-day basis for getting stuff done with Claude Code. Most of these are development-oriented in some way or another, but also often end up being useful for other things.
Repo: ed3dai/ed3d-plugins
Other skills on ed3d-plugins.
- /doing-a-simple-two-stage-fanout
Use when analyzing a large corpus of text, code, or data that exceeds a single agent's effective context - orchestrates parallel Worker subagents, Critic review subagents, and a final Summarizer subagent with task tracking and failure recovery
Open skill - /using-generic-agents
Use to decide what kind of generic agent you should use
Open skill - /creating-a-plugin
Use when creating a new Claude Code plugin or setting up plugin structure - provides complete file organization, manifest format, and component definitions for commands, agents, skills, hooks, and MCP servers
Open skill - /creating-an-agent
Use when creating specialized subagents for Claude Code plugins or the Task tool - covers description writing for auto-delegation, tool selection, prompt structure, and testing agents
Open skill - /maintaining-a-marketplace
Use when creating, releasing, or maintaining a Claude Code Plugin Marketplace - covers marketplace.json schema, version management, release checklists, changelog conventions, and validation to prevent sync drift between plugin.json and marketplace.json
Open skill - /maintaining-project-context
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, determines affected contracts and documentation, and coordinates updates
Open skill

