|
1 | | -# CLAUDE.md |
2 | | - |
3 | | -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
4 | | - |
5 | | -## Overview |
6 | | - |
7 | | -Vespertide is a Rust workspace for defining database schemas in JSON/YAML and generating migration plans and SQL from model diffs. It enables declarative schema management by comparing the current model state against a baseline reconstructed from applied migrations. |
8 | | - |
9 | | -## Build and Test Commands |
10 | | - |
11 | | -```bash |
12 | | -# Build the entire workspace |
13 | | -cargo build |
14 | | - |
15 | | -# Run all tests |
16 | | -cargo test |
17 | | - |
18 | | -# Run tests for a specific crate |
19 | | -cargo test -p vespertide-core |
20 | | -cargo test -p vespertide-planner |
21 | | - |
22 | | -# Format code |
23 | | -cargo fmt |
24 | | - |
25 | | -# Lint (important: use all targets and features) |
26 | | -cargo clippy --all-targets --all-features |
27 | | - |
28 | | -# Regenerate JSON schemas |
29 | | -cargo run -p vespertide-schema-gen -- --out schemas |
30 | | - |
31 | | -# Run CLI commands (use -p vespertide-cli) |
32 | | -cargo run -p vespertide-cli -- init |
33 | | -cargo run -p vespertide-cli -- new user |
34 | | -cargo run -p vespertide-cli -- diff |
35 | | -cargo run -p vespertide-cli -- sql |
36 | | -cargo run -p vespertide-cli -- revision -m "message" |
37 | | -cargo run -p vespertide-cli -- status |
38 | | -cargo run -p vespertide-cli -- log |
39 | | -``` |
40 | | - |
41 | | -## Architecture |
42 | | - |
43 | | -### Core Data Flow |
44 | | - |
45 | | -1. **Schema Definition**: Users define tables in JSON files (`TableDef`) in the `models/` directory |
46 | | -2. **Baseline Reconstruction**: Applied migrations are replayed to rebuild the baseline schema |
47 | | -3. **Diffing**: Current models are compared against the baseline to compute changes |
48 | | -4. **Planning**: Changes are converted into a `MigrationPlan` with versioned actions |
49 | | -5. **SQL Generation**: Migration actions are translated into SQL statements |
50 | | - |
51 | | -### Crate Responsibilities |
52 | | - |
53 | | -- **vespertide-core**: Data structures (`TableDef`, `ColumnDef`, `ColumnType`, `MigrationAction`, `MigrationPlan`, constraints, indexes) |
54 | | - - `ColumnType` enum with `Simple(SimpleColumnType)` and `Complex(ComplexColumnType)` variants |
55 | | - - `ColumnType::to_sql()` and `ColumnType::to_rust_type()` methods for type conversion |
56 | | -- **vespertide-planner**: |
57 | | - - `schema_from_plans()`: Replays applied migrations to reconstruct baseline schema |
58 | | - - `diff_schemas()`: Compares two schemas and generates migration actions |
59 | | - - `plan_next_migration()`: Combines baseline reconstruction + diffing to create the next migration |
60 | | - - `apply_action()`: Applies a single migration action to a schema (used during replay) |
61 | | - - `validate_*()`: Validates schemas and migration plans |
62 | | -- **vespertide-query**: Converts `MigrationAction` → SQL with bind parameters |
63 | | - - Uses `ColumnType::to_sql()` method for SQL type conversion |
64 | | -- **vespertide-config**: Manages `vespertide.json` (models/migrations directories, naming case preferences) |
65 | | -- **vespertide-cli**: Command-line interface implementation |
66 | | -- **vespertide-exporter**: Exports schemas to other formats (e.g., SeaORM entities) |
67 | | - - Uses `ColumnType::to_rust_type(nullable)` method for Rust type conversion |
68 | | -- **vespertide-schema-gen**: Generates JSON Schema files for validation |
69 | | -- **vespertide-macro**: Placeholder for future runtime migration executor |
70 | | - |
71 | | -### Key Architectural Patterns |
72 | | - |
73 | | -**Migration Replay Pattern**: The planner doesn't store a "current database state" - it reconstructs it by replaying all applied migrations in order. This ensures the baseline is always derivable from the migration history. |
74 | | - |
75 | | -**Declarative Diffing**: Users declare the desired end state in model files. The diff engine compares this against the reconstructed baseline to compute necessary changes. |
76 | | - |
77 | | -**Action-Based Migrations**: All changes are expressed as typed `MigrationAction` enums (CreateTable, AddColumn, ModifyColumnType, etc.) rather than raw SQL. SQL generation happens in a separate layer. |
78 | | - |
79 | | -## Important Implementation Details |
80 | | - |
81 | | -### ColumnDef Structure |
82 | | -When creating `ColumnDef` instances in tests or code, you must initialize ALL fields including the newer inline constraint fields: |
83 | | - |
84 | | -```rust |
85 | | -ColumnDef { |
86 | | - name: "id".into(), |
87 | | - r#type: ColumnType::Simple(SimpleColumnType::Integer), |
88 | | - nullable: false, |
89 | | - default: None, |
90 | | - comment: None, |
91 | | - primary_key: None, // Inline PK declaration |
92 | | - unique: None, // Inline unique constraint |
93 | | - index: None, // Inline index creation |
94 | | - foreign_key: None, // Inline FK definition |
95 | | -} |
96 | | -``` |
97 | | - |
98 | | -These inline fields (added recently) allow constraints to be defined directly on columns in addition to table-level `TableConstraint` definitions. |
99 | | - |
100 | | -### ColumnType Structure |
101 | | -`ColumnType` is an enum with two variants: |
102 | | -- `Simple(SimpleColumnType)`: Built-in types like `Integer`, `Text`, `Boolean`, etc. |
103 | | -- `Complex(ComplexColumnType)`: Types with parameters like `Varchar { length }` or `Custom { custom_type }` |
104 | | - |
105 | | -**Important**: In Rust code, always use `ColumnType::Simple(SimpleColumnType::Integer)` instead of the old `ColumnType::Integer` syntax. The `From` trait is implemented for convenience: |
106 | | -```rust |
107 | | -// These are equivalent: |
108 | | -ColumnType::Simple(SimpleColumnType::Integer) |
109 | | -SimpleColumnType::Integer.into() |
110 | | -``` |
111 | | - |
112 | | -### ColumnType Methods |
113 | | -`ColumnType` provides two utility methods: |
114 | | -- `to_sql()`: Returns the SQL type string (e.g., `"INTEGER"`, `"VARCHAR(255)"`) |
115 | | -- `to_rust_type(nullable: bool)`: Returns the Rust type string for SeaORM entity generation (e.g., `"i32"` or `"Option<i32>"`) |
116 | | - |
117 | | -These methods replace the old standalone functions `column_type_sql()` and `rust_type()`. |
118 | | - |
119 | | -### Foreign Key Definition |
120 | | -Foreign keys can be defined inline on columns via the `foreign_key` field: |
121 | | - |
122 | | -```rust |
123 | | -pub struct ForeignKeyDef { |
124 | | - pub ref_table: TableName, |
125 | | - pub ref_columns: Vec<ColumnName>, |
126 | | - pub on_delete: Option<ReferenceAction>, |
127 | | - pub on_update: Option<ReferenceAction>, |
128 | | -} |
129 | | -``` |
130 | | - |
131 | | -### Migration Plan Validation |
132 | | -- Non-nullable columns added to existing tables require either a `default` value or a `fill_with` backfill expression |
133 | | -- Schemas are validated for constraint consistency before diffing |
134 | | -- The planner validates that column/table names follow the configured naming case |
135 | | - |
136 | | -### SQL Generation Target |
137 | | -SQL generation currently uses PostgreSQL-compatible syntax. The query builder can be extended to support other database systems. |
138 | | - |
139 | | -### JSON Schema Generation |
140 | | -The `vespertide-schema-gen` crate uses `schemars` to generate JSON Schemas from the Rust types. After modifying core data structures, regenerate schemas with: |
141 | | -```bash |
142 | | -cargo run -p vespertide-schema-gen -- --out schemas |
143 | | -``` |
144 | | - |
145 | | -Schema base URL can be overridden via `VESP_SCHEMA_BASE_URL` environment variable. |
146 | | - |
147 | | -## Testing Patterns |
148 | | - |
149 | | -- Tests use helper functions like `col()` and `table()` to reduce boilerplate |
150 | | -- Use `rstest` for parameterized tests (common in planner/query crates) |
151 | | -- Use `serial_test::serial` for tests that modify the filesystem or working directory |
152 | | -- Snapshot testing with `insta` is used in the exporter crate |
153 | | - |
154 | | -## Limitations |
155 | | - |
156 | | -- YAML loading is not implemented (templates can be generated but not parsed) |
157 | | -- Runtime migration executor (`run_migrations`) in `vespertide-macro` is not implemented |
158 | | -- SQL generation currently uses PostgreSQL-compatible syntax |
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Vespertide is a Rust workspace for defining database schemas in JSON/YAML and generating migration plans and SQL from model diffs. It enables declarative schema management by comparing the current model state against a baseline reconstructed from applied migrations. |
| 8 | + |
| 9 | +The workspace uses Rust **edition 2024**. |
| 10 | + |
| 11 | +## Build and Test Commands |
| 12 | + |
| 13 | +```bash |
| 14 | +# Build the entire workspace |
| 15 | +cargo build |
| 16 | + |
| 17 | +# Run all tests |
| 18 | +cargo test |
| 19 | + |
| 20 | +# Run tests for a specific crate |
| 21 | +cargo test -p vespertide-core |
| 22 | +cargo test -p vespertide-planner |
| 23 | + |
| 24 | +# Run a single test |
| 25 | +cargo test -p vespertide-planner test_name |
| 26 | + |
| 27 | +# Run tests with output shown |
| 28 | +cargo test -- --nocapture |
| 29 | + |
| 30 | +# Format code |
| 31 | +cargo fmt |
| 32 | + |
| 33 | +# Lint (important: use all targets and features) |
| 34 | +cargo clippy --all-targets --all-features |
| 35 | + |
| 36 | +# Regenerate JSON schemas |
| 37 | +cargo run -p vespertide-schema-gen -- --out schemas |
| 38 | + |
| 39 | +# Snapshot testing (exporter crate) |
| 40 | +cargo insta test -p vespertide-exporter |
| 41 | +cargo insta accept |
| 42 | + |
| 43 | +# Run CLI commands (use -p vespertide-cli) |
| 44 | +cargo run -p vespertide-cli -- init |
| 45 | +cargo run -p vespertide-cli -- new user |
| 46 | +cargo run -p vespertide-cli -- diff |
| 47 | +cargo run -p vespertide-cli -- sql |
| 48 | +cargo run -p vespertide-cli -- revision -m "message" |
| 49 | +cargo run -p vespertide-cli -- status |
| 50 | +cargo run -p vespertide-cli -- log |
| 51 | +cargo run -p vespertide-cli -- export --orm seaorm |
| 52 | +``` |
| 53 | + |
| 54 | +## Architecture |
| 55 | + |
| 56 | +### Core Data Flow |
| 57 | + |
| 58 | +1. **Schema Definition**: Users define tables in JSON files (`TableDef`) in the `models/` directory |
| 59 | +2. **Baseline Reconstruction**: Applied migrations are replayed to rebuild the baseline schema |
| 60 | +3. **Diffing**: Current models are compared against the baseline to compute changes |
| 61 | +4. **Planning**: Changes are converted into a `MigrationPlan` with versioned actions |
| 62 | +5. **SQL Generation**: Migration actions are translated into SQL statements |
| 63 | + |
| 64 | +### Crate Responsibilities |
| 65 | + |
| 66 | +- **vespertide-core**: Data structures (`TableDef`, `ColumnDef`, `ColumnType`, `MigrationAction`, `MigrationPlan`, constraints, indexes) |
| 67 | + - `ColumnType` enum with `Simple(SimpleColumnType)` and `Complex(ComplexColumnType)` variants |
| 68 | + - `ColumnType::to_sql()` and `ColumnType::to_rust_type()` methods for type conversion |
| 69 | +- **vespertide-planner**: |
| 70 | + - `schema_from_plans()`: Replays applied migrations to reconstruct baseline schema |
| 71 | + - `diff_schemas()`: Compares two schemas and generates migration actions |
| 72 | + - `plan_next_migration()`: Combines baseline reconstruction + diffing to create the next migration |
| 73 | + - `apply_action()`: Applies a single migration action to a schema (used during replay) |
| 74 | + - `validate_*()`: Validates schemas and migration plans |
| 75 | +- **vespertide-query**: Converts `MigrationAction` → SQL with bind parameters |
| 76 | + - Uses `ColumnType::to_sql()` method for SQL type conversion |
| 77 | +- **vespertide-config**: Manages `vespertide.json` (models/migrations directories, naming case preferences) |
| 78 | +- **vespertide-cli**: Command-line interface implementation |
| 79 | +- **vespertide-exporter**: Exports schemas to other formats (e.g., SeaORM entities) |
| 80 | + - Uses `ColumnType::to_rust_type(nullable)` method for Rust type conversion |
| 81 | +- **vespertide-schema-gen**: Generates JSON Schema files for validation |
| 82 | +- **vespertide-loader**: Loads migrations and models from filesystem (JSON/YAML) |
| 83 | +- **vespertide-naming**: Naming conventions and case conversion helpers |
| 84 | +- **vespertide-macro**: Compile-time migration macro (`vespertide_migration!`) for SeaORM |
| 85 | +- **vespertide**: Main crate that re-exports vespertide-core and vespertide-macro |
| 86 | + |
| 87 | +### Key Architectural Patterns |
| 88 | + |
| 89 | +**Migration Replay Pattern**: The planner doesn't store a "current database state" - it reconstructs it by replaying all applied migrations in order. This ensures the baseline is always derivable from the migration history. |
| 90 | + |
| 91 | +**Declarative Diffing**: Users declare the desired end state in model files. The diff engine compares this against the reconstructed baseline to compute necessary changes. |
| 92 | + |
| 93 | +**Action-Based Migrations**: All changes are expressed as typed `MigrationAction` enums (CreateTable, AddColumn, ModifyColumnType, etc.) rather than raw SQL. SQL generation happens in a separate layer. |
| 94 | + |
| 95 | +## Important Implementation Details |
| 96 | + |
| 97 | +### ColumnDef Structure |
| 98 | +When creating `ColumnDef` instances in tests or code, you must initialize ALL fields including the newer inline constraint fields: |
| 99 | + |
| 100 | +```rust |
| 101 | +ColumnDef { |
| 102 | + name: "id".into(), |
| 103 | + r#type: ColumnType::Simple(SimpleColumnType::Integer), |
| 104 | + nullable: false, |
| 105 | + default: None, |
| 106 | + comment: None, |
| 107 | + primary_key: None, // Inline PK declaration |
| 108 | + unique: None, // Inline unique constraint |
| 109 | + index: None, // Inline index creation |
| 110 | + foreign_key: None, // Inline FK definition |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +These inline fields (added recently) allow constraints to be defined directly on columns in addition to table-level `TableConstraint` definitions. |
| 115 | + |
| 116 | +### ColumnType Structure |
| 117 | +`ColumnType` is an enum with two variants: |
| 118 | +- `Simple(SimpleColumnType)`: Built-in types like `Integer`, `Text`, `Boolean`, etc. |
| 119 | +- `Complex(ComplexColumnType)`: Types with parameters like `Varchar { length }` or `Custom { custom_type }` |
| 120 | + |
| 121 | +**Important**: In Rust code, always use `ColumnType::Simple(SimpleColumnType::Integer)` instead of the old `ColumnType::Integer` syntax. The `From` trait is implemented for convenience: |
| 122 | +```rust |
| 123 | +// These are equivalent: |
| 124 | +ColumnType::Simple(SimpleColumnType::Integer) |
| 125 | +SimpleColumnType::Integer.into() |
| 126 | +``` |
| 127 | + |
| 128 | +### ColumnType Methods |
| 129 | +`ColumnType` provides two utility methods: |
| 130 | +- `to_sql()`: Returns the SQL type string (e.g., `"INTEGER"`, `"VARCHAR(255)"`) |
| 131 | +- `to_rust_type(nullable: bool)`: Returns the Rust type string for SeaORM entity generation (e.g., `"i32"` or `"Option<i32>"`) |
| 132 | + |
| 133 | +These methods replace the old standalone functions `column_type_sql()` and `rust_type()`. |
| 134 | + |
| 135 | +### Foreign Key Definition |
| 136 | +Foreign keys can be defined inline on columns via the `foreign_key` field: |
| 137 | + |
| 138 | +```rust |
| 139 | +pub struct ForeignKeyDef { |
| 140 | + pub ref_table: TableName, |
| 141 | + pub ref_columns: Vec<ColumnName>, |
| 142 | + pub on_delete: Option<ReferenceAction>, |
| 143 | + pub on_update: Option<ReferenceAction>, |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +### Migration Plan Validation |
| 148 | +- Non-nullable columns added to existing tables require either a `default` value or a `fill_with` backfill expression |
| 149 | +- Schemas are validated for constraint consistency before diffing |
| 150 | +- The planner validates that column/table names follow the configured naming case |
| 151 | + |
| 152 | +### SQL Generation Target |
| 153 | +SQL generation currently uses PostgreSQL-compatible syntax. The query builder can be extended to support other database systems. |
| 154 | + |
| 155 | +### JSON Schema Generation |
| 156 | +The `vespertide-schema-gen` crate uses `schemars` to generate JSON Schemas from the Rust types. After modifying core data structures, regenerate schemas with: |
| 157 | +```bash |
| 158 | +cargo run -p vespertide-schema-gen -- --out schemas |
| 159 | +``` |
| 160 | + |
| 161 | +Schema base URL can be overridden via `VESP_SCHEMA_BASE_URL` environment variable. |
| 162 | + |
| 163 | +## Testing Patterns |
| 164 | + |
| 165 | +- Tests use helper functions like `col()` and `table()` to reduce boilerplate |
| 166 | +- Use `rstest` for parameterized tests (common in planner/query crates) |
| 167 | +- Use `serial_test::serial` for tests that modify the filesystem or working directory |
| 168 | +- Snapshot testing with `insta` is used in the exporter crate |
| 169 | + |
| 170 | +## Limitations |
| 171 | + |
| 172 | +- YAML loading is not implemented (templates can be generated but not parsed) |
0 commit comments