Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ data/
!package.json
!tsconfig.json
!.vscode/settings.json
# The backend's OpenAPI document — the API contract the frontend types are
# generated from (see backend/export_openapi.py, frontend gen:api script).
!frontend/src/api/openapi.json

# Docker
.dockerignore
Expand Down
49 changes: 49 additions & 0 deletions backend/export_openapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Export the backend's OpenAPI document to the frontend.

The frontend's API types are **generated** from this document (via
``openapi-typescript`` — see ``frontend/package.json``'s ``gen:api`` script),
not hand-written. ``qh`` derives a complete OpenAPI schema — request bodies,
responses and ``components.schemas`` — from :class:`ef.service.EfService`'s
Python type hints, so this file is the single source of truth for the
frontend's view of the API.

Run it whenever the backend API surface changes::

cd backend && python export_openapi.py

It writes ``frontend/src/api/openapi.json``; regenerate the TypeScript types
afterwards with ``cd frontend && pnpm gen:api``.
"""

from __future__ import annotations

import json
from pathlib import Path

from app.main import build_app

#: Where the spec is written — ``frontend/src/api/openapi.json``, resolved
#: relative to this script so it works from any working directory.
SPEC_PATH = (
Path(__file__).resolve().parent.parent
/ "frontend"
/ "src"
/ "api"
/ "openapi.json"
)


def export_openapi(spec_path: Path = SPEC_PATH) -> Path:
"""Build the app, render its OpenAPI document and write it to ``spec_path``.

Returns the path written, for logging / scripting.
"""
spec = build_app().openapi()
spec_path.parent.mkdir(parents=True, exist_ok=True)
spec_path.write_text(json.dumps(spec, indent=2) + "\n")
return spec_path


if __name__ == "__main__":
written = export_openapi()
print(f"Wrote OpenAPI spec to {written}")
14 changes: 8 additions & 6 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"typecheck": "tsc"
"typecheck": "tsc",
"gen:api": "openapi-typescript src/api/openapi.json -o src/api/openapi.d.ts"
},
"dependencies": {
"@zodal/core": "link:../../../i/_zodals/zodal/packages/core",
"@zodal/store": "link:../../../i/_zodals/zodal/packages/store",
"@zodal/ui": "link:../../../i/_zodals/zodal/packages/ui",
"@zodal/ui-shadcn": "link:../../../i/_zodals/zodal-ui-shadcn",
"acture": "^1.2.1",
"acture-hotkeys": "^1.0.0",
"acture-palette-react": "^1.0.0",
Expand All @@ -21,18 +26,15 @@
"react-dom": "^19.0.0",
"tailwind-merge": "^2.6.0",
"zod": "^4.0.0",
"zustand": "^5.0.0",
"@zodal/core": "link:../../../i/_zodals/zodal/packages/core",
"@zodal/ui": "link:../../../i/_zodals/zodal/packages/ui",
"@zodal/store": "link:../../../i/_zodals/zodal/packages/store",
"@zodal/ui-shadcn": "link:../../../i/_zodals/zodal-ui-shadcn"
"zustand": "^5.0.0"
},
"devDependencies": {
"@types/node": "^22.10.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"openapi-typescript": "^7.13.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2",
Expand Down
Loading