Source: data_layer/docs/README_DRIZZLE_FIRST.md
Drizzle-First Schema System
π― Quick Summary
What: A complete code generation pipeline where Drizzle TypeScript schemas are the single source of truth.
Why: Edit schemas once in TypeScript, automatically generate everything else (JSON Schema, Pydantic, SQLAlchemy, SQL, GraphQL, Neo4j Cypher).
How: Run python scripts/generate_from_drizzle.py after editing Drizzle schemas.
π Quick Start (30 seconds)
# 1. Look at the example
cat schemas/domain/drizzle/v1/examples/league_example.schema.ts
# 2. Generate everything
python scripts/generate_from_drizzle.py
# 3. Check the output
ls -R schemas/generated/from_drizzle/π Documentation
| Document | Purpose |
|---|---|
| DRIZZLE_FIRST_ARCHITECTURE.md | Complete architecture guide with best practices |
| MIGRATION_TO_DRIZZLE_FIRST.md | Step-by-step migration guide from JSON Schema |
| Example Schema | Comprehensive example showing all features |
ποΈ Architecture
π Write Here (Drizzle TypeScript)
β
βββββ΄βββββββββββββββββββββββββββββββββββββββ
β python scripts/generate_from_drizzle.py β
βββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βββ> schemas/generated/from_drizzle/json/ (JSON Schema)
βββ> schemas/generated/from_drizzle/pydantic/ (Pydantic Models)
βββ> schemas/generated/from_drizzle/sqlalchemy/ (SQLAlchemy Models)
βββ> schemas/generated/from_drizzle/sql/ (SQL DDL)
βββ> schemas/generated/from_drizzle/graphql/ (GraphQL SDL)
βββ> schemas/generated/from_drizzle/neo4j/ (Neo4j Cypher)π‘ Key Benefits
β Single Source of Truth
- Edit once in TypeScript
- Generate everything automatically
- No more manual synchronization
β Type Safety
- TypeScript gives immediate feedback
- IDE autocomplete for schema definitions
- Compile-time error checking
β Comprehensive Generation
- JSON Schema - API documentation, validation
- Pydantic - Python FastAPI validation
- SQLAlchemy - Python ORM queries
- SQL - Database migrations
- GraphQL - GraphQL API schemas
- Neo4j - Graph database constraints
β Best DX (Developer Experience)
- Beautiful TypeScript syntax
- Clear error messages
- Fast iteration
- Example-driven
π What Gets Generated
1. JSON Schema
Use for: API documentation, OpenAPI specs, validation
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "users",
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "maxLength": 255 }
},
"required": ["email"]
}2. Pydantic Models
Use for: FastAPI, Python validation, type hints
class Users(BaseModel):
id: Optional[UUID] = None
email: str
created_at: datetime
class UsersCreate(BaseModel):
"""For POST requests (excludes auto-generated fields)"""
email: str
class UsersUpdate(BaseModel):
"""For PUT/PATCH requests (all optional)"""
email: Optional[str] = None3. SQLAlchemy Models
Use for: Python ORM queries, complex relationships
class Users(Base):
__tablename__ = "users"
id = Column(UUID, primary_key=True)
email = Column(String(255), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)4. SQL DDL
Use for: Database migrations, schema initialization
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE INDEX users_email_idx ON users(email);5. GraphQL SDL
Use for: GraphQL APIs, frontend type generation
type Users {
id: UUID!
email: String!
createdAt: DateTime!
}
type Query {
users(id: ID!): Users
usersList(limit: Int): [Users!]!
}6. Neo4j Cypher
Use for: Graph database constraints, indexes
CREATE CONSTRAINT users_id_unique IF NOT EXISTS
FOR (n:Users) REQUIRE n.id IS UNIQUE;
CREATE INDEX users_email_index IF NOT EXISTS
FOR (n:Users) ON (n.email);π οΈ Usage
Define Your Schema
Create schemas/domain/drizzle/v1/my_feature/users.schema.ts:
import { pgTable, uuid, varchar, timestamp } from "drizzle-orm/pg-core";
/**
* Users in the system
*/
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: varchar("email", { length: 255 }).notNull().unique(),
name: varchar("name", { length: 100 }).notNull(),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});Generate Everything
# Generate all targets
python scripts/generate_from_drizzle.py
# Generate specific target
python scripts/generate_from_drizzle.py --target pydantic
# Process specific schema
python scripts/generate_from_drizzle.py --schema schemas/domain/drizzle/v1/my_feature/users.schema.tsUse Generated Code
Python (FastAPI):
from fastapi import FastAPI
from schemas.generated.from_drizzle.pydantic.users_model import Users, UsersCreate
app = FastAPI()
@app.post("/users", response_model=Users)
async def create_user(user: UsersCreate):
# Automatic validation!
return await db.insert_user(user)SQL (Migrations):
psql -d mydatabase -f schemas/generated/from_drizzle/sql/schema.sqlGraphQL (API):
query GetUser {
users(id: "123") {
email
name
}
}π Best Practices
Always Include These Fields
export const my_table = pgTable("my_table", {
// β
UUID primary key with auto-generation
id: uuid("id").primaryKey().defaultRandom(),
// ... your fields ...
// β
Timestamps for auditing
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});Define Foreign Keys Properly
export const documents = pgTable("documents", {
id: uuid("id").primaryKey().defaultRandom(),
// β
Foreign key with cascade delete
league_id: uuid("league_id")
.notNull()
.references(() => leagues.id, { onDelete: 'cascade' }),
}, (table) => ({
// β
Index on foreign key
leagueIdx: index("documents_league_idx").on(table.league_id),
}));Use JSONB for Flexible Data
export const leagues = pgTable("leagues", {
// ... other fields ...
// β
JSONB with default value
metadata: jsonb("metadata").default({}),
tags: jsonb("tags").default([]),
});Add JSDoc Comments
/**
* Leagues represent sports organizations and competitions
*/
export const leagues = pgTable("leagues", {
// ... columns ...
});π Comparison
| Approach | Pros | Cons |
|---|---|---|
| JSON Schema First | Language-agnostic, widely supported | Manual sync, verbose, no type safety |
| Prisma First | Great DX, migrations | Prisma-specific, limited to Prisma |
| Drizzle First (β ) | Type-safe, generates everything, best DX | Requires TypeScript knowledge |
π§ Advanced
Custom Generators
Add your own generator in scripts/generate_from_drizzle.py:
class MyCustomGenerator:
def generate(self, tables: List[Table], output_dir: Path) -> None:
for table in tables:
# Your custom logic
passSelective Generation
# Only SQL
python scripts/generate_from_drizzle.py --target sql
# Only specific schema
python scripts/generate_from_drizzle.py --schema schemas/domain/drizzle/v1/combat/**.tsπ Troubleshooting
| Problem | Solution |
|---|---|
| Columns not parsing | Check Drizzle syntax: varchar("name", { length: 100 }) |
| Foreign keys failing | Use arrow function: references(() => table.id) |
| Types are wrong | Check column type: timestamp() not date() |
| SQL migration fails | Check for circular dependencies |
π Resources
- π Full Architecture Guide - Comprehensive documentation
- π Migration Guide - Step-by-step migration from JSON Schema
- π Example Schema - Working example with all features
- π§ Generation Script - The actual generator
- π Drizzle ORM Docs (opens in a new tab) - Official Drizzle documentation
π― Next Steps
- Learn: Read DRIZZLE_FIRST_ARCHITECTURE.md
- Explore: Check out the example
- Try: Generate from the example
- Create: Make your first Drizzle schema
- Migrate: Follow MIGRATION_TO_DRIZZLE_FIRST.md
β Checklist
- Read architecture guide
- Run example generation
- Review generated output
- Create your first Drizzle schema
- Generate and test
- Integrate with your app
- Migrate remaining schemas
Questions? Check the Architecture Guide or Migration Guide.
Ready to start? Run: python scripts/generate_from_drizzle.py