@plazmodium/odin 0.3.3-beta → 0.3.5-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -10
- package/builtin/ODIN.md +1067 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +713 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +189 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +205 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts +1 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +6 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/adapters/skills/types.d.ts +1 -0
- package/dist/adapters/skills/types.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.d.ts +10 -2
- package/dist/adapters/workflow-state/in-memory.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.js +98 -5
- package/dist/adapters/workflow-state/in-memory.js.map +1 -1
- package/dist/adapters/workflow-state/supabase.d.ts +8 -2
- package/dist/adapters/workflow-state/supabase.d.ts.map +1 -1
- package/dist/adapters/workflow-state/supabase.js +204 -0
- package/dist/adapters/workflow-state/supabase.js.map +1 -1
- package/dist/adapters/workflow-state/types.d.ts +15 -1
- package/dist/adapters/workflow-state/types.d.ts.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/domain/skill-draft-validation.d.ts +18 -0
- package/dist/domain/skill-draft-validation.d.ts.map +1 -0
- package/dist/domain/skill-draft-validation.js +100 -0
- package/dist/domain/skill-draft-validation.js.map +1 -0
- package/dist/domain/skill-proposals.d.ts +11 -0
- package/dist/domain/skill-proposals.d.ts.map +1 -0
- package/dist/domain/skill-proposals.js +103 -0
- package/dist/domain/skill-proposals.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +39 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +30 -1
- package/dist/schemas.js.map +1 -1
- package/dist/server.js +38 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/apply-migrations.d.ts +10 -0
- package/dist/tools/apply-migrations.d.ts.map +1 -1
- package/dist/tools/apply-migrations.js +10 -26
- package/dist/tools/apply-migrations.js.map +1 -1
- package/dist/tools/capture-learning.d.ts.map +1 -1
- package/dist/tools/capture-learning.js +14 -1
- package/dist/tools/capture-learning.js.map +1 -1
- package/dist/tools/get-skill-proposal-queue.d.ts +5 -0
- package/dist/tools/get-skill-proposal-queue.d.ts.map +1 -0
- package/dist/tools/get-skill-proposal-queue.js +21 -0
- package/dist/tools/get-skill-proposal-queue.js.map +1 -0
- package/dist/tools/get-skill-proposals.d.ts +4 -0
- package/dist/tools/get-skill-proposals.d.ts.map +1 -0
- package/dist/tools/get-skill-proposals.js +11 -0
- package/dist/tools/get-skill-proposals.js.map +1 -0
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/tools/publish-skill-proposal.d.ts +5 -0
- package/dist/tools/publish-skill-proposal.d.ts.map +1 -0
- package/dist/tools/publish-skill-proposal.js +57 -0
- package/dist/tools/publish-skill-proposal.js.map +1 -0
- package/dist/tools/record-skill-proposal-decision.d.ts +4 -0
- package/dist/tools/record-skill-proposal-decision.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-decision.js +22 -0
- package/dist/tools/record-skill-proposal-decision.js.map +1 -0
- package/dist/tools/record-skill-proposal-draft.d.ts +5 -0
- package/dist/tools/record-skill-proposal-draft.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-draft.js +65 -0
- package/dist/tools/record-skill-proposal-draft.js.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts +5 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.js +20 -0
- package/dist/tools/sync-skill-proposal-candidates.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/migrations/009_skill_proposal_candidates.sql +124 -0
- package/migrations/010_skill_proposals.sql +36 -0
- package/migrations/README.md +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-fastapi
|
|
3
|
+
description: FastAPI framework for building high-performance async Python APIs with automatic OpenAPI docs
|
|
4
|
+
category: backend
|
|
5
|
+
version: "0.100+"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- postgresql
|
|
8
|
+
- mongodb
|
|
9
|
+
- redis
|
|
10
|
+
- rest-api
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Python FastAPI
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
FastAPI is a modern, high-performance Python web framework for building APIs. It uses Python type hints for automatic validation, serialization, and OpenAPI documentation.
|
|
18
|
+
|
|
19
|
+
## Project Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/
|
|
23
|
+
├── main.py # FastAPI app instance + startup
|
|
24
|
+
├── config.py # Settings via pydantic-settings
|
|
25
|
+
├── routers/
|
|
26
|
+
│ ├── auth.py # Auth endpoints
|
|
27
|
+
│ └── users.py # User endpoints
|
|
28
|
+
├── models/
|
|
29
|
+
│ ├── user.py # SQLAlchemy / ORM models
|
|
30
|
+
│ └── schemas.py # Pydantic request/response schemas
|
|
31
|
+
├── services/
|
|
32
|
+
│ └── user_service.py # Business logic
|
|
33
|
+
├── middleware/
|
|
34
|
+
│ └── auth.py # Auth dependency
|
|
35
|
+
├── db/
|
|
36
|
+
│ ├── session.py # Database session
|
|
37
|
+
│ └── migrations/ # Alembic migrations
|
|
38
|
+
└── tests/
|
|
39
|
+
├── conftest.py # Fixtures
|
|
40
|
+
└── test_users.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Core Patterns
|
|
44
|
+
|
|
45
|
+
### App Setup
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from fastapi import FastAPI
|
|
49
|
+
from contextlib import asynccontextmanager
|
|
50
|
+
|
|
51
|
+
@asynccontextmanager
|
|
52
|
+
async def lifespan(app: FastAPI):
|
|
53
|
+
# Startup
|
|
54
|
+
await db.connect()
|
|
55
|
+
yield
|
|
56
|
+
# Shutdown
|
|
57
|
+
await db.disconnect()
|
|
58
|
+
|
|
59
|
+
app = FastAPI(title="My API", lifespan=lifespan)
|
|
60
|
+
app.include_router(auth_router, prefix="/auth", tags=["auth"])
|
|
61
|
+
app.include_router(users_router, prefix="/users", tags=["users"])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Pydantic Schemas
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
68
|
+
|
|
69
|
+
class UserCreate(BaseModel):
|
|
70
|
+
email: EmailStr
|
|
71
|
+
password: str = Field(min_length=8)
|
|
72
|
+
name: str = Field(max_length=100)
|
|
73
|
+
|
|
74
|
+
class UserResponse(BaseModel):
|
|
75
|
+
id: int
|
|
76
|
+
email: str
|
|
77
|
+
name: str
|
|
78
|
+
|
|
79
|
+
model_config = {"from_attributes": True} # ORM mode
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Dependency Injection
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from fastapi import Depends, HTTPException, status
|
|
86
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
87
|
+
|
|
88
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
|
89
|
+
|
|
90
|
+
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
|
|
91
|
+
payload = decode_jwt(token)
|
|
92
|
+
if not payload:
|
|
93
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
94
|
+
user = await user_service.get_by_id(payload["sub"])
|
|
95
|
+
if not user:
|
|
96
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
97
|
+
return user
|
|
98
|
+
|
|
99
|
+
# Use in endpoint
|
|
100
|
+
@router.get("/me", response_model=UserResponse)
|
|
101
|
+
async def get_me(user: User = Depends(get_current_user)):
|
|
102
|
+
return user
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Error Handling
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from fastapi import HTTPException
|
|
109
|
+
from fastapi.responses import JSONResponse
|
|
110
|
+
|
|
111
|
+
class AppException(Exception):
|
|
112
|
+
def __init__(self, status_code: int, detail: str, code: str):
|
|
113
|
+
self.status_code = status_code
|
|
114
|
+
self.detail = detail
|
|
115
|
+
self.code = code
|
|
116
|
+
|
|
117
|
+
@app.exception_handler(AppException)
|
|
118
|
+
async def app_exception_handler(request, exc: AppException):
|
|
119
|
+
return JSONResponse(
|
|
120
|
+
status_code=exc.status_code,
|
|
121
|
+
content={"error": exc.code, "detail": exc.detail},
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Best Practices
|
|
126
|
+
|
|
127
|
+
1. **Use async** — prefer `async def` endpoints with async DB drivers (asyncpg, motor)
|
|
128
|
+
2. **Pydantic v2** — use `model_config` dict, not inner `Config` class
|
|
129
|
+
3. **Dependency injection** — use `Depends()` for auth, DB sessions, services
|
|
130
|
+
4. **Response models** — always set `response_model` to control serialization
|
|
131
|
+
5. **Background tasks** — use `BackgroundTasks` for non-blocking operations
|
|
132
|
+
6. **Settings** — use `pydantic-settings` with `.env` files for configuration
|
|
133
|
+
7. **Alembic** — use for database migrations, never raw SQL in app code
|
|
134
|
+
|
|
135
|
+
## Gotchas
|
|
136
|
+
|
|
137
|
+
- **Sync vs async**: Mixing sync and async code blocks the event loop — use `run_in_executor` for sync operations
|
|
138
|
+
- **Pydantic v1 vs v2**: Many tutorials use v1 syntax — check for `model_config` vs inner `Config`
|
|
139
|
+
- **Dependency scope**: Dependencies are per-request by default; use `yield` dependencies for cleanup
|
|
140
|
+
- **OpenAPI schema**: Circular model references require `model_rebuild()` calls
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mongodb
|
|
3
|
+
description: MongoDB document database patterns, schema design, indexing, and aggregation
|
|
4
|
+
category: database
|
|
5
|
+
version: "7.x"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- nodejs-express
|
|
8
|
+
- nodejs-fastify
|
|
9
|
+
- python-fastapi
|
|
10
|
+
- rest-api
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# MongoDB
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
MongoDB is a document database that stores data in flexible JSON-like documents. This skill covers schema design, queries, indexing, and aggregation pipelines.
|
|
18
|
+
|
|
19
|
+
## Schema Design
|
|
20
|
+
|
|
21
|
+
### Embedding vs Referencing
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// EMBED when data is read together and has a 1:few relationship
|
|
25
|
+
{
|
|
26
|
+
_id: ObjectId("..."),
|
|
27
|
+
name: "John",
|
|
28
|
+
addresses: [
|
|
29
|
+
{ street: "123 Main", city: "Springfield", type: "home" },
|
|
30
|
+
{ street: "456 Work", city: "Springfield", type: "work" }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// REFERENCE when data is many:many, frequently updated independently, or large
|
|
35
|
+
{
|
|
36
|
+
_id: ObjectId("..."),
|
|
37
|
+
name: "John",
|
|
38
|
+
organization_id: ObjectId("...") // Reference to organizations collection
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Schema Validation
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
db.createCollection("users", {
|
|
46
|
+
validator: {
|
|
47
|
+
$jsonSchema: {
|
|
48
|
+
bsonType: "object",
|
|
49
|
+
required: ["email", "name", "createdAt"],
|
|
50
|
+
properties: {
|
|
51
|
+
email: { bsonType: "string", pattern: "^.+@.+$" },
|
|
52
|
+
name: { bsonType: "string", maxLength: 100 },
|
|
53
|
+
role: { enum: ["admin", "member", "viewer"] },
|
|
54
|
+
createdAt: { bsonType: "date" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Query Patterns
|
|
62
|
+
|
|
63
|
+
### CRUD
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Find with projection
|
|
67
|
+
const user = await db.collection('users').findOne(
|
|
68
|
+
{ email: "john@example.com" },
|
|
69
|
+
{ projection: { password_hash: 0 } } // Exclude sensitive fields
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Update with operators
|
|
73
|
+
await db.collection('users').updateOne(
|
|
74
|
+
{ _id: userId },
|
|
75
|
+
{ $set: { name: "New Name" }, $currentDate: { updatedAt: true } }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Upsert
|
|
79
|
+
await db.collection('metrics').updateOne(
|
|
80
|
+
{ date: today, type: "pageview" },
|
|
81
|
+
{ $inc: { count: 1 } },
|
|
82
|
+
{ upsert: true }
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Aggregation Pipeline
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const result = await db.collection('orders').aggregate([
|
|
90
|
+
{ $match: { status: "completed", createdAt: { $gte: thirtyDaysAgo } } },
|
|
91
|
+
{ $group: { _id: "$customerId", totalSpent: { $sum: "$total" }, orderCount: { $sum: 1 } } },
|
|
92
|
+
{ $sort: { totalSpent: -1 } },
|
|
93
|
+
{ $limit: 10 },
|
|
94
|
+
{ $lookup: { from: "customers", localField: "_id", foreignField: "_id", as: "customer" } },
|
|
95
|
+
{ $unwind: "$customer" },
|
|
96
|
+
{ $project: { name: "$customer.name", totalSpent: 1, orderCount: 1 } }
|
|
97
|
+
]).toArray();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Indexes
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Single field
|
|
104
|
+
db.users.createIndex({ email: 1 }, { unique: true });
|
|
105
|
+
|
|
106
|
+
// Compound (order matters for query optimization)
|
|
107
|
+
db.orders.createIndex({ customerId: 1, createdAt: -1 });
|
|
108
|
+
|
|
109
|
+
// TTL (auto-delete documents after expiry)
|
|
110
|
+
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
111
|
+
|
|
112
|
+
// Text search
|
|
113
|
+
db.articles.createIndex({ title: "text", body: "text" });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Best Practices
|
|
117
|
+
|
|
118
|
+
1. **Design for queries** — model data based on access patterns, not normalization
|
|
119
|
+
2. **Embed by default** — reference only when there's a clear reason
|
|
120
|
+
3. **Index for your queries** — use `explain()` to verify index usage
|
|
121
|
+
4. **Use `$project` early** — reduce document size in aggregation pipelines
|
|
122
|
+
5. **Avoid unbounded arrays** — arrays that grow without limit cause performance issues
|
|
123
|
+
6. **Use transactions sparingly** — multi-document transactions add overhead; design schemas to minimize need
|
|
124
|
+
7. **Connection pooling** — reuse client instances; don't connect/disconnect per request
|
|
125
|
+
|
|
126
|
+
## Gotchas
|
|
127
|
+
|
|
128
|
+
- **No JOINs** — `$lookup` exists but is expensive; embed data you read together
|
|
129
|
+
- **Document size limit** — 16MB per document; watch out for growing arrays
|
|
130
|
+
- **ObjectId ordering** — ObjectIds contain timestamps, so default `_id` sort is chronological
|
|
131
|
+
- **Schemaless trap** — lack of enforced schema leads to inconsistent data; use validation
|
|
132
|
+
- **Write concern** — default `w:1` may lose data on failover; use `w:"majority"` for durability
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: postgresql
|
|
3
|
+
description: PostgreSQL database best practices, query patterns, indexing, and performance optimization
|
|
4
|
+
category: database
|
|
5
|
+
version: "15+"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- prisma-orm
|
|
8
|
+
- supabase
|
|
9
|
+
- nodejs-express
|
|
10
|
+
- nodejs-fastify
|
|
11
|
+
- python-fastapi
|
|
12
|
+
- python-django
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# PostgreSQL
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
PostgreSQL is an advanced open-source relational database. This skill covers schema design, query patterns, indexing, and performance for application developers.
|
|
20
|
+
|
|
21
|
+
## Schema Design
|
|
22
|
+
|
|
23
|
+
### Tables
|
|
24
|
+
|
|
25
|
+
```sql
|
|
26
|
+
-- Always use explicit types and constraints
|
|
27
|
+
CREATE TABLE users (
|
|
28
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
29
|
+
email TEXT NOT NULL UNIQUE,
|
|
30
|
+
password_hash TEXT NOT NULL,
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
33
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
-- Use enums for fixed value sets
|
|
37
|
+
CREATE TYPE user_role AS ENUM ('admin', 'member', 'viewer');
|
|
38
|
+
ALTER TABLE users ADD COLUMN role user_role NOT NULL DEFAULT 'member';
|
|
39
|
+
|
|
40
|
+
-- Junction table for many-to-many
|
|
41
|
+
CREATE TABLE user_organizations (
|
|
42
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
43
|
+
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
|
44
|
+
role user_role NOT NULL DEFAULT 'member',
|
|
45
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
46
|
+
PRIMARY KEY (user_id, org_id)
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Indexes
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
-- B-tree (default) for equality and range queries
|
|
54
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
55
|
+
|
|
56
|
+
-- Partial index for common filtered queries
|
|
57
|
+
CREATE INDEX idx_active_users ON users(created_at) WHERE is_active = true;
|
|
58
|
+
|
|
59
|
+
-- GIN for JSONB and full-text search
|
|
60
|
+
CREATE INDEX idx_metadata ON products USING GIN(metadata);
|
|
61
|
+
CREATE INDEX idx_search ON articles USING GIN(to_tsvector('english', title || ' ' || body));
|
|
62
|
+
|
|
63
|
+
-- Composite for multi-column queries (leftmost prefix rule)
|
|
64
|
+
CREATE INDEX idx_org_role ON user_organizations(org_id, role);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Query Patterns
|
|
68
|
+
|
|
69
|
+
### Common Patterns
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Upsert (INSERT ... ON CONFLICT)
|
|
73
|
+
INSERT INTO users (email, name) VALUES ($1, $2)
|
|
74
|
+
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, updated_at = now()
|
|
75
|
+
RETURNING *;
|
|
76
|
+
|
|
77
|
+
-- Pagination with cursor (better than OFFSET for large tables)
|
|
78
|
+
SELECT * FROM users
|
|
79
|
+
WHERE created_at < $1 -- cursor: last item's created_at
|
|
80
|
+
ORDER BY created_at DESC
|
|
81
|
+
LIMIT 20;
|
|
82
|
+
|
|
83
|
+
-- CTE for complex queries
|
|
84
|
+
WITH active_users AS (
|
|
85
|
+
SELECT * FROM users WHERE last_login > now() - interval '30 days'
|
|
86
|
+
)
|
|
87
|
+
SELECT org_id, count(*) as active_count
|
|
88
|
+
FROM user_organizations uo
|
|
89
|
+
JOIN active_users au ON uo.user_id = au.id
|
|
90
|
+
GROUP BY org_id;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### JSONB
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
-- Query JSONB fields
|
|
97
|
+
SELECT * FROM products WHERE metadata->>'category' = 'electronics';
|
|
98
|
+
SELECT * FROM products WHERE metadata @> '{"tags": ["sale"]}';
|
|
99
|
+
|
|
100
|
+
-- Update JSONB
|
|
101
|
+
UPDATE products SET metadata = metadata || '{"featured": true}' WHERE id = $1;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Best Practices
|
|
105
|
+
|
|
106
|
+
1. **Use UUID primary keys** for distributed-friendly IDs (`gen_random_uuid()`)
|
|
107
|
+
2. **Always `TIMESTAMPTZ`** — never `TIMESTAMP` without timezone
|
|
108
|
+
3. **Parameterized queries** — never interpolate user input into SQL
|
|
109
|
+
4. **Foreign keys with ON DELETE** — explicit cascade/restrict/set null behavior
|
|
110
|
+
5. **`EXPLAIN ANALYZE`** — profile queries before optimizing; check seq scans
|
|
111
|
+
6. **Connection pooling** — use PgBouncer or built-in pool (Supabase provides this)
|
|
112
|
+
7. **Migrations** — use a migration tool (Alembic, Prisma, Flyway); never manual DDL in production
|
|
113
|
+
|
|
114
|
+
## Gotchas
|
|
115
|
+
|
|
116
|
+
- **NULL semantics** — `NULL != NULL`, use `IS NOT DISTINCT FROM` for nullable comparisons
|
|
117
|
+
- **OFFSET performance** — degrades linearly with page number; use cursor-based pagination
|
|
118
|
+
- **Lock contention** — long-running transactions block writes; keep transactions short
|
|
119
|
+
- **Index bloat** — `REINDEX` or `pg_repack` for heavily updated tables
|
|
120
|
+
- **Text search** — `LIKE '%term%'` can't use B-tree indexes; use GIN + `tsvector` for full-text
|