Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
de1ec36
dev GORM
2heunxun May 16, 2026
26d9a9b
GORM dev
2heunxun May 16, 2026
dce9edf
GORM dev
2heunxun May 16, 2026
b544ba0
Merge pull request #1 from 2heunxun/PR
2heunxun May 21, 2026
965472c
Refactoring: ReModeling Page Vespertide
2heunxun May 21, 2026
8aca2aa
hotfix : GORM bug fix
2heunxun May 23, 2026
1e9729f
Merge upstream/main into main (114 commits)
2heunxun Jun 27, 2026
4f538b8
feat : Django 지원
2heunxun Jul 11, 2026
0d3ab08
feat : Django 지원
2heunxun Jul 11, 2026
831633d
CI : CI Test fix
2heunxun Jul 11, 2026
7769c36
CI : CI Test fix
2heunxun Jul 11, 2026
310e91e
CI : CI Test fix
2heunxun Jul 11, 2026
4c1be50
test: add coverage for Django/GORM/CLI uncovered branches to reach 100%
2heunxun Jul 11, 2026
cc2114c
ci: bust musl-contaminated toolchain cache across all ubuntu jobs
2heunxun Jul 11, 2026
e85d6cc
fix(deps): update sea-orm rc.40→rc.42 to remove ouroboros advisory
2heunxun Jul 11, 2026
84cc342
test(exporter): add coverage for remaining 27 uncovered lines to reac…
2heunxun Jul 11, 2026
af5d986
fix(arbitrary): filter pg reserved keywords from bare-ident DEFAULT g…
2heunxun Jul 11, 2026
b31bd78
CI : CI Test fix
2heunxun Jul 12, 2026
8bdd516
CI : CI Test fix
2heunxun Jul 12, 2026
584b04b
test(coverage): fix 3 uncovered regions to restore 100% line coverage
2heunxun Jul 12, 2026
29eb380
CI : CI Test fix
2heunxun Jul 25, 2026
a573bfe
test(coverage): restructure remaining 99.96% gaps for tarpaulin attri…
2heunxun Jul 25, 2026
510e868
test(coverage): close remaining 99.96% gaps with verified fixes
2heunxun Jul 25, 2026
0c2a0a3
fmt: run cargo fmt on erd svg_coverage test and gorm render_enum
2heunxun Jul 25, 2026
e135c4d
feat(django): recognize M2M junction tables via render_entity_with_sc…
2heunxun Jul 26, 2026
8a0754e
feat(django,gorm): surface composite (multi-column) FK relations
2heunxun Jul 26, 2026
cde7a34
fix(gorm): emit reverse has-many relation for self-referencing FKs
2heunxun Jul 26, 2026
101c7b5
test(exporter): wire Django & GORM into the shared cross-ORM snapshot…
2heunxun Jul 26, 2026
cdba876
feat(config): add DjangoConfig/GormConfig customization sections
2heunxun Jul 26, 2026
9fa9bfd
test(cli): add clean_export_dir regression test for Django
2heunxun Jul 26, 2026
5e7e4d3
docs: reflect JPA/GORM/Django in README and AGENTS.md
2heunxun Jul 26, 2026
8b1a800
test(exporter): close coverage gaps introduced by items 1-2 (99.92% o…
2heunxun Jul 26, 2026
593e1d5
test(cli): close remaining coverage gap in export.rs (100% verified)
2heunxun Jul 26, 2026
6391691
fix(django): represent composite PKs with Django 5.2's CompositePrima…
2heunxun Jul 26, 2026
4f14e21
chore: revert unrelated CI cache-key tweak and untrack .idea
2heunxun Aug 5, 2026
79e96f2
refactor(landing): CodeTabs를 CSS-only 라디오 탭으로 전환해 use client와 fallbac…
2heunxun Aug 5, 2026
461fb90
refactor(landing): CopyInstall에 인라인 D2Coding 스타일 대신 등록된 typography 프리…
2heunxun Aug 5, 2026
cb591ac
refactor(landing): SectionHead 제목-강조 간격을 텍스트 공백 대신 gap으로 구현
2heunxun Aug 5, 2026
98f30ee
refactor(landing): eyebrow typography 등록, pill radius를 %로, 불필요한 gap="…
2heunxun Aug 5, 2026
5cb7551
refactor(landing):히어로 섹션 마크업 정리 (span 중복 제거, mb를 Box로, stretch 정렬),He…
2heunxun Aug 5, 2026
ea8a74d
Del : No using Markdown
2heunxun Aug 8, 2026
2c3ec0d
refactor(landing): PR 리뷰 피드백 반영 (피처 카드 모바일 패딩, Examples Flex->VStack,…
2heunxun Aug 8, 2026
a57856d
Del : 불필요한 파일 삭제
2heunxun Aug 15, 2026
2d4d8f9
Del : 불필요한 파일 삭제
2heunxun Aug 15, 2026
db1fb1d
test(exporter): 중복된 render_schema_gorm_and_django 테스트 제거
2heunxun Aug 15, 2026
e023e93
fix(exporter): schema_scenario catch-all에서 tarpaulin exclude 제거
2heunxun Aug 15, 2026
9bf4474
feat(config,gorm): infer GORM package name from export directory
2heunxun Aug 19, 2026
be24f95
Merge remote-tracking branch 'origin/main'
2heunxun Aug 19, 2026
e2a94d4
Merge upstream/main (Prisma exporter) into pr-169
2heunxun Aug 19, 2026
8736469
feat(config,gorm): infer GORM package name from export directory
2heunxun Aug 19, 2026
9a7feb8
fmt: run cargo fmt on config.rs after cherry-pick
2heunxun Aug 19, 2026
d28a242
Merge branch 'pr-169'
2heunxun Aug 19, 2026
2512f36
fix(landing): move code-tab visibility rules onto target elements
2heunxun Aug 20, 2026
016a6a9
fix(landing): address PR review feedback on code-tabs/copy-install
2heunxun Aug 20, 2026
fb74925
Merge remote-tracking branch 'upstream/main'
2heunxun Aug 27, 2026
0fd08c1
Merge remote-tracking branch 'upstream/main'
yyuneu Sep 15, 2026
55774bb
fix(exporter): GORM 멀티 테이블 export
yyuneu Sep 15, 2026
ac93a37
refactor(exporter): gorm 모듈을 mod/render/types/enums로 분할
yyuneu Sep 15, 2026
6ee92b7
test(exporter): Django/GORM 스냅샷의 assertion_line 메타데이터 제거
yyuneu Sep 15, 2026
2ccce55
chore(changepacks): Django/GORM 익스포터 descriptor 추가
yyuneu Sep 15, 2026
253d630
chore: CLAUDE.md 개행 되돌림
yyuneu Sep 15, 2026
509ff1e
docs(exporter): GORM/Django 서술과 8-ORM 문구 정리
yyuneu Sep 15, 2026
cb4a905
test(exporter): rstest 케이스·dispatch arm 순서를 Orm 선언 순서로 통일
yyuneu Sep 15, 2026
31050d6
refactor(exporter): to_pascal_case·reference_action_str를 공용 함수에 위임
yyuneu Sep 15, 2026
97f6290
test(exporter): ON UPDATE를 함께 지정한 FK 시나리오를 공유 스위트에 추가
yyuneu Sep 15, 2026
fd3a08e
test(exporter): 모듈-로컬 스냅샷 스위트 제거
yyuneu Sep 15, 2026
1d177f9
fix(exporter): GORM enum 상수 이름 산어화와 선언 중복 제거
yyuneu Sep 15, 2026
fe87800
fix(exporter): Django 무명 unique 제약에 SQL 계층과 같은 이름 부여
yyuneu Sep 15, 2026
355bd6c
fix(exporter): Django Meta가 DB 컬럼명 대신 필드명을 참조
yyuneu Sep 15, 2026
b992b2b
test(cli): GORM 패키지명·Django app_label 경로를 스냅샷으로 고정
yyuneu Sep 15, 2026
c207e87
refactor(exporter): GORM 관계명 접미사를 공용 claim_binding에 위임
yyuneu Sep 15, 2026
27c6c0b
refactor(core): SimpleColumnKind·ReferenceActionKind 제거와 non_exhausti…
yyuneu Sep 15, 2026
b067028
refactor(exporter): ComplexColumnType 가드를 다른 백엔드와 같은 형태로
yyuneu Sep 15, 2026
ae17b95
refactor(config): GORM config 섹션 제거하고 패키지명은 export 디렉터리에서만 추론
yyuneu Sep 15, 2026
fb07aad
refactor(exporter): GORM·Django의 테스트 전용 to_pascal_case 쉼 제거
yyuneu Sep 15, 2026
321186f
fix(exporter): Django enum 라벨을 Django 자동 생성에 맡김
yyuneu Sep 15, 2026
f34106d
refactor(exporter,cli): 사용처 없는 항목과 항상 참인 술어 제거
yyuneu Sep 15, 2026
e8a9a65
refactor(exporter): Django enum 멤버명을 공용 to_screaming_snake_case에 위임
yyuneu Sep 17, 2026
b15a534
docs: 커버리지 고고학 주석을 동작 서술로 교체
yyuneu Sep 15, 2026
d61e39a
refactor(exporter): GORM·Django의 제약 스캔과 이름 헬퍼를 공용으로
yyuneu Sep 17, 2026
5f4ff12
fix(exporter): GORM 식별자를 export 가능한 이름으로
yyuneu Sep 17, 2026
75afa70
fix(exporter): GORM 필드명 충돌을 하나의 점유 집합으로 방지
yyuneu Sep 17, 2026
0a03eb9
fix(exporter): GORM 태그의 default·char·PG 네이티브 타입
yyuneu Sep 17, 2026
461b8fb
fix(exporter): GORM unique·index 이름을 SQL 계층과 일치
yyuneu Sep 17, 2026
6d3d174
test(exporter): GORM contains 단언을 공유 스냅샷으로
yyuneu Sep 17, 2026
276ee03
fix(exporter): Django FK 컬럼의 primary_key와 OneToOneField
yyuneu Sep 17, 2026
1c59c1a
fix(exporter): 파이썬 3개 백엔드의 키워드 이스케이프와 Django 필드명 규칙
yyuneu Sep 17, 2026
9881bca
fix(exporter): Django M2M 필드명 산어화와 컬럼 충돌 방지
yyuneu Sep 17, 2026
054689a
fix(exporter): Django 모델을 비관리로, JSONB는 JSONField, unique 이름은 SQL 계층과 일치
yyuneu Sep 17, 2026
b161c4e
test(exporter): Django contains 단언을 공유 스냅샷으로
yyuneu Sep 17, 2026
880cccf
chore(exporter): 벤치 ORM 목록을 8개로
yyuneu Sep 17, 2026
4fe777f
fix(cli): 파이썬 export 파일명을 모듈명으로
yyuneu Sep 17, 2026
bed34c9
style(exporter): SQLModel의 common import 병합
yyuneu Sep 17, 2026
1fc93c7
docs(exporter): GORM 식별자·태그 규칙과 파이썬 백엔드 변경을 기재
yyuneu Sep 17, 2026
3becfab
fix(exporter): 여러 테이블이 공유하는 Django enum 클래스를 모델명으로 한정
yyuneu Sep 17, 2026
a3befa9
fix(cli): GORM·Django를 단일 파일로 출력
yyuneu Sep 17, 2026
dffa84c
fix(exporter): GORM 역방향 관계를 공용 스캔으로
yyuneu Sep 17, 2026
4b526fd
fix(exporter): Django 시스템 체크가 거부하는 모델명·on_delete·M2M 출력 수정
yyuneu Sep 17, 2026
3f44c70
style(exporter): GORM 출력을 gofmt 레이아웃으로
yyuneu Sep 17, 2026
1efa785
docs: exporter 스냅샷 개수 갱신
yyuneu Sep 17, 2026
65554fd
test(cli): ERD 테스트를 upstream과 동일하게
yyuneu Sep 17, 2026
4f396b1
test(cli): export 통합 테스트의 생성물 contains 단언 제거
yyuneu Sep 17, 2026
28147ef
refactor(exporter): GORM 패키지명 추론을 exporter로 이동
yyuneu Sep 17, 2026
d5aaaae
refactor(exporter): GORM·Django 테스트 배치와 시그니처 정리
yyuneu Sep 17, 2026
1e7a042
fix(exporter): 생성 코드의 문자열 리터럴 이스케이프
yyuneu Sep 17, 2026
93b3ce7
fix(exporter): 한 파일 안의 최상위 이름 충돌 방지
yyuneu Sep 17, 2026
c5d6965
fix(exporter): Django FK가 참조 컬럼을 따르도록
yyuneu Sep 17, 2026
8bb2595
fix(exporter): Django 시스템 체크가 거부하는 on_delete·인덱스 이름·필드명
yyuneu Sep 17, 2026
ab81cce
fix(exporter): GORM belongs-to를 포인터로
yyuneu Sep 17, 2026
2c92b14
docs: 백엔드 목록과 AGENTS의 낡은 서술 정리
yyuneu Sep 17, 2026
6dc4179
docs(landing): ORM 목록에 Django 등 전체 백엔드 표기
yyuneu Sep 17, 2026
52c6ed6
refactor(exporter): 단일 컬럼 FK 스캔을 하나로
yyuneu Sep 17, 2026
355fea6
fix(exporter): Django FK의 attname 충돌과 unique 아닌 참조 컬럼
yyuneu Sep 17, 2026
9869fa9
fix(exporter): PK가 곧 FK인 one-to-one의 역방향 관계
yyuneu Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_5O2QrCyrfNoD1wBL403qE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes": {"crates/vespertide-cli/Cargo.toml": "Minor", "crates/vespertide-config/Cargo.toml": "Minor", "crates/vespertide-core/Cargo.toml": "Minor", "crates/vespertide-exporter/Cargo.toml": "Minor", "crates/vespertide-loader/Cargo.toml": "Minor", "crates/vespertide-lsp/Cargo.toml": "Minor", "crates/vespertide-macro/Cargo.toml": "Minor", "crates/vespertide-naming/Cargo.toml": "Minor", "crates/vespertide-planner/Cargo.toml": "Minor", "crates/vespertide-query/Cargo.toml": "Minor", "crates/vespertide/Cargo.toml": "Minor"}, "note": "GORM(Go)·Django(Python) 익스포터를 7·8번째 ORM 백엔드로 추가. `Orm`이 exhaustive pub enum이라 `Orm::Gorm`/`Orm::Django` 추가가 0.x 기준 breaking이고, vespertide-config에 `django` 설정 섹션이, vespertide-cli에 `export --orm gorm|django` 경로가 함께 들어간다(스키마 전체를 `models.go`·`models.py` 한 파일로 쓴다). 기존 파이썬 백엔드(SQLAlchemy·SQLModel)도 파이썬 키워드 컬럼명을 이스케이프하고 export 파일명을 파이썬 모듈명으로 쓸 수 있게 바꾸며, Django 모델은 `managed = False`로 나간다. vespertide-core의 `SimpleColumnType`·`ReferenceAction`에서 `#[non_exhaustive]`를 제거해 downstream이 exhaustive match를 쓸 수 있게 한다(기존 `_` arm은 `unreachable_patterns` 경고가 된다). published 크레이트를 전부 같은 Minor로 올리는 이유는 #185·#186과 동일하다: [workspace.dependencies]의 `=` 핀으로 물려 있어 일부만 올리면 핀과 크레이트 버전이 어긋나 resolve가 깨진다.", "date": "2026-09-15T07:40:25.0000000Z"}
18 changes: 9 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ vespertide/
│ ├── vespertide-planner/ # Schema diffing, baseline reconstruction, validation
│ ├── vespertide-query/ # SQL generation (Postgres/MySQL/SQLite)
│ ├── vespertide-cli/ # CLI commands: init, diff, sql, revision, export
│ ├── vespertide-exporter/ # ORM codegen: SeaORM, SQLAlchemy, SQLModel, JPA, Prisma, Drizzle
│ ├── vespertide-exporter/ # ORM codegen: SeaORM, SQLAlchemy, SQLModel, JPA, Prisma, Drizzle, GORM, Django
│ ├── vespertide-loader/ # Filesystem loading of models/migrations
│ ├── vespertide-config/ # vespertide.json configuration
│ ├── vespertide-lsp/ # Language server: 13 LSP capabilities + HS-7~11 caching
Expand Down Expand Up @@ -48,7 +48,7 @@ vespertide/
| Schema diffing | `vespertide-planner/src/diff/` | topological sort for FK deps |
| SQL generation | `vespertide-query/src/sql/` | One file per action type |
| CLI commands | `vespertide-cli/src/commands/` | `cmd_*` functions |
| ORM export | `vespertide-exporter/src/{seaorm,sqlalchemy,sqlmodel,jpa,prisma,drizzle}/` | Backend-specific generators |
| ORM export | `vespertide-exporter/src/{seaorm,sqlalchemy,sqlmodel,jpa,prisma,drizzle,gorm,django}/` | Backend-specific generators |
| Compile-time macro | `vespertide-macro/src/lib.rs` | `vespertide_migration!` proc macro |
| **LSP RingCache (HS-7~11)** | `vespertide-lsp/src/cache.rs` | Generic ring-buffer LRU shared across symbols/diagnostics/drift/semantic-token caches |
| **LSP drift cache** | `vespertide-lsp/src/drift/cache.rs` | HS-10 drift cache implementation |
Expand Down Expand Up @@ -103,7 +103,7 @@ When constructing struct literals (e.g. `TableDef { name: ... }`), prefer `.into
from string literals over the explicit constructor for terseness.

### `#[non_exhaustive]` Structs (0.2.0+)
`VespertideConfig`, `SeaOrmConfig`, `MigrationOptions` are `#[non_exhaustive]`:
`VespertideConfig`, `SeaOrmConfig`, `DjangoConfig`, `MigrationOptions` are `#[non_exhaustive]`:
external callers MUST construct via `..Default::default()` or the provided
constructor.

Expand Down Expand Up @@ -170,7 +170,7 @@ See `docs/clippy-allow-audit.md` for the full audit history.
| `QueryError::Other(...)` in new code | Emits deprecation warning. Use `SchemaError` / `InvalidColumnType` / `BackendError` / `UnsupportedAction` |
| Exhaustive struct literal for `MigrationOptions` / `VespertideConfig` | `#[non_exhaustive]` — use `..Default::default()` |
| Comparing newtype with `String::eq(&name.to_string(), "user")` | `TableName: PartialEq<&str>` — use `name == "user"` directly |
| Per-ORM exporter snapshot test (single ORM) | Use the 6-ORM `orm_cases!` macro; snapshots must cross-compare all ORMs |
| Per-ORM exporter snapshot test (single ORM) | Use the 8-ORM `orm_cases!` macro; snapshots must cross-compare all ORMs |

## COMMANDS

Expand Down Expand Up @@ -235,7 +235,7 @@ Files near the ceiling (next split candidates — line counts as of the
| `query/src/sql/delete_column/mod.rs` | 1138 | prod+inline-tests (≤1200) | DROP COLUMN with SQLite rebuild |
| `query/src/sql/add_constraint/mod.rs` | 1138 | prod+inline-tests (≤1200) | ADD CONSTRAINT |
| `core/src/schema/table/tests/mod.rs` | 1137 | test-file (≤1200) | Table normalization tests |
| `exporter/src/tests/fixtures/mod.rs` | 1146 | test-file (≤1200) | Shared 6-ORM fixture schemas |
| `exporter/src/tests/fixtures/mod.rs` | 1173 | test-file (≤1200) | Shared 8-ORM fixture schemas |
| `planner/src/validate/check_strengthening.rs` | 1121 | prod+inline-tests (≤1200) | CHECK strengthening analysis |
| `query/src/sql/helpers.rs` | 1109 | prod+inline-tests (≤1200) | Identifier quoting / type-cast helpers |
| `lsp/src/code_actions.rs` | 1107 | prod+inline-tests (≤1200) | LSP code actions (incl. CHECK BETWEEN-swap) |
Expand Down Expand Up @@ -369,7 +369,7 @@ alongside what the dialect emits *in place of* the missing construct.
**How many cases:**

Where the axis has a documented matrix — `vespertide-query`'s
`{PG, MySQL, SQLite}` triple and the exporter's six-ORM `orm_cases!` — fan out
`{PG, MySQL, SQLite}` triple and the exporter's eight-ORM `orm_cases!` — fan out
**always**, even when every case renders the same bytes: identity across the
matrix is itself the assertion (`uniform_sql_is_emitted_byte_for_byte`), and a
lone single-backend snapshot is a fault (`vespertide-query/AGENTS.md`).
Expand Down Expand Up @@ -397,14 +397,14 @@ fn create_table_snapshot(#[case] backend: DatabaseBackend) {
```

This is the same pattern used by `vespertide-query` (3 backends, 564 snapshots)
and `vespertide-exporter` (6 ORMs via `Orm` enum, 414 cross-ORM snapshots). When
and `vespertide-exporter` (8 ORMs via `Orm` enum, 632 cross-ORM snapshots). When
adding a new backend / ORM / format, the change is **one `#[case::name(Value)]`
line**.

### Exporter snapshots MUST cover ALL ORMs (no per-ORM snapshots)
Every `vespertide-exporter` snapshot test MUST be written through the shared `orm_cases!` rstest macro in `crates/vespertide-exporter/src/tests/mod.rs`, which renders each fixture for **all six ORMs** (`Orm::SeaOrm`, `Orm::SqlAlchemy`, `Orm::SqlModel`, `Orm::Jpa`, `Orm::Prisma`, `Orm::Drizzle`). A new export scenario = ONE fixture + ONE `orm_cases!(...)` line, producing exactly six snapshots (one per ORM) in the single shared `crates/vespertide-exporter/src/tests/snapshots/` directory.
Every `vespertide-exporter` snapshot test MUST be written through the shared `orm_cases!` rstest macro in `crates/vespertide-exporter/src/tests/mod.rs`, which renders each fixture for **all eight ORMs** (`Orm::SeaOrm`, `Orm::SqlAlchemy`, `Orm::SqlModel`, `Orm::Jpa`, `Orm::Prisma`, `Orm::Drizzle`, `Orm::Gorm`, `Orm::Django`). A new export scenario = ONE fixture + ONE `orm_cases!(...)` line, producing exactly eight snapshots (one per ORM) in the single shared `crates/vespertide-exporter/src/tests/snapshots/` directory.

FORBIDDEN: per-ORM `#[test]` snapshot functions inside `src/seaorm/`, `src/sqlalchemy/`, `src/sqlmodel/`, `src/jpa/`, `src/prisma/`, `src/drizzle/`, or any `snapshots/` directory other than `src/tests/snapshots/`. A scenario snapshotted for only one ORM is a defect — ORM output must always be cross-compared across all six. When adding a new ORM the change is a single `#[case::<orm>(Orm::<Variant>)]` line in the macro, never a new per-ORM test.
FORBIDDEN: per-ORM `#[test]` snapshot functions inside `src/seaorm/`, `src/sqlalchemy/`, `src/sqlmodel/`, `src/jpa/`, `src/prisma/`, `src/drizzle/`, `src/gorm/`, `src/django/`, or any `snapshots/` directory other than `src/tests/snapshots/`. A scenario snapshotted for only one ORM is a defect — ORM output must always be cross-compared across all eight. When adding a new ORM the change is a single `#[case::<orm>(Orm::<Variant>)]` line in the macro, never a new per-ORM test.

Exception: an entry point that exists in only one backend (Prisma's single-file `render_schema`, which deduplicates enums globally; Drizzle's dialect-aware `render_schema`, whose axis is the SQL dialect rather than the ORM) is not a cross-ORM scenario, so its snapshot tests live as inline tests of that module — with the snapshot files still written to the shared `src/tests/snapshots/` via `with_settings!(snapshot_path => ...)`.

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ Declarative database schema management. Define your schemas in JSON, and Vespert
- **Enum Types**: Native string enums and integer enums (no migration needed for new values)
- **Zero-Runtime Migrations**: Compile-time macro generates database-specific SQL
- **JSON Schema Validation**: Ships with JSON Schemas for IDE autocompletion and validation
- **ORM Export**: Export schemas to SeaORM, SQLAlchemy, SQLModel, JPA, Prisma, Drizzle
- **ORM Export**: Export schemas to SeaORM, SQLAlchemy, SQLModel, JPA, Prisma, Drizzle, GORM, Django
- **Language Server**: First-class editor support via the bundled `vespertide-lsp` — see [LSP Features](#lsp-features) below

## What's new in 0.2.0

API stability pass with a byte-identical JSON wire format — existing models and migration files load unchanged.

- **Newtype identifiers**: `TableName`, `ColumnName`, `IndexName` in `vespertide-core` (`crates/vespertide-core/src/schema/names.rs`). `#[serde(transparent)]` keeps JSON identical; `Deref<Target = str>` means most call sites need no edit.
- **`#[non_exhaustive]` configs**: `VespertideConfig`, `SeaOrmConfig`, and `MigrationOptions` must be built with `..Default::default()` (or `MigrationOptions::new()`), so future fields don't break semver.
- **`#[non_exhaustive]` configs**: `VespertideConfig`, `SeaOrmConfig`, `DjangoConfig`, and `MigrationOptions` must be built with `..Default::default()` (or `MigrationOptions::new()`), so future fields don't break semver.
- **Decomposed `QueryError`**: new `InvalidColumnType`, `SchemaError`, `BackendError`, and `UnsupportedAction` variants. `QueryError::Other(String)` is `#[deprecated]` but still compiles.
- **Cloneable `MigrationError`**: backed by `Arc<dyn Error>`, so retry loops can re-emit errors without re-running the planner.
- **Faster LSP**: every editor hot path (diagnostics, symbols, drift) is now `RingCache`-backed in `vespertide-lsp`. No API change; -99% latency on the synthetic `tools/lsp-profile/` workload.
Expand Down Expand Up @@ -245,6 +245,8 @@ vespertide export --orm sqlmodel # Python - SQLModel (FastAPI)
vespertide export --orm jpa # Java - JPA/Hibernate entities
vespertide export --orm prisma # Prisma - schema.prisma models
vespertide export --orm drizzle # TypeScript - Drizzle ORM (pg/mysql/sqlite files)
vespertide export --orm gorm # Go - GORM models (models.go)
vespertide export --orm django # Python - Django models (models.py)
```

## Runtime Migrations (Macro)
Expand Down
16 changes: 15 additions & 1 deletion apps/landing/devup.json
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,21 @@
},
null,
null
]
],
"code": {
"fontFamily": "D2Coding",
"fontWeight": 400,
"fontSize": "13px",
"lineHeight": 1.5,
"letterSpacing": "0em"
},
"eyebrow": {
"fontFamily": "D2Coding",
"fontWeight": 400,
"fontSize": "11px",
"lineHeight": 1.4,
"letterSpacing": "0.14em"
}
}
}
}
238 changes: 238 additions & 0 deletions apps/landing/src/app/_components/code-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { Box, Text } from '@devup-ui/react'

import { CodeWindow, HighlightedCode } from './code-window'

export interface CodeExample {
key: string
label: string
file: string
html: string
}

export type CodeExampleQuad = [CodeExample, CodeExample, CodeExample, CodeExample]

// devup-ui extracts `selectors` strings at build time, so each of the 4
// fixed tabs is written out literally rather than generated by a .map()
// over a runtime key — a template-literal selector key can't be statically
// extracted into CSS.
//
// Each target element (label / title / panel) declares its own "when am I
// shown" condition anchored on the checkbox's id (`#code-tab-N:checked ~ *
// &`), rather than the checkbox declaring rules for elements elsewhere in
// the tree. The checkbox's own `:checked` state isn't observable from the
// checkbox's position in isolation, so ownership of the visibility rule
// belongs on the element it affects.
export function CodeTabs({ examples: [a, b, c, d] }: { examples: CodeExampleQuad }) {
return (
<Box>
<Box
as="input"
boxSize="1px"
defaultChecked
id="code-tab-0"
name="code-tab"
opacity="0"
overflow="hidden"
position="absolute"
type="radio"
/>
<Box
as="input"
boxSize="1px"
id="code-tab-1"
name="code-tab"
opacity="0"
overflow="hidden"
position="absolute"
type="radio"
/>
<Box
as="input"
boxSize="1px"
id="code-tab-2"
name="code-tab"
opacity="0"
overflow="hidden"
position="absolute"
type="radio"
/>
<Box
as="input"
boxSize="1px"
id="code-tab-3"
name="code-tab"
opacity="0"
overflow="hidden"
position="absolute"
type="radio"
/>
<CodeWindow
tabs={
<>
<Box
as="label"
border="1px solid transparent"
borderRadius="4px"
color="$caption"
cursor="pointer"
htmlFor="code-tab-0"
px="12px"
py="6px"
selectors={{
'#code-tab-0:checked ~ * &': {
bg: '$containerBackground',
border: '1px solid $border',
color: '$title',
},
'#code-tab-0:focus-visible ~ * &': {
boxShadow: '0 0 0 2px $vespertidePrimary',
},
}}
transition="color .15s, background .15s"
typography="eyebrow"
>
{a.label}
</Box>
<Box
as="label"
border="1px solid transparent"
borderRadius="4px"
color="$caption"
cursor="pointer"
htmlFor="code-tab-1"
px="12px"
py="6px"
selectors={{
'#code-tab-1:checked ~ * &': {
bg: '$containerBackground',
border: '1px solid $border',
color: '$title',
},
'#code-tab-1:focus-visible ~ * &': {
boxShadow: '0 0 0 2px $vespertidePrimary',
},
}}
transition="color .15s, background .15s"
typography="eyebrow"
>
{b.label}
</Box>
<Box
as="label"
border="1px solid transparent"
borderRadius="4px"
color="$caption"
cursor="pointer"
htmlFor="code-tab-2"
px="12px"
py="6px"
selectors={{
'#code-tab-2:checked ~ * &': {
bg: '$containerBackground',
border: '1px solid $border',
color: '$title',
},
'#code-tab-2:focus-visible ~ * &': {
boxShadow: '0 0 0 2px $vespertidePrimary',
},
}}
transition="color .15s, background .15s"
typography="eyebrow"
>
{c.label}
</Box>
<Box
as="label"
border="1px solid transparent"
borderRadius="4px"
color="$caption"
cursor="pointer"
htmlFor="code-tab-3"
px="12px"
py="6px"
selectors={{
'#code-tab-3:checked ~ * &': {
bg: '$containerBackground',
border: '1px solid $border',
color: '$title',
},
'#code-tab-3:focus-visible ~ * &': {
boxShadow: '0 0 0 2px $vespertidePrimary',
},
}}
transition="color .15s, background .15s"
typography="eyebrow"
>
{d.label}
</Box>
</>
}
title={
<>
<Text
as="span"
display="none"
id="code-title-0"
selectors={{ '#code-tab-0:checked ~ * &': { display: 'block' } }}
>
{a.file}
</Text>
<Text
as="span"
display="none"
id="code-title-1"
selectors={{ '#code-tab-1:checked ~ * &': { display: 'block' } }}
>
{b.file}
</Text>
<Text
as="span"
display="none"
id="code-title-2"
selectors={{ '#code-tab-2:checked ~ * &': { display: 'block' } }}
>
{c.file}
</Text>
<Text
as="span"
display="none"
id="code-title-3"
selectors={{ '#code-tab-3:checked ~ * &': { display: 'block' } }}
>
{d.file}
</Text>
</>
}
>
<Box
display="none"
id="code-panel-0"
selectors={{ '#code-tab-0:checked ~ * &': { display: 'block' } }}
>
<HighlightedCode html={a.html} />
</Box>
<Box
display="none"
id="code-panel-1"
selectors={{ '#code-tab-1:checked ~ * &': { display: 'block' } }}
>
<HighlightedCode html={b.html} />
</Box>
<Box
display="none"
id="code-panel-2"
selectors={{ '#code-tab-2:checked ~ * &': { display: 'block' } }}
>
<HighlightedCode html={c.html} />
</Box>
<Box
display="none"
id="code-panel-3"
selectors={{ '#code-tab-3:checked ~ * &': { display: 'block' } }}
>
<HighlightedCode html={d.html} />
</Box>
</CodeWindow>
</Box>
)
}
Loading