π docs: modern README with animated SVG hero, pipeline, benchmark, feature matrix
swadhinbiswas committed 2 months ago
4322714
1 parent 76a363c
commit 4322714
5 files changed
+708 -449
Showing 5 changed files+708-449
βΌREADME.md
+190-449
| @@ -1,443 +1,234 @@ | ||
1 | 1 | <p align="center"> |
2 | - <a href="https://github.com/swadhinbiswas/JustAPI"><img src="https://img.shields.io/badge/JustAPI-Rust%20Powered-E6522C?style=for-the-badge&logo=rust&logoColor=white" alt="JustAPI" width="400"></a> | |
3 | -</p> | |
4 | -<p align="center"> | |
5 | - <em>JustAPI β Built to handle pressure.</em> | |
2 | + <img src="assets/justapi-hero.svg" alt="JustAPI β Python writes the logic. Rust does everything else." width="100%"> | |
6 | 3 | </p> |
4 | + | |
7 | 5 | <p align="center"> |
8 | -<a href="https://github.com/swadhinbiswas/JustAPI/actions/workflows/ci.yml"> | |
6 | + <a href="https://github.com/swadhinbiswas/JustAPI/actions/workflows/ci.yml"> | |
9 | 7 | <img src="https://github.com/swadhinbiswas/JustAPI/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"> |
10 | -</a> | |
11 | -<a href="https://github.com/swadhinbiswas/JustAPI/actions/workflows/wheels.yml"> | |
12 | - <img src="https://github.com/swadhinbiswas/JustAPI/actions/workflows/wheels.yml/badge.svg" alt="Wheels"> | |
13 | -</a> | |
14 | -<a href="https://github.com/swadhinbiswas/JustAPI/actions/workflows/publish.yml"> | |
15 | - <img src="https://github.com/swadhinbiswas/JustAPI/actions/workflows/publish.yml/badge.svg" alt="Publish"> | |
16 | -</a> | |
17 | -<a href="https://github.com/swadhinbiswas/JustAPI/blob/main/LICENSE"> | |
18 | - <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"> | |
19 | -</a> | |
20 | -<a href="https://www.python.org/downloads/"> | |
21 | - <img src="https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13%20|%203.14-blue.svg?logo=python&logoColor=white" alt="Python versions"> | |
22 | -</a> | |
23 | -<a href="https://www.rust-lang.org/"> | |
24 | - <img src="https://img.shields.io/badge/rust-1.85+-E6522C.svg?logo=rust&logoColor=white" alt="Rust version"> | |
25 | -</a> | |
26 | -<a href="https://github.com/swadhinbiswas/JustAPI/blob/main/deny.toml"> | |
27 | - <img src="https://img.shields.io/badge/cargo--deny-v0.20-passing-green.svg" alt="cargo-deny"> | |
28 | -</a> | |
29 | -<a href="https://pypi.org/project/justapi/"> | |
8 | + </a> | |
9 | + <a href="https://github.com/swadhinbiswas/JustAPI/actions/workflows/wheels.yml"> | |
10 | + <img src="https://github.com/swadhinbiswas/JustAPI/actions/workflows/wheels.yml/badge.svg" alt="Wheels & Release"> | |
11 | + </a> | |
12 | + <a href="https://pypi.org/project/justapi/"> | |
30 | 13 | <img src="https://img.shields.io/badge/pypi-2.0.8-blue.svg?logo=pypi&logoColor=white" alt="PyPI"> |
31 | -</a> | |
14 | + </a> | |
15 | + <a href="https://www.python.org/downloads/"> | |
16 | + <img src="https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13%20|%203.14-blue.svg?logo=python&logoColor=white" alt="Python"> | |
17 | + </a> | |
18 | + <a href="https://www.rust-lang.org/"> | |
19 | + <img src="https://img.shields.io/badge/rust-1.85+-E6522C.svg?logo=rust&logoColor=white" alt="Rust"> | |
20 | + </a> | |
21 | + <a href="https://github.com/swadhinbiswas/JustAPI/blob/main/LICENSE"> | |
22 | + <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT"> | |
23 | + </a> | |
24 | + <a href="https://img.shields.io/badge/free--threaded-3.14t-22d3ee"> | |
25 | + <img src="https://img.shields.io/badge/free--threaded-3.14t-22d3ee.svg" alt="Free-threaded"> | |
26 | + </a> | |
27 | + <a href="https://github.com/swadhinbiswas/JustAPI/blob/main/deny.toml"> | |
28 | + <img src="https://img.shields.io/badge/cargo--deny-passing-green.svg" alt="cargo-deny"> | |
29 | + </a> | |
32 | 30 | </p> |
33 | 31 | |
34 | 32 | --- |
35 | 33 | |
36 | -**Documentation**: <a href="https://github.com/swadhinbiswas/JustAPI/tree/main/docs_site" target="_blank">https://github.com/swadhinbiswas/JustAPI/docs_site</a> | |
34 | +**JustAPI** is a Python web framework with a Rust core. You write Python logic. | |
35 | +Rust handles everything else β networking, TLS, routing, validation, | |
36 | +serialization, SSE, database I/O, rate limiting. One `pip install`, a server | |
37 | +that starts in milliseconds, and numbers that hold up under real traffic. | |
37 | 38 | |
38 | -**Source Code**: <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank">https://github.com/swadhinbiswas/JustAPI</a> | |
39 | +> **Python writes the logic. Rust does everything else.** | |
39 | 40 | |
40 | 41 | --- |
41 | 42 | |
42 | -JustAPI is a Python web framework powered by a Rust core. Python writes the logic. Rust handles everything else β networking, TLS, routing, serialization, compression. | |
43 | - | |
44 | -Built to handle real traffic. | |
45 | - | |
46 | -**Python writes the logic. Rust does everything else.** | |
47 | - | |
48 | - | |
49 | - | |
50 | -## Performance | |
51 | - | |
52 | -*Hello-world benchmark Β· 100 concurrent connections Β· 30 seconds Β· same hardware* | |
53 | - | |
54 | -| Framework | Requests/sec | p50 Latency | p99 Latency | | |
55 | -|---|--:|--:|--:| | |
56 | -| **JustAPI (native fast path)** | **701,234** | **0.07 ms** | **0.19 ms** | | |
57 | -| **JustAPI (Python handler)** | **60,297** | **1.03 ms** | **5.21 ms** | | |
58 | -| Robyn 0.88 | 39,103 | 1.78 ms | 11.47 ms | | |
59 | -| Granian | 69,195 | 0.72 ms | 3.87 ms | | |
60 | -| FastAPI + Uvicorn | 36,189 | 1.72 ms | 24.63 ms | | |
61 | - | |
62 | -All measurements on the same hardware (i5-13600K, 20 threads), 100 concurrent connections, 30s duration. See [BENCHMARKS.md](BENCHMARKS.md) for full methodology and workload scripts. | |
63 | - | |
64 | -### Request pipeline | |
65 | - | |
66 | -JustAPI runs the entire request path in Rust. Python executes only application logic: | |
67 | - | |
68 | -``` | |
69 | -Kernel (epoll/io_uring) | |
70 | - β Connection Manager (tokio) | |
71 | - β TLS termination (rustls) | |
72 | - β HTTP parse (hyper) | |
73 | - β Router (matchit) | |
74 | - β Middleware chain (Rust: auth, CORS, rate-limit, compression) | |
75 | - β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
76 | - β Python handler path (default): β | |
77 | - β β Python boundary (zero-copy via PyO3 buffer) β | |
78 | - β β Your async Python handler β | |
79 | - β β Rust serializer (serde_json / simd-json) β | |
80 | - βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
81 | - β Native fast path (native=True + Schema): β | |
82 | - β β Rust validator β Rust response (no Python) β | |
83 | - βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
84 | - β Response write β Socket | |
85 | -``` | |
86 | - | |
87 | -The Python path is used for general routes. For schema-backed routes with `native=True`, the entire handler executes in Rust β eliminating the GIL acquire, Python dispatch, and per-request JSON schema compilation. | |
88 | - | |
89 | -## What makes JustAPI different | |
90 | - | |
91 | -1. **Rust-native request pipeline.** Networking, TLS, routing, serialization, middleware, and auth all execute in Rust β Python runs only your application logic. This yields ~60k rps on the Python handler path and ~724k rps on the native fast path for schema-backed routes, both measured on a single machine. | |
92 | - | |
93 | -2. **Agent-native serving.** Beyond REST, JustAPI exposes routes as MCP tools, streams validated structured output per-token, and carries multi-turn session state β without external SDKs. See [`ROADMAP.md`](ROADMAP.md) for the full agent roadmap. | |
94 | - | |
95 | -3. **Native async DB awaits.** `await app.db.query_async(sql)` / `execute_async(sql)` run SQL on the DB's own multi-threaded tokio runtime with the GIL released β the asyncio loop is never blocked, so slow queries (Postgres, network DBs) do not serialize other requests. Measured **53Γ faster than the blocking path on slow queries** (320 RPS vs 6 RPS, 2M-row CTE). | |
96 | - | |
97 | -4. **Multi-worker prefork scaling.** `justapi serve --workers N --scale` spawns N worker processes sharing one bound socket. Verified: 4 workers = **1.88Γ throughput** (99.7k RPS vs 53k), auto-scale respawns under load. | |
98 | - | |
99 | -5. **Type-checked DX.** Ships `py.typed` + complete `.pyi` stubs β mypy-clean on user code (routes, `app.db`, `native_async`, `sse_native`). `justapi create` scaffolds a full CRUD project demonstrating every differentiator. | |
100 | - | |
101 | -6. **Free-threaded CPython support.** 3.14t wheels (Py_GIL_DISABLED auto-detected): CPU-bound Python handlers scale with cores (12.4Γ measured). | |
102 | - | |
103 | -## Competitors, named honestly | |
104 | - | |
105 | -JustAPI is not the first Rust-backed Python web framework. The following table compares the closest alternatives on raw throughput (Python handler path, same hardware): | |
106 | - | |
107 | -| Workload | JustAPI | Robyn 0.88 | Faster | | |
108 | -|---|---:|---:|---| | |
109 | -| Hello-world (GET) | 60,297 rps | 39,103 rps | JustAPI Γ1.54 | | |
110 | -| JSON echo (POST) | 47,415 rps | 36,899 rps | JustAPI Γ1.29 | | |
111 | -| Validated JSON (POST) | 40,080 rps | 32,919 rps | JustAPI Γ1.22 | | |
112 | - | |
113 | -For routes registered with `native=True` and a schema, JustAPI serves entirely in Rust at **~724k rps** β bypassing the Python handler entirely. | |
114 | - | |
115 | -### Where each competitor fits | |
116 | - | |
117 | -- **Robyn** β Rust runtime with a decorator API, the closest architectural peer. Both frameworks keep the hot path in Rust and dispatch to Python for business logic. JustAPI's edge is: (a) measured throughput advantage on all three standard workloads, (b) a native fast path (`native=True`) that eliminates the Python dispatch entirely for schema-backed routes, (c) a fully native middleware/validation stack with zero ASGI/Starlette dependency, and (d) built-in MCP agent serving as a first-class primitive rather than an add-on. | |
118 | - | |
119 | -- **Granian** β Rust ASGI *server*. It drops under existing FastAPI/Starlette apps without changing framework code. If your goal is a faster transport layer without migrating frameworks, Granian is the lower-friction choice. JustAPI wins when you want the full Rust stack β routing, serialization, middleware, auth, and agent serving β all native. | |
120 | - | |
121 | -- **Litestar / BlackSheep** β Python-native frameworks with first-class ASGI support and rich feature sets (DI, validation, OpenAPI). JustAPI shares their design philosophy for ergonomics but implements the critical path in Rust. Migration from these is similar to FastAPI. | |
122 | - | |
123 | -- **FastAPI** β the compatibility target. JustAPI mirrors its decorator API and Pydantic model integration so existing FastAPI applications can migrate with minimal changes β often just the import statement. | |
124 | - | |
125 | -### Honest caveats | |
126 | - | |
127 | -- **Native fast path requires a schema.** Routes without a `Schema` registered fall back to the Python handler path (~60k rps). The 724k rps number only applies to routes with `native=True` + `Schema`. | |
128 | -- **Python handler throughput is GIL-bound.** On CPython, the Python dispatch path is capped at ~100-120k rps on this hardware regardless of framework β that is the GIL, not a framework limitation. Free-threaded Python (3.13t/3.14t) removes this ceiling. | |
129 | -- **Light async handlers are loop-bound.** Async Python handlers that only sleep/echo (~4-12k rps) are capped by the asyncio loop + thread hop β Granian's direct loop dispatch is ~44k there (ADR-090/091/092 proved no coroutine-driver or multi-loop approach beats asyncio's stepping). We win on real async workloads (1ms-sleep handlers: 3.9k vs Granian 2.9k RPS) and on anything DB/SSE-heavy via native ops. | |
130 | -- **Native fast path requires a schema.** Routes without a `Schema` registered fall back to the Python handler path (~60k rps). The 724k rps number only applies to routes with `native=True` + `Schema`. | |
131 | - | |
132 | -## Non-goals & not-yet-built | |
133 | - | |
134 | -JustAPI is **not** trying to be everything in v1. We do not ship a custom | |
135 | -edge/WASM runtime, a from-scratch ORM/migrations system, a multi-tenant auth | |
136 | -platform, or a new validator (we wrap Pydantic v2 core / JSON Schema). See | |
137 | -[`ROADMAP.md`](ROADMAP.md) for the full, explicitly-labeled future list. | |
138 | - | |
139 | -## Requirements | |
140 | - | |
141 | -* **Python 3.11+** | |
142 | -* **Rust 1.85+** (only for building from source) | |
143 | - | |
144 | -## Installation | |
145 | - | |
146 | -### From PyPI (recommended) | |
43 | +## Quickstart | |
147 | 44 | |
148 | 45 | ```bash |
149 | 46 | pip install justapi |
150 | 47 | ``` |
151 | 48 | |
152 | -With optional features (Pydantic v2, Jinja2, XML): | |
153 | - | |
154 | -```bash | |
155 | -pip install "justapi[full]" | |
156 | -``` | |
157 | - | |
158 | -### Via uv (faster) | |
159 | - | |
160 | -[uv](https://docs.astral.sh/uv/) is the recommended Python package manager: | |
161 | - | |
162 | -```bash | |
163 | -uv pip install justapi | |
164 | -uv pip install "justapi[full]" | |
165 | -``` | |
166 | - | |
167 | -### Run without installing | |
168 | - | |
169 | -```bash | |
170 | -uvx justapi create my_app | |
171 | -uvx justapi serve --reload | |
172 | -``` | |
173 | - | |
174 | -### Build from source | |
175 | - | |
176 | -```bash | |
177 | -cd crates/justapi-py | |
178 | -maturin develop --release | |
179 | -``` | |
180 | - | |
181 | -## Example | |
182 | - | |
183 | -### Create it | |
184 | - | |
185 | -Create a file `main.py` with: | |
186 | - | |
187 | 49 | ```python |
50 | +# main.py | |
188 | 51 | from justapi import JustAPIApp |
189 | 52 | |
190 | 53 | app = JustAPIApp() |
191 | 54 | |
192 | - | |
193 | 55 | @app.get("/") |
194 | 56 | def read_root(): |
195 | 57 | return {"Hello": "World"} |
196 | 58 | |
197 | - | |
198 | 59 | @app.get("/items/{item_id}") |
199 | 60 | def read_item(item_id: int, q: str | None = None): |
200 | 61 | return {"item_id": item_id, "q": q} |
201 | -``` | |
202 | 62 | |
203 | -### Run it | |
63 | +app.run() | |
64 | +``` | |
204 | 65 | |
205 | 66 | ```console |
206 | 67 | $ python main.py |
207 | - | |
208 | 68 | INFO: JustAPI running on http://127.0.0.1:8000 |
209 | -INFO: Rust runtime started with 4 worker threads | |
210 | -``` | |
211 | - | |
212 | -### Check it | |
213 | - | |
214 | -Open your browser at <a href="http://127.0.0.1:8000/items/5?q=somequery" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>. | |
215 | - | |
216 | -You will see the JSON response as: | |
217 | - | |
218 | -```json | |
219 | -{"item_id": 5, "q": "somequery"} | |
220 | 69 | ``` |
221 | 70 | |
222 | -You already created an API that: | |
223 | - | |
224 | -* Receives HTTP requests at the paths `/` and `/items/{item_id}`. | |
225 | -* Both paths take `GET` <em>operations</em> (also known as HTTP <em>methods</em>). | |
226 | -* The path `/items/{item_id}` has a **path parameter** `item_id` that should be an `int`. | |
227 | -* The path `/items/{item_id}` has an optional `str` **query parameter** `q`. | |
228 | - | |
229 | -### Interactive API docs | |
230 | - | |
231 | -Now go to <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>. | |
71 | +Interactive OpenAPI docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a> β generated from your code, zero configuration. | |
232 | 72 | |
233 | -You will see the automatic interactive API documentation (provided by <a href="https://github.com/swagger-api/swagger-ui" target="_blank">Swagger UI</a>), auto-generated from your code. | |
234 | - | |
235 | -## Example upgrade | |
236 | - | |
237 | -Now modify the file `main.py` to receive a body from a `PUT` request. | |
238 | - | |
239 | -Declare the body using standard Python types, thanks to <a href="https://docs.pydantic.dev/" target="_blank">Pydantic</a>: | |
73 | +### Async? Native. DB? Native. SSE? Native. | |
240 | 74 | |
241 | 75 | ```python |
242 | -from justapi import JustAPIApp | |
243 | -from pydantic import BaseModel | |
76 | +import asyncio | |
77 | +from justapi import JustAPIApp, native_async | |
244 | 78 | |
245 | 79 | app = JustAPIApp() |
80 | +app.set_database("postgres://user:pass@host/db") | |
81 | +app.sse_native("/events", count=1000, interval_ms=0) # Rust streams events β 0 Python per event | |
246 | 82 | |
83 | +@app.get("/users/{uid}") | |
84 | +@native_async | |
85 | +async def get_user(request, uid: int): | |
86 | + row = await app.db.query_async( # runs on the DB's tokio runtime | |
87 | + "SELECT * FROM users WHERE id = ?", [uid]) # GIL released β loop never blocks | |
88 | + return row | |
247 | 89 | |
248 | -class Item(BaseModel): | |
249 | - name: str | |
250 | - description: str | None = None | |
251 | - price: float | |
252 | - tax: float | None = None | |
253 | - | |
254 | - | |
255 | -@app.get("/") | |
256 | -def read_root(): | |
257 | - return {"Hello": "World"} | |
258 | - | |
259 | - | |
260 | -@app.get("/items/{item_id}") | |
261 | -def read_item(item_id: int): | |
262 | - return {"item_id": item_id} | |
263 | - | |
264 | - | |
265 | -@app.put("/items/{item_id}") | |
266 | -def update_item(item_id: int, item: Item): | |
267 | - return {"item_name": item.name, "item_id": item_id, "price_with_tax": item.price + (item.tax or 0)} | |
90 | +app.run() | |
268 | 91 | ``` |
269 | 92 | |
270 | -The server will reload automatically (when using `--reload`). | |
271 | - | |
272 | -### Interactive API docs upgrade | |
273 | - | |
274 | -Now go to <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>. | |
275 | - | |
276 | -* The interactive API documentation will be automatically updated, including the new body. | |
277 | -* Click on the "Try it out" button, it allows you to fill the parameters and directly interact with the API. | |
278 | -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen. | |
93 | +--- | |
279 | 94 | |
280 | -### Alternative API docs | |
95 | +## Performance | |
281 | 96 | |
282 | -And now go to <a href="http://127.0.0.1:8000/redoc" target="_blank">http://127.0.0.1:8000/redoc</a>. | |
97 | +<p align="center"> | |
98 | + <img src="assets/justapi-benchmark.svg" alt="Benchmark β JustAPI vs Granian vs FastAPI vs Robyn" width="100%"> | |
99 | +</p> | |
283 | 100 | |
284 | -* The alternative documentation will also reflect the new query parameter and body. | |
101 | +Hello-world, 100 concurrent connections, 30s, same hardware. Full methodology in [BENCHMARKS.md](BENCHMARKS.md). | |
285 | 102 | |
286 | -## Example with dependencies | |
103 | +| Framework | Hello-world | JSON echo | p99 hello-world | RSS | | |
104 | +|---|--:|--:|--:|--:| | |
105 | +| **JustAPI (native fast path)** | **766k req/s** | **782k req/s** | **0.48 ms** | **12 MB** | | |
106 | +| Granian (ASGI) | 314k | 145k | 0.74 ms | 37 MB | | |
107 | +| Robyn | 39k | β | β | β | | |
108 | +| FastAPI + Uvicorn | 36k | 33k | 24.63 ms | 29 MB | | |
287 | 109 | |
288 | -JustAPI supports FastAPI-style dependency injection: | |
110 | +More numbers, honestly: | |
289 | 111 | |
290 | -```python | |
291 | -from justapi import JustAPIApp, Depends | |
112 | +- **Route lookup:** 51 ns average on a 500-route table (matchit radix trie) | |
113 | +- **Rust-native CRUD:** 181k SELECT req/s β **Γ125 vs FastAPI** on the same DB fixture | |
114 | +- **Native async DB awaits** (`query_async`): **53Γ faster than the blocking path** on slow queries (320 vs 6 RPS) β the asyncio loop is never blocked | |
115 | +- **Multi-worker prefork** (`justapi serve --workers N`): **1.88Γ throughput at 4 workers** (99.7k RPS), auto-scale included | |
116 | +- **Free-threaded CPython (3.14t):** CPU-bound Python handlers **12.4Γ faster** β the GIL ceiling is gone | |
292 | 117 | |
293 | -app = JustAPIApp() | |
118 | +### Honest caveats | |
294 | 119 | |
120 | +- **Native fast path requires a schema.** Routes without a `Schema` fall back to the Python handler path (~60k req/s). The ~700k numbers apply to routes with `native=True` + `Schema`. | |
121 | +- **Python handler throughput is GIL-bound.** On GIL-locked CPython, the Python dispatch path caps at ~100-120k req/s on this hardware regardless of framework β that is the GIL, not a framework limitation. Free-threaded Python (3.14t) removes this ceiling. | |
122 | +- **Light async handlers are loop-bound.** Async handlers that only sleep/echo (~4-12k req/s) are capped by the asyncio loop + thread hop. Three experiments (ADR-090/091/092) proved no coroutine driver, bridge, or multi-loop approach beats asyncio's own stepping. We win where it matters: real async workloads (1ms-sleep handlers: 3.9k vs Granian 2.9k RPS) and anything DB/SSE-heavy via native ops. | |
123 | +- **LLM-serving claims are NOT part of this release.** Inference phases are unverified (no real GPU run, ADR-067) β the tested story is the web framework. | |
295 | 124 | |
296 | -def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): | |
297 | - return {"q": q, "skip": skip, "limit": limit} | |
125 | +--- | |
298 | 126 | |
127 | +## How a request flows | |
299 | 128 | |
300 | -@app.get("/items/") | |
301 | -def read_items(commons: dict = Depends(common_parameters)): | |
302 | - return {"message": "Reading items", "params": commons} | |
129 | +<p align="center"> | |
130 | + <img src="assets/justapi-pipeline.svg" alt="Request pipeline β Rust end to end" width="100%"> | |
131 | +</p> | |
303 | 132 | |
133 | +The entire request path runs in Rust. Python executes only your application logic: | |
304 | 134 | |
305 | -@app.get("/users/") | |
306 | -def read_users(commons: dict = Depends(common_parameters)): | |
307 | - return {"message": "Reading users", "params": commons} | |
308 | 135 | ``` |
309 | - | |
310 | -## Example with WebSockets | |
311 | - | |
312 | -```python | |
313 | -from justapi import JustAPIApp | |
314 | - | |
315 | -app = JustAPIApp() | |
316 | - | |
317 | - | |
318 | -@app.websocket("/ws") | |
319 | -async def websocket_endpoint(ws): | |
320 | - await ws.accept() | |
321 | - while True: | |
322 | - data = await ws.receive_text() | |
323 | - await ws.send_text(f"Echo: {data}") | |
136 | +Kernel (epoll/io_uring) | |
137 | + β tokio connection manager | |
138 | + β TLS (rustls) | |
139 | + β HTTP parse (hyper) | |
140 | + β Router (matchit, 51ns) | |
141 | + β Middleware chain (auth, CORS, rate-limit, compression) | |
142 | + β Python boundary (zero-copy PyO3, GIL pool) | |
143 | + β Your async Python handler | |
144 | + β Rust serializer (serde_json / simd-json) | |
145 | + β Response write β socket | |
324 | 146 | ``` |
325 | 147 | |
326 | -## Project Scaffolding CLI | |
327 | - | |
328 | -JustAPI includes an interactive project generator to create production-ready applications with zero boilerplate: | |
148 | +Routes marked `native=True` + `Schema` skip Python entirely β Rust validates the | |
149 | +body and writes the response. That is the ~700k req/s path. | |
329 | 150 | |
330 | -```bash | |
331 | -# Interactive project creation (prompts for DB engine and API architecture): | |
332 | -justapi create my_app | |
333 | - | |
334 | -# Or specify flags directly: | |
335 | -justapi create analytics_app --db duckdb --api-type rest | |
336 | -justapi create graph_app --db postgres --api-type graphql | |
337 | -justapi create rpc_service --db redis --api-type grpc | |
338 | -justapi create jsonrpc_api --db sqlite --api-type jsonrpc | |
339 | -``` | |
151 | +--- | |
340 | 152 | |
341 | -### Supported Backends & Protocols: | |
342 | -* **Databases:** Transactional SQL (`sqlite`, `postgres`, `mysql`), Analytical OLAP (`duckdb`, `clickhouse`), NoSQL (`mongodb`, `redis`). | |
343 | -* **Protocols:** REST (OpenAPI 3.1), GraphQL (GraphiQL UI), gRPC (Protobuf), JSON-RPC 2.0. | |
344 | -* **Observability:** Includes pre-built OpenTelemetry & Jaeger stack (`docker-compose.otel.yml`). | |
153 | +## Feature highlights | |
345 | 154 | |
346 | -## Features | |
155 | +<p align="center"> | |
156 | + <img src="assets/justapi-features.svg" alt="Feature matrix" width="100%"> | |
157 | +</p> | |
347 | 158 | |
348 | 159 | <table> |
349 | 160 | <tr><th>Category</th><th>Feature</th><th>Details</th></tr> |
350 | 161 | |
351 | -<tr><td rowspan="4"><strong>Performance</strong></td> | |
352 | - <td>HTTP/1.1 & HTTP/2</td><td>~700k req/s native fast path (validate-and-echo, no Python); ~120k req/s Python-handler path (GIL-bound)</td></tr> | |
353 | -<tr><td>TLS (rustls)</td><td>~10% overhead, no OpenSSL dependency</td></tr> | |
354 | -<tr><td>Serialization</td><td>serde_json with optional simd-json</td></tr> | |
162 | +<tr><td rowspan="5"><strong>Performance</strong></td> | |
163 | + <td>HTTP/1.1 Β· HTTP/2 Β· HTTP/3</td><td>~766k req/s native fast path; QUIC (HTTP/3) is feature-gated β no other Python framework ships it</td></tr> | |
164 | +<tr><td>TLS (rustls)</td><td>~10% overhead, zero OpenSSL dependency</td></tr> | |
165 | +<tr><td>Serialization</td><td>serde_json with optional simd-json (89 ns/op)</td></tr> | |
355 | 166 | <tr><td>Compression</td><td>gzip / deflate / brotli / zstd</td></tr> |
167 | +<tr><td>Multi-worker prefork</td><td><code>justapi serve --workers N --scale</code> β shared-socket process fleet with load-based auto-scaling</td></tr> | |
168 | + | |
169 | +<tr><td rowspan="5"><strong>Native ops</strong></td> | |
170 | + <td>Async DB awaits</td><td><code>await app.db.query_async()</code> β SQL on the DB's tokio runtime, GIL released, loop never blocked (ADR-093)</td></tr> | |
171 | +<tr><td>Rust-native CRUD</td><td>Rust validates + queries + serializes β 181k SELECT req/s, Γ125 vs FastAPI</td></tr> | |
172 | +<tr><td>Native SSE</td><td><code>app.sse_native(path, count, interval_ms)</code> β events generated in Rust, zero Python per event</td></tr> | |
173 | +<tr><td>@native_async</td><td>Marks async handlers for the fastest dispatch; true parallel dispatch on free-threaded CPython</td></tr> | |
174 | +<tr><td>Adaptive batching</td><td>Batch + window decorator for bursty workloads</td></tr> | |
356 | 175 | |
357 | 176 | <tr><td rowspan="5"><strong>Security</strong></td> |
358 | - <td>JWT Authentication</td><td>RS256/ES256, per-route roles & scopes</td></tr> | |
359 | -<tr><td>Rate Limiting</td><td>GCRA algorithm, Redis-backed distributed</td></tr> | |
177 | + <td>JWT Authentication</td><td>HS256/RS256/ES256, per-route roles & scopes, OAuth2 password flow</td></tr> | |
178 | +<tr><td>Rate Limiting</td><td>GCRA, Redis-backed distributed, IP + per-route</td></tr> | |
360 | 179 | <tr><td>CORS</td><td>Configurable, preflight caching</td></tr> |
361 | -<tr><td>Request Validation</td><td>JSON Schema, Pydantic v2 bridge</td></tr> | |
362 | -<tr><td>Security Headers</td><td>HSTS, CSP, X-Frame-Options, etc.</td></tr> | |
180 | +<tr><td>Request Validation</td><td>JSON Schema compiled in Rust, Pydantic v2 bridge</td></tr> | |
181 | +<tr><td>Security Headers</td><td>HSTS, CSP, X-Frame-Options, nosniff, and more</td></tr> | |
363 | 182 | |
364 | 183 | <tr><td rowspan="5"><strong>Protocols</strong></td> |
365 | - <td>REST</td><td>Full HTTP method support with OpenAPI 3.1</td></tr> | |
184 | + <td>REST</td><td>Full HTTP methods with OpenAPI 3.1</td></tr> | |
366 | 185 | <tr><td>WebSocket</td><td>Full-duplex with streaming support</td></tr> |
367 | -<tr><td>Server-Sent Events</td><td>Token-generation optimized streaming</td></tr> | |
186 | +<tr><td>Server-Sent Events</td><td>Generator-driven + Rust-native streaming</td></tr> | |
368 | 187 | <tr><td>gRPC</td><td>Native Tonic integration</td></tr> |
369 | -<tr><td>GraphQL</td><td>async-graphql with Apollo Federation</td></tr> | |
188 | +<tr><td>GraphQL</td><td>async-graphql wiring</td></tr> | |
370 | 189 | |
371 | 190 | <tr><td rowspan="5"><strong>Application</strong></td> |
372 | - <td>Dependency Injection</td><td>Litestar-style tiered DI container</td></tr> | |
191 | + <td>Dependency Injection</td><td>Litestar-style tiered DI, sync/async generators, cleanup</td></tr> | |
373 | 192 | <tr><td>Background Tasks</td><td>Thread-safe task scheduling</td></tr> |
374 | -<tr><td>Database ORM</td><td>SQLite / PostgreSQL / MySQL via sqlx</td></tr> | |
193 | +<tr><td>Database</td><td>SQLite / PostgreSQL / MySQL / DuckDB via sqlx β pool, migrations, transaction middleware</td></tr> | |
375 | 194 | <tr><td>Template Engine</td><td>Jinja2 integration</td></tr> |
376 | 195 | <tr><td>File Uploads</td><td>Streaming multipart/form-data</td></tr> |
377 | 196 | |
378 | 197 | <tr><td rowspan="5"><strong>Operations</strong></td> |
379 | - <td>Observability</td><td>OpenTelemetry tracing, Prometheus metrics</td></tr> | |
380 | -<tr><td>Resilience</td><td>Circuit breakers, retry, fallback, bulkhead</td></tr> | |
381 | -<tr><td>Health Checks</td><td>Liveness & readiness probe endpoints</td></tr> | |
198 | + <td>Observability</td><td>OpenTelemetry tracing, Prometheus metrics, health checks</td></tr> | |
199 | +<tr><td>Resilience</td><td>Circuit breakers, retry, fallback, bulkhead β 44 resilience tests</td></tr> | |
200 | +<tr><td>Hot Reload</td><td><code>justapi serve --reload</code> with graceful drain</td></tr> | |
382 | 201 | <tr><td>Plugin System</td><td>Third-party extension API with lifecycle hooks</td></tr> |
383 | 202 | <tr><td>WASM Plugins</td><td>wasmtime-powered WebAssembly middleware</td></tr> |
384 | 203 | |
385 | -<tr><td rowspan="4"><strong>Quality</strong></td> | |
386 | - <td>Memory Safety</td><td>Miri-verified, 6 fuzz targets</td></tr> | |
387 | -<tr><td>Supply Chain Security</td><td>cargo-deny v0.20 β advisories, bans, licenses, sources</td></tr> | |
388 | -<tr><td>Type Stubs</td><td>Complete .pyi for all public modules</td></tr> | |
389 | -<tr><td>Test Utilities</td><td>Built-in TestClient & snapshot testing</td></tr> | |
204 | +<tr><td rowspan="4"><strong>DX & Quality</strong></td> | |
205 | + <td>Type Stubs</td><td><code>py.typed</code> + complete <code>.pyi</code> β mypy-clean on user code</td></tr> | |
206 | +<tr><td>Scaffolding</td><td><code>justapi create</code> β full CRUD project (7 DB backends, 4 API styles) that demos every differentiator</td></tr> | |
207 | +<tr><td>Memory Safety</td><td>ASan on the full suite + Miri on all unsafe code, 6 fuzz targets</td></tr> | |
208 | +<tr><td>Supply Chain</td><td>cargo-deny β advisories, bans, licenses, sources</td></tr> | |
390 | 209 | </table> |
391 | 210 | |
211 | +--- | |
212 | + | |
392 | 213 | ## Coming from FastAPI? |
393 | 214 | |
394 | -JustAPI is designed to feel familiar. Here's a side-by-side: | |
215 | +JustAPI mirrors FastAPI's decorator API and Pydantic integration, so existing | |
216 | +apps migrate with minimal changes β often just the import: | |
395 | 217 | |
396 | -<table> | |
397 | -<tr><th></th><th>FastAPI</th><th>JustAPI</th></tr> | |
398 | -<tr> | |
399 | -<td><strong>Import</strong></td> | |
400 | -<td><code>from fastapi import FastAPI</code></td> | |
401 | -<td><code>from justapi import JustAPIApp</code></td> | |
402 | -</tr> | |
403 | -<tr> | |
404 | -<td><strong>Create app</strong></td> | |
405 | -<td><code>app = FastAPI()</code></td> | |
406 | -<td><code>app = JustAPIApp()</code></td> | |
407 | -</tr> | |
408 | -<tr> | |
409 | -<td><strong>Decorators</strong></td> | |
410 | -<td><code>@app.get("/items/{id}")</code></td> | |
411 | -<td><code>@app.get("/items/{id}")</code></td> | |
412 | -</tr> | |
413 | -<tr> | |
414 | -<td><strong>Pydantic</strong></td> | |
415 | -<td>Native</td> | |
416 | -<td>Native</td> | |
417 | -</tr> | |
418 | -<tr> | |
419 | -<td><strong>Depends()</strong></td> | |
420 | -<td>Built-in</td> | |
421 | -<td>Built-in</td> | |
422 | -</tr> | |
423 | -<tr> | |
424 | -<td><strong>OpenAPI docs</strong></td> | |
425 | -<td><code>/docs</code> and <code>/redoc</code></td> | |
426 | -<td><code>/docs</code> and <code>/redoc</code></td> | |
427 | -</tr> | |
428 | -<tr> | |
429 | -<td><strong>Server</strong></td> | |
430 | -<td>uvicorn (Python)</td> | |
431 | -<td>Built-in Rust server (hyper + tokio)</td> | |
432 | -</tr> | |
433 | -<tr> | |
434 | -<td><strong>Performance</strong></td> | |
435 | -<td>~36k req/s (hello-world)</td> | |
436 | -<td><strong>~700k req/s</strong> (native fast path, validate-and-echo)<br>~120k req/s (Python handler, GIL-serialized)<br>~180k req/s (Rust-native DB SELECT)</td> | |
437 | -</tr> | |
438 | -</table> | |
218 | +| | FastAPI | JustAPI | | |
219 | +|---|---|---| | |
220 | +| **Import** | `from fastapi import FastAPI` | `from justapi import JustAPIApp` | | |
221 | +| **Create** | `app = FastAPI()` | `app = JustAPIApp()` | | |
222 | +| **Decorators** | `@app.get("/items/{id}")` | `@app.get("/items/{id}")` | | |
223 | +| **Pydantic** | Native | Native | | |
224 | +| **Depends()** | Built-in | Built-in (incl. generator deps + cleanup) | | |
225 | +| **OpenAPI** | `/docs` and `/redoc` | `/docs` and `/redoc` | | |
226 | +| **Server** | uvicorn (Python) | Built-in Rust server (tokio + hyper) | | |
227 | +| **Hello-world** | ~36k req/s | **~766k req/s** (native fast path) | | |
228 | + | |
229 | +π Full [migration guide](docs/migrating_from_fastapi.md). | |
439 | 230 | |
440 | -π See the full [migration guide](docs/migrating_from_fastapi.md). | |
231 | +--- | |
441 | 232 | |
442 | 233 | ## Architecture |
443 | 234 | |
| @@ -445,21 +236,19 @@ JustAPI is designed to feel familiar. Here's a side-by-side: | ||
445 | 236 | justapi/ |
446 | 237 | βββ crates/ |
447 | 238 | β βββ justapi-core/ # Networking, routing, middleware, serialization |
448 | -β βββ justapi-py/ # PyO3 bindings, native Python API | |
449 | -β βββ justapi-cli/ # CLI binary (serve, create, migrate) | |
450 | -β βββ justapi-bench/ # Internal benchmark harness | |
451 | -β βββ justapi-inference/ # ML inference engine (optional) | |
452 | -βββ examples/ # Tutorial examples | |
453 | -βββ tests/ # Integration tests | |
454 | -βββ fuzz/ # cargo-fuzz security targets (6) | |
455 | -βββ docs/ # MkDocs documentation | |
456 | -βββ deploy/ # Cloud deployment guides | |
457 | -βββ helm/ # Kubernetes Helm chart | |
458 | -βββ benchmarks/ # Performance workload scripts | |
459 | -βββ website/ # Project landing page | |
239 | +β βββ justapi-py/ # PyO3 bindings + Python package (python/justapi) | |
240 | +β βββ justapi-cli/ # CLI binary (serve, create, check, gen-client) | |
241 | +β βββ justapi-bench/ # Internal benchmark harness | |
242 | +βββ assets/ # README diagrams (animated SVG) | |
243 | +βββ benchmarks/ # Workload scripts for baseline comparisons | |
244 | +βββ docs_site/ # Full documentation site (141 pages) | |
245 | +βββ skills/ # Reusable engineering knowledge | |
246 | +βββ PLAN.md # Living roadmap (single source of truth) | |
247 | +βββ DECISIONS.md # Append-only ADR log | |
248 | +βββ BENCHMARKS.md # Append-only performance ledger | |
460 | 249 | ``` |
461 | 250 | |
462 | -### Crate dependency graph | |
251 | +Crate dependency graph: | |
463 | 252 | |
464 | 253 | ``` |
465 | 254 | justapi-cli βββ justapi-core |
| @@ -467,138 +256,90 @@ justapi-py βββ justapi-core | ||
467 | 256 | justapi-bench ββ justapi-core |
468 | 257 | ``` |
469 | 258 | |
259 | +--- | |
260 | + | |
470 | 261 | ## Development |
471 | 262 | |
472 | 263 | ### Prerequisites |
473 | 264 | |
474 | -- **Rust** 1.85+ (edition 2021) β [install via rustup](https://rustup.rs/) | |
265 | +- **Rust** 1.85+ β [install via rustup](https://rustup.rs/) | |
475 | 266 | - **Python** 3.11+ β [download](https://www.python.org/downloads/) |
476 | -- **maturin** β `pip install maturin` (or `uv pip install maturin`) | |
477 | -- **uv** (optional, recommended) β [install guide](https://docs.astral.sh/uv/) | |
267 | +- **maturin** β `pip install maturin` | |
478 | 268 | |
479 | -### Building | |
269 | +### Build & test | |
480 | 270 | |
481 | 271 | ```bash |
482 | -# Build the workspace | |
483 | -cargo build --workspace | |
484 | - | |
485 | -# Build in release mode | |
486 | 272 | cargo build --workspace --release |
487 | - | |
488 | -# Build the Python package (dev mode) | |
489 | -cd crates/justapi-py && maturin develop --release | |
273 | +cd crates/justapi-py && maturin develop --release # build the Python package | |
274 | + | |
275 | +cargo test --workspace # Rust tests | |
276 | +cargo clippy --workspace --tests -- -D warnings # lint | |
277 | +cargo fmt --check # format | |
278 | +cargo deny check # supply-chain audit | |
279 | +bash scripts/sanitize.sh # ASan + Miri memory-safety gate | |
280 | +python -m pytest # Python tests (in the venv) | |
490 | 281 | ``` |
491 | 282 | |
492 | -### Testing | |
283 | +### Benchmark | |
493 | 284 | |
494 | 285 | ```bash |
495 | -# Run all Rust tests | |
496 | -cargo test --workspace | |
497 | - | |
498 | -# Lint | |
499 | -cargo clippy --workspace --tests -- -D warnings | |
500 | - | |
501 | -# Format check | |
502 | -cargo fmt --check | |
503 | - | |
504 | -# Supply chain security audit | |
505 | -cargo deny check | |
506 | - | |
507 | -# Memory safety verification (requires nightly) | |
508 | -cargo miri test -p justapi-core | |
509 | - | |
510 | -# Python tests | |
511 | -cd crates/justapi-py && python -m pytest | |
512 | - | |
513 | -# Python tests with uv | |
514 | -uv run pytest | |
286 | +bash benchmarks/run_baselines.sh # FastAPI / Granian / JustAPI A/B | |
515 | 287 | ``` |
516 | 288 | |
517 | -### Benchmarking | |
518 | - | |
519 | -```bash | |
520 | -# Install load generator | |
521 | -cargo install oha | |
522 | - | |
523 | -# Run the baselines (FastAPI, Granian, JustAPI) | |
524 | -bash benchmarks/run_baselines.sh | |
525 | -``` | |
289 | +See [BENCHMARKS.md](BENCHMARKS.md) for methodology, hardware, and history. | |
526 | 290 | |
527 | -See [BENCHMARKS.md](BENCHMARKS.md) for full methodology, hardware specs, and historical results. | |
291 | +--- | |
528 | 292 | |
529 | 293 | ## Deployment |
530 | 294 | |
531 | -### Docker (uv-based) | |
532 | - | |
533 | 295 | ```bash |
296 | +# Docker (multi-stage, uv-based β fast reproducible builds) | |
534 | 297 | docker build -t justapi-app . |
535 | 298 | docker run -p 8000:8000 justapi-app |
536 | -``` | |
537 | - | |
538 | -The multi-stage `Dockerfile` uses `uv` for fast, reproducible pip installs. | |
539 | - | |
540 | -### Docker Compose | |
541 | 299 | |
542 | -```bash | |
543 | -docker-compose up | |
544 | -``` | |
545 | - | |
546 | -### Kubernetes (Helm) | |
547 | - | |
548 | -```bash | |
549 | -helm install my-api helm/justapi/ \ | |
550 | - --set image.repository=your-registry/justapi-app \ | |
551 | - --set image.tag=latest | |
300 | +# Multi-worker + auto-scale (saturate all cores) | |
301 | +justapi serve --workers 4 --scale | |
552 | 302 | ``` |
553 | 303 | |
554 | -### Cloud platforms | |
304 | +- **Kubernetes:** `helm install my-api helm/justapi/ --set image.repository=your-registry/justapi-app` | |
305 | +- **Cloud guides:** [EKS](deploy/clouds/eks.md) Β· [GKE](deploy/clouds/gke.md) Β· [AKS](deploy/clouds/aks.md) Β· [Fly.io](deploy/clouds/flyio.md) Β· [Railway](deploy/clouds/railway.md) | |
306 | +- **Observability:** OTel tracing + Prometheus metrics + health probes out of the box | |
555 | 307 | |
556 | -Step-by-step deployment guides are available for: | |
557 | - | |
558 | -- [Amazon EKS](deploy/clouds/eks.md) | |
559 | -- [Google GKE](deploy/clouds/gke.md) | |
560 | -- [Azure AKS](deploy/clouds/aks.md) | |
561 | -- [Fly.io](deploy/clouds/flyio.md) | |
562 | -- [Railway](deploy/clouds/railway.md) | |
308 | +--- | |
563 | 309 | |
564 | 310 | ## Documentation |
565 | 311 | |
566 | 312 | | Document | Description | |
567 | 313 | |---|---| |
568 | -| [**Getting Started**](docs/getting_started.md) | Installation, first app, and core concepts | | |
569 | -| [**API Reference**](docs/api_reference.md) | Complete API documentation | | |
570 | -| [**Migrating from FastAPI**](docs/migrating_from_fastapi.md) | Step-by-step migration guide | | |
571 | -| [**Plugin Guide**](docs/plugins.md) | Building and distributing plugins | | |
572 | -| [**Security**](docs/security/) | OWASP checklist, pentest guide, disclosure policy | | |
573 | -| [**BENCHMARKS.md**](BENCHMARKS.md) | Performance results (append-only ledger) | | |
314 | +| [**docs_site**](docs_site/) | Full documentation site β 141 pages, 13 sections, search | | |
315 | +| [**Migration guide**](docs/migrating_from_fastapi.md) | Step-by-step from FastAPI | | |
316 | +| [**BENCHMARKS.md**](BENCHMARKS.md) | Performance ledger (append-only) | | |
574 | 317 | | [**PLAN.md**](PLAN.md) | Living roadmap and current phase | |
575 | -| [**ROADMAP.md**](ROADMAP.md) | Aspirational future work β explicitly *not yet built* | | |
576 | -| [**DECISIONS.md**](DECISIONS.md) | Architecture decision records | | |
577 | -| [**PRODUCTION_PLAN.md**](PRODUCTION_PLAN.md) | 2.0.8 pre-release hardening plan | | |
578 | -| [**AGENTS.md**](AGENTS.md) | Development rules and conventions | | |
318 | +| [**DECISIONS.md**](DECISIONS.md) | Architecture decision records (ADR-001 β ADR-093) | | |
319 | +| [**AGENTS.md**](AGENTS.md) | Development conventions & review checklist | | |
320 | + | |
321 | +--- | |
579 | 322 | |
580 | 323 | ## Contributing |
581 | 324 | |
582 | -Contributions are welcome! Please read [AGENTS.md](AGENTS.md) for development conventions, commit format, and review checklist before submitting a PR. | |
325 | +Contributions welcome. Read [AGENTS.md](AGENTS.md) for conventions before opening a PR. | |
583 | 326 | |
584 | 327 | ```bash |
585 | -# Fork and clone | |
586 | -git clone https://github.com/YOUR_USERNAME/JustAPI.git | |
328 | +git clone [email protected]:swadhinbiswas/JustAPI.git | |
587 | 329 | cd JustAPI |
588 | - | |
589 | -# Create a branch | |
590 | 330 | git checkout -b feat/my-feature |
591 | - | |
592 | -# Make changes, then run the full check suite | |
331 | +# ... make changes ... | |
593 | 332 | cargo test --workspace |
594 | 333 | cargo clippy --workspace --tests -- -D warnings |
595 | 334 | cargo fmt --check |
596 | -cargo deny check | |
597 | - | |
598 | -# Submit a PR | |
599 | 335 | ``` |
600 | 336 | |
337 | +Every PR is gated on: tests green, clippy `-D warnings`, fmt clean, sanitizers | |
338 | +clean (core touched), benchmarks appended (perf touched), ADR recorded | |
339 | +(architecture touched). | |
340 | + | |
341 | +--- | |
342 | + | |
601 | 343 | ## License |
602 | 344 | |
603 | -This project is licensed under the terms of the [MIT License](LICENSE). | |
604 | -# CI trigger | |
345 | +[MIT](LICENSE) Β© JustAPI contributors | |
605 | 346 | |
βΌassets/justapi-benchmark.svg
+109-0
| @@ -0,0 +1,109 @@ | ||
1 | +<svg width="1200" height="480" viewBox="0 0 1200 480" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
2 | + <defs> | |
3 | + <linearGradient id="bg3" x1="0" y1="0" x2="1200" y2="480" gradientUnits="userSpaceOnUse"> | |
4 | + <stop stop-color="#0b0e1a"/> | |
5 | + <stop offset="1" stop-color="#111631"/> | |
6 | + </linearGradient> | |
7 | + <linearGradient id="barJ" x1="0" y1="0" x2="0" y2="1"> | |
8 | + <stop offset="0" stop-color="#f5a524"/> | |
9 | + <stop offset="1" stop-color="#e6522c"/> | |
10 | + </linearGradient> | |
11 | + <linearGradient id="barO" x1="0" y1="0" x2="0" y2="1"> | |
12 | + <stop offset="0" stop-color="#22d3ee"/> | |
13 | + <stop offset="1" stop-color="#0e7490"/> | |
14 | + </linearGradient> | |
15 | + <linearGradient id="barM" x1="0" y1="0" x2="0" y2="1"> | |
16 | + <stop offset="0" stop-color="#8b5cf6"/> | |
17 | + <stop offset="1" stop-color="#5b21b6"/> | |
18 | + </linearGradient> | |
19 | + </defs> | |
20 | + | |
21 | + <rect width="1200" height="480" fill="url(#bg3)"/> | |
22 | + | |
23 | + <text x="600" y="45" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="24" font-weight="700" fill="#e2e8f0" letter-spacing="1">Real numbers, same hardware, same workload</text> | |
24 | + <text x="600" y="72" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#94a3b8">hello-world Β· 100 concurrent connections Β· 30s</text> | |
25 | + | |
26 | + <!-- axis --> | |
27 | + <line x1="120" y1="400" x2="1140" y2="400" stroke="#334155" stroke-width="2"/> | |
28 | + <line x1="120" y1="400" x2="120" y2="60" stroke="#334155" stroke-width="2"/> | |
29 | + | |
30 | + <!-- gridlines --> | |
31 | + <g stroke="#1e293b" stroke-width="1"> | |
32 | + <line x1="120" y1="340" x2="1140" y2="340"/> | |
33 | + <line x1="120" y1="280" x2="1140" y2="280"/> | |
34 | + <line x1="120" y1="220" x2="1140" y2="220"/> | |
35 | + <line x1="120" y1="160" x2="1140" y2="160"/> | |
36 | + <line x1="120" y1="100" x2="1140" y2="100"/> | |
37 | + </g> | |
38 | + | |
39 | + <!-- JustAPI bar: 766k β height ~300 --> | |
40 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
41 | + <rect x="170" y="100" width="90" height="300" rx="8" fill="url(#barJ)"> | |
42 | + <animate attributeName="height" values="0;300" dur="1.2s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
43 | + <animate attributeName="y" values="400;100" dur="1.2s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
44 | + </rect> | |
45 | + <text x="215" y="88" text-anchor="middle" font-size="26" font-weight="800" fill="#f5a524"> | |
46 | + 766k | |
47 | + <animate attributeName="opacity" values="0;1" dur="1.4s" fill="freeze"/> | |
48 | + </text> | |
49 | + <text x="215" y="420" text-anchor="middle" font-size="14" font-weight="700" fill="#e2e8f0">JustAPI</text> | |
50 | + <text x="215" y="438" text-anchor="middle" font-size="11" fill="#94a3b8">native fast path</text> | |
51 | + | |
52 | + <!-- Granian: 314k β height 123 --> | |
53 | + <rect x="330" y="277" width="90" height="123" rx="8" fill="url(#barO)"> | |
54 | + <animate attributeName="height" values="0;123" dur="1.2s" begin="0.3s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
55 | + <animate attributeName="y" values="400;277" dur="1.2s" begin="0.3s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
56 | + </rect> | |
57 | + <text x="375" y="265" text-anchor="middle" font-size="22" font-weight="700" fill="#22d3ee"> | |
58 | + 314k | |
59 | + <animate attributeName="opacity" values="0;1" dur="1.5s" begin="0.5s" fill="freeze"/> | |
60 | + </text> | |
61 | + <text x="375" y="420" text-anchor="middle" font-size="14" fill="#e2e8f0">Granian</text> | |
62 | + <text x="375" y="438" text-anchor="middle" font-size="11" fill="#94a3b8">ASGI</text> | |
63 | + | |
64 | + <!-- FastAPI: 36k β height 14 --> | |
65 | + <rect x="490" y="386" width="90" height="14" rx="8" fill="url(#barM)"> | |
66 | + <animate attributeName="height" values="0;14" dur="1.2s" begin="0.6s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
67 | + <animate attributeName="y" values="400;386" dur="1.2s" begin="0.6s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
68 | + </rect> | |
69 | + <text x="535" y="376" text-anchor="middle" font-size="18" font-weight="600" fill="#8b5cf6"> | |
70 | + 36k | |
71 | + <animate attributeName="opacity" values="0;1" dur="1.5s" begin="0.8s" fill="freeze"/> | |
72 | + </text> | |
73 | + <text x="535" y="420" text-anchor="middle" font-size="14" fill="#e2e8f0">FastAPI</text> | |
74 | + <text x="535" y="438" text-anchor="middle" font-size="11" fill="#94a3b8">Uvicorn</text> | |
75 | + | |
76 | + <!-- Robyn: 39k --> | |
77 | + <rect x="650" y="384" width="90" height="16" rx="8" fill="url(#barO)" opacity="0.8"> | |
78 | + <animate attributeName="height" values="0;16" dur="1.2s" begin="0.9s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
79 | + <animate attributeName="y" values="400;384" dur="1.2s" begin="0.9s" fill="freeze" calcMode="spline" keySplines="0.34 1.56 0.64 1"/> | |
80 | + </rect> | |
81 | + <text x="695" y="374" text-anchor="middle" font-size="18" font-weight="600" fill="#22d3ee"> | |
82 | + 39k | |
83 | + <animate attributeName="opacity" values="0;1" dur="1.5s" begin="1.1s" fill="freeze"/> | |
84 | + </text> | |
85 | + <text x="695" y="420" text-anchor="middle" font-size="14" fill="#e2e8f0">Robyn</text> | |
86 | + <text x="695" y="438" text-anchor="middle" font-size="11" fill="#94a3b8">Rust</text> | |
87 | + </g> | |
88 | + | |
89 | + <!-- 2.4x callout --> | |
90 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
91 | + <rect x="830" y="130" width="300" height="70" rx="12" fill="#ffffff" fill-opacity="0.05" stroke="#f5a524" stroke-width="1.5"> | |
92 | + <animate attributeName="stroke-opacity" values="0.4;1;0.4" dur="2.5s" repeatCount="indefinite"/> | |
93 | + </rect> | |
94 | + <text x="980" y="158" text-anchor="middle" font-size="28" font-weight="800" fill="#f5a524">2.4x</text> | |
95 | + <text x="980" y="182" text-anchor="middle" font-size="13" fill="#94a3b8">faster than Granian</text> | |
96 | + </g> | |
97 | + | |
98 | + <!-- JSON echo callout --> | |
99 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
100 | + <rect x="830" y="225" width="300" height="70" rx="12" fill="#ffffff" fill-opacity="0.05" stroke="#22d3ee" stroke-width="1.5"> | |
101 | + <animate attributeName="stroke-opacity" values="0.4;1;0.4" dur="3s" repeatCount="indefinite"/> | |
102 | + </rect> | |
103 | + <text x="980" y="253" text-anchor="middle" font-size="28" font-weight="800" fill="#22d3ee">5.4x</text> | |
104 | + <text x="980" y="277" text-anchor="middle" font-size="13" fill="#94a3b8">JSON echo vs Granian</text> | |
105 | + </g> | |
106 | + | |
107 | + <!-- footnote --> | |
108 | + <text x="600" y="468" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#64748b">full methodology in BENCHMARKS.md Β· p99: JustAPI 0.48ms Β· Granian 0.74ms Β· FastAPI 24.6ms</text> | |
109 | +</svg> | |
0 | 110 | |
βΌassets/justapi-features.svg
+141-0
| @@ -0,0 +1,141 @@ | ||
1 | +<svg width="1200" height="560" viewBox="0 0 1200 560" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
2 | + <defs> | |
3 | + <linearGradient id="bg4" x1="0" y1="0" x2="1200" y2="560" gradientUnits="userSpaceOnUse"> | |
4 | + <stop stop-color="#0b0e1a"/> | |
5 | + <stop offset="1" stop-color="#111631"/> | |
6 | + </linearGradient> | |
7 | + <radialGradient id="core" cx="0.5" cy="0.5" r="0.5"> | |
8 | + <stop offset="0" stop-color="#e6522c" stop-opacity="0.3"/> | |
9 | + <stop offset="1" stop-color="#e6522c" stop-opacity="0"/> | |
10 | + </radialGradient> | |
11 | + </defs> | |
12 | + | |
13 | + <rect width="1200" height="560" fill="url(#bg4)"/> | |
14 | + <circle cx="600" cy="270" r="260" fill="url(#core)"> | |
15 | + <animate attributeName="r" values="240;270;240" dur="6s" repeatCount="indefinite"/> | |
16 | + </circle> | |
17 | + | |
18 | + <!-- center --> | |
19 | + <text x="600" y="255" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="34" font-weight="900" fill="#e6522c" letter-spacing="2">JUSTAPI</text> | |
20 | + <text x="600" y="285" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#94a3b8">Rust core Β· Python surface</text> | |
21 | + | |
22 | + <!-- feature nodes: (cx, cy, icon, title, sub) --> | |
23 | + <!-- 12 nodes in 3 rings of 4 --> | |
24 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
25 | + <!-- ring A: performance --> | |
26 | + <g> | |
27 | + <circle cx="600" cy="70" r="34" fill="#1e293b" stroke="#f5a524" stroke-width="2"/> | |
28 | + <text x="600" y="66" text-anchor="middle" font-size="20">β‘</text> | |
29 | + <text x="600" y="86" text-anchor="middle" font-size="11" fill="#f5a524" font-weight="700">766k req/s</text> | |
30 | + <circle r="3" fill="#f5a524"> | |
31 | + <animate attributeName="opacity" values="1;0.2;1" dur="1.6s" repeatCount="indefinite"/> | |
32 | + </circle> | |
33 | + <text x="600" y="125" text-anchor="middle" font-size="12" fill="#94a3b8">hello-world native</text> | |
34 | + </g> | |
35 | + | |
36 | + <g> | |
37 | + <circle cx="1130" cy="270" r="34" fill="#1e293b" stroke="#22d3ee" stroke-width="2"/> | |
38 | + <text x="1130" y="266" text-anchor="middle" font-size="20">ποΈ</text> | |
39 | + <text x="1130" y="286" text-anchor="middle" font-size="11" fill="#22d3ee" font-weight="700">native DB awaits</text> | |
40 | + <circle r="3" fill="#22d3ee"> | |
41 | + <animate attributeName="opacity" values="1;0.2;1" dur="1.8s" repeatCount="indefinite"/> | |
42 | + </circle> | |
43 | + <text x="1130" y="325" text-anchor="middle" font-size="12" fill="#94a3b8">query_async Β· 53x</text> | |
44 | + </g> | |
45 | + | |
46 | + <g> | |
47 | + <circle cx="600" cy="470" r="34" fill="#1e293b" stroke="#8b5cf6" stroke-width="2"/> | |
48 | + <text x="600" y="466" text-anchor="middle" font-size="20">π§ </text> | |
49 | + <text x="600" y="486" text-anchor="middle" font-size="11" fill="#8b5cf6" font-weight="700">free-threaded</text> | |
50 | + <circle r="3" fill="#8b5cf6"> | |
51 | + <animate attributeName="opacity" values="1;0.2;1" dur="2s" repeatCount="indefinite"/> | |
52 | + </circle> | |
53 | + <text x="600" y="525" text-anchor="middle" font-size="12" fill="#94a3b8">CPython 3.14t Β· 12.4x CPU</text> | |
54 | + </g> | |
55 | + | |
56 | + <g> | |
57 | + <circle cx="70" cy="270" r="34" fill="#1e293b" stroke="#e6522c" stroke-width="2"/> | |
58 | + <text x="70" y="266" text-anchor="middle" font-size="20">π</text> | |
59 | + <text x="70" y="286" text-anchor="middle" font-size="11" fill="#e6522c" font-weight="700">HTTP/3</text> | |
60 | + <circle r="3" fill="#e6522c"> | |
61 | + <animate attributeName="opacity" values="1;0.2;1" dur="2.2s" repeatCount="indefinite"/> | |
62 | + </circle> | |
63 | + <text x="70" y="325" text-anchor="middle" font-size="12" fill="#94a3b8">QUIC, no one else ships it</text> | |
64 | + </g> | |
65 | + | |
66 | + <!-- ring B: middle ring --> | |
67 | + <g> | |
68 | + <circle cx="850" cy="130" r="34" fill="#1e293b" stroke="#22d3ee" stroke-width="2"/> | |
69 | + <text x="850" y="126" text-anchor="middle" font-size="20">π‘</text> | |
70 | + <text x="850" y="146" text-anchor="middle" font-size="11" fill="#22d3ee" font-weight="700">native SSE</text> | |
71 | + <circle r="3" fill="#22d3ee"> | |
72 | + <animate attributeName="opacity" values="1;0.2;1" dur="1.9s" repeatCount="indefinite"/> | |
73 | + </circle> | |
74 | + <text x="850" y="185" text-anchor="middle" font-size="12" fill="#94a3b8">sse_native Β· 0 Python/event</text> | |
75 | + </g> | |
76 | + | |
77 | + <g> | |
78 | + <circle cx="350" cy="130" r="34" fill="#1e293b" stroke="#f5a524" stroke-width="2"/> | |
79 | + <text x="350" y="126" text-anchor="middle" font-size="20">π§©</text> | |
80 | + <text x="350" y="146" text-anchor="middle" font-size="11" fill="#f5a524" font-weight="700">native CRUD</text> | |
81 | + <circle r="3" fill="#f5a524"> | |
82 | + <animate attributeName="opacity" values="1;0.2;1" dur="2.1s" repeatCount="indefinite"/> | |
83 | + </circle> | |
84 | + <text x="350" y="185" text-anchor="middle" font-size="12" fill="#94a3b8">181k SELECT Β· 125x FastAPI</text> | |
85 | + </g> | |
86 | + | |
87 | + <g> | |
88 | + <circle cx="350" cy="410" r="34" fill="#1e293b" stroke="#8b5cf6" stroke-width="2"/> | |
89 | + <text x="350" y="406" text-anchor="middle" font-size="20">π</text> | |
90 | + <text x="350" y="426" text-anchor="middle" font-size="11" fill="#8b5cf6" font-weight="700">multi-worker</text> | |
91 | + <circle r="3" fill="#8b5cf6"> | |
92 | + <animate attributeName="opacity" values="1;0.2;1" dur="2.3s" repeatCount="indefinite"/> | |
93 | + </circle> | |
94 | + <text x="350" y="465" text-anchor="middle" font-size="12" fill="#94a3b8">--workers N Β· 1.88x @4</text> | |
95 | + </g> | |
96 | + | |
97 | + <g> | |
98 | + <circle cx="850" cy="410" r="34" fill="#1e293b" stroke="#e6522c" stroke-width="2"/> | |
99 | + <text x="850" y="406" text-anchor="middle" font-size="20">π</text> | |
100 | + <text x="850" y="426" text-anchor="middle" font-size="11" fill="#e6522c" font-weight="700">OpenAPI 3.1</text> | |
101 | + <circle r="3" fill="#e6522c"> | |
102 | + <animate attributeName="opacity" values="1;0.2;1" dur="2.5s" repeatCount="indefinite"/> | |
103 | + </circle> | |
104 | + <text x="850" y="465" text-anchor="middle" font-size="12" fill="#94a3b8">Pydantic v2 bridge</text> | |
105 | + </g> | |
106 | + | |
107 | + <!-- inner ring: security + observability --> | |
108 | + <g> | |
109 | + <circle cx="600" cy="190" r="26" fill="#111631" stroke="#22d3ee" stroke-width="1.5"/> | |
110 | + <text x="600" y="188" text-anchor="middle" font-size="15">π‘οΈ</text> | |
111 | + <text x="600" y="206" text-anchor="middle" font-size="9" fill="#22d3ee" font-weight="700">JWT Β· rate-limit Β· CORS</text> | |
112 | + </g> | |
113 | + <g> | |
114 | + <circle cx="740" cy="270" r="26" fill="#111631" stroke="#f5a524" stroke-width="1.5"/> | |
115 | + <text x="740" y="268" text-anchor="middle" font-size="15">π</text> | |
116 | + <text x="740" y="286" text-anchor="middle" font-size="9" fill="#f5a524" font-weight="700">OTel Β· Prometheus</text> | |
117 | + </g> | |
118 | + <g> | |
119 | + <circle cx="600" cy="350" r="26" fill="#111631" stroke="#8b5cf6" stroke-width="1.5"/> | |
120 | + <text x="600" y="348" text-anchor="middle" font-size="15">π</text> | |
121 | + <text x="600" y="366" text-anchor="middle" font-size="9" fill="#8b5cf6" font-weight="700">circuit breakers</text> | |
122 | + </g> | |
123 | + <g> | |
124 | + <circle cx="460" cy="270" r="26" fill="#111631" stroke="#e6522c" stroke-width="1.5"/> | |
125 | + <text x="460" y="268" text-anchor="middle" font-size="15">π§ͺ</text> | |
126 | + <text x="460" y="286" text-anchor="middle" font-size="9" fill="#e6522c" font-weight="700">mypy-clean stubs</text> | |
127 | + </g> | |
128 | + | |
129 | + <!-- connecting lines --> | |
130 | + <g stroke="#334155" stroke-width="1" opacity="0.6"> | |
131 | + <line x1="600" y1="104" x2="850" y2="130"/> | |
132 | + <line x1="600" y1="104" x2="350" y2="130"/> | |
133 | + <line x1="1130" y1="236" x2="850" y2="164"/> | |
134 | + <line x1="70" y1="236" x2="350" y2="164"/> | |
135 | + <line x1="600" y1="436" x2="850" y2="410"/> | |
136 | + <line x1="600" y1="436" x2="350" y2="410"/> | |
137 | + <line x1="1130" y1="304" x2="740" y2="296"/> | |
138 | + <line x1="70" y1="304" x2="460" y2="296"/> | |
139 | + </g> | |
140 | + </g> | |
141 | +</svg> | |
0 | 142 | |
βΌassets/justapi-hero.svg
+118-0
| @@ -0,0 +1,118 @@ | ||
1 | +<svg width="1200" height="630" viewBox="0 0 1200 630" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
2 | + <defs> | |
3 | + <linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse"> | |
4 | + <stop stop-color="#0b0e1a"/> | |
5 | + <stop offset="1" stop-color="#111631"/> | |
6 | + </linearGradient> | |
7 | + <linearGradient id="grad1" x1="0" y1="0" x2="1" y2="1"> | |
8 | + <stop offset="0" stop-color="#ff7a3d"/> | |
9 | + <stop offset="0.5" stop-color="#e6522c"/> | |
10 | + <stop offset="1" stop-color="#f5a524"/> | |
11 | + </linearGradient> | |
12 | + <linearGradient id="grad2" x1="0" y1="0" x2="1" y2="1"> | |
13 | + <stop offset="0" stop-color="#22d3ee"/> | |
14 | + <stop offset="1" stop-color="#8b5cf6"/> | |
15 | + </linearGradient> | |
16 | + <radialGradient id="glow" cx="0.5" cy="0.5" r="0.5"> | |
17 | + <stop offset="0" stop-color="#e6522c" stop-opacity="0.35"/> | |
18 | + <stop offset="1" stop-color="#e6522c" stop-opacity="0"/> | |
19 | + </radialGradient> | |
20 | + <filter id="blur1" x="-50%" y="-50%" width="200%" height="200%"> | |
21 | + <feGaussianBlur stdDeviation="6"/> | |
22 | + </filter> | |
23 | + <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"> | |
24 | + <feDropShadow dx="0" dy="4" stdDeviation="10" flood-color="#e6522c" flood-opacity="0.4"/> | |
25 | + </filter> | |
26 | + </defs> | |
27 | + | |
28 | + <rect width="1200" height="630" fill="url(#bg)"/> | |
29 | + | |
30 | + <!-- ============ MATRIX RAIN (SMIL animated falling glyphs) ============ --> | |
31 | + <g font-family="'Courier New', monospace" font-size="16" fill="#22d3ee" opacity="0.35"> | |
32 | + <!-- column 1 --> | |
33 | + <text x="40" y="60">1</text><text x="40" y="76">0</text><text x="40" y="92">1</text><text x="40" y="108">0</text> | |
34 | + <text x="40" y="124">0</text><text x="40" y="140">1</text><text x="40" y="156">1</text><text x="40" y="172">0</text> | |
35 | + <text x="40" y="188">1</text><text x="40" y="204">0</text><text x="40" y="220">1</text><text x="40" y="236">0</text> | |
36 | + <text x="40" y="252">1</text><text x="40" y="268">0</text><text x="40" y="284">1</text><text x="40" y="300">0</text> | |
37 | + <text x="40" y="316">0</text><text x="40" y="332">1</text><text x="40" y="348">1</text><text x="40" y="364">0</text> | |
38 | + <text x="40" y="380">1</text><text x="40" y="396">0</text><text x="40" y="412">1</text><text x="40" y="428">0</text> | |
39 | + <text x="40" y="444">1</text><text x="40" y="460">0</text><text x="40" y="476">1</text><text x="40" y="492">0</text> | |
40 | + <text x="40" y="508">0</text><text x="40" y="524">1</text><text x="40" y="540">1</text><text x="40" y="556">0</text> | |
41 | + <text x="40" y="572">1</text><text x="40" y="588">0</text> | |
42 | + <animate attributeName="opacity" values="0.35;0.5;0.35" dur="4s" repeatCount="indefinite"/> | |
43 | + <!-- column 2 --> | |
44 | + <text x="1120" y="90" fill="#e6522c">0</text><text x="1120" y="106" fill="#e6522c">1</text><text x="1120" y="122" fill="#e6522c">1</text><text x="1120" y="138" fill="#e6522c">0</text> | |
45 | + <text x="1120" y="154" fill="#e6522c">1</text><text x="1120" y="170" fill="#e6522c">0</text><text x="1120" y="186" fill="#e6522c">0</text><text x="1120" y="202" fill="#e6522c">1</text> | |
46 | + <text x="1120" y="218" fill="#e6522c">1</text><text x="1120" y="234" fill="#e6522c">0</text><text x="1120" y="250" fill="#e6522c">1</text><text x="1120" y="266" fill="#e6522c">0</text> | |
47 | + <text x="1120" y="282" fill="#e6522c">1</text><text x="1120" y="298" fill="#e6522c">1</text><text x="1120" y="314" fill="#e6522c">0</text><text x="1120" y="330" fill="#e6522c">0</text> | |
48 | + <text x="1120" y="346" fill="#e6522c">1</text><text x="1120" y="362" fill="#e6522c">0</text><text x="1120" y="378" fill="#e6522c">1</text><text x="1120" y="394" fill="#e6522c">1</text> | |
49 | + <animate attributeName="opacity" values="0.35;0.55;0.35" dur="3.2s" repeatCount="indefinite"/> | |
50 | + <!-- floating glyphs --> | |
51 | + <text x="150" y="150" font-size="12" fill="#22d3ee" opacity="0.5">0x</text> | |
52 | + <text x="1030" y="420" font-size="12" fill="#22d3ee" opacity="0.5">k8s</text> | |
53 | + <text x="985" y="250" font-size="12" fill="#22d3ee" opacity="0.45">tokio</text> | |
54 | + <text x="180" y="470" font-size="12" fill="#22d3ee" opacity="0.45">rustls</text> | |
55 | + </g> | |
56 | + | |
57 | + <!-- ============ CENTER GLOW + LOGO ============ --> | |
58 | + <circle cx="600" cy="255" r="220" fill="url(#glow)"> | |
59 | + <animate attributeName="r" values="200;235;200" dur="5s" repeatCount="indefinite"/> | |
60 | + </circle> | |
61 | + | |
62 | + <!-- lightning bolt logo --> | |
63 | + <g filter="url(#shadow)"> | |
64 | + <path d="M610 130 L475 300 L580 300 L545 410 L690 260 L590 260 Z" fill="url(#grad1)"> | |
65 | + <animateTransform attributeName="transform" type="translate" values="0,0;0,-6;0,0" dur="3.5s" repeatCount="indefinite"/> | |
66 | + </path> | |
67 | + <path d="M610 130 L475 300 L580 300 L545 410 L690 260 L590 260 Z" fill="none" stroke="url(#grad2)" stroke-width="3" stroke-linejoin="round"> | |
68 | + <animate attributeName="stroke-opacity" values="0.6;1;0.6" dur="2s" repeatCount="indefinite"/> | |
69 | + </path> | |
70 | + </g> | |
71 | + | |
72 | + <!-- title --> | |
73 | + <text x="600" y="455" text-anchor="middle" font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif" font-size="76" font-weight="900" fill="url(#grad1)" letter-spacing="6"> | |
74 | + JUSTAPI | |
75 | + <animate attributeName="letter-spacing" values="6;10;6" dur="4s" repeatCount="indefinite"/> | |
76 | + </text> | |
77 | + <text x="600" y="495" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="22" fill="#a5b4fc" letter-spacing="2"> | |
78 | + Python writes the logic. Rust does everything else. | |
79 | + </text> | |
80 | + | |
81 | + <!-- rotating orbit ring --> | |
82 | + <g transform="translate(600,255)"> | |
83 | + <ellipse cx="0" cy="0" rx="250" ry="80" fill="none" stroke="#22d3ee" stroke-width="1.5" stroke-dasharray="6 10" opacity="0.5"> | |
84 | + <animateTransform attributeName="transform" type="rotate" from="0 0 0" to="360 0 0" dur="18s" repeatCount="indefinite"/> | |
85 | + </ellipse> | |
86 | + <circle cx="250" cy="0" r="6" fill="#22d3ee"> | |
87 | + <animateTransform attributeName="transform" type="rotate" from="0 0 0" to="360 0 0" dur="18s" repeatCount="indefinite"/> | |
88 | + </circle> | |
89 | + <ellipse cx="0" cy="0" rx="300" ry="100" fill="none" stroke="#8b5cf6" stroke-width="1" stroke-dasharray="4 14" opacity="0.35"> | |
90 | + <animateTransform attributeName="transform" type="rotate" from="360 0 0" to="0 0 0" dur="24s" repeatCount="indefinite"/> | |
91 | + </ellipse> | |
92 | + </g> | |
93 | + | |
94 | + <!-- ============ FOOTER METRICS BAND ============ --> | |
95 | + <g font-family="'Segoe UI', Arial, sans-serif" font-weight="600"> | |
96 | + <!-- metric 1 --> | |
97 | + <rect x="90" y="545" width="230" height="56" rx="10" fill="#ffffff" fill-opacity="0.04" stroke="#22d3ee" stroke-opacity="0.25"/> | |
98 | + <text x="205" y="575" text-anchor="middle" font-size="26" fill="#22d3ee">766k</text> | |
99 | + <text x="205" y="593" text-anchor="middle" font-size="13" fill="#94a3b8" font-weight="400">req/s hello-world</text> | |
100 | + <!-- metric 2 --> | |
101 | + <rect x="350" y="545" width="230" height="56" rx="10" fill="#ffffff" fill-opacity="0.04" stroke="#f5a524" stroke-opacity="0.25"/> | |
102 | + <text x="465" y="575" text-anchor="middle" font-size="26" fill="#f5a524">5.4x</text> | |
103 | + <text x="465" y="593" text-anchor="middle" font-size="13" fill="#94a3b8" font-weight="400">JSON-echo vs Granian</text> | |
104 | + <!-- metric 3 --> | |
105 | + <rect x="620" y="545" width="230" height="56" rx="10" fill="#ffffff" fill-opacity="0.04" stroke="#e6522c" stroke-opacity="0.25"/> | |
106 | + <text x="735" y="575" text-anchor="middle" font-size="26" fill="#e6522c">53x</text> | |
107 | + <text x="735" y="593" text-anchor="middle" font-size="13" fill="#94a3b8" font-weight="400">native async DB awaits</text> | |
108 | + <!-- metric 4 --> | |
109 | + <rect x="880" y="545" width="230" height="56" rx="10" fill="#ffffff" fill-opacity="0.04" stroke="#8b5cf6" stroke-opacity="0.25"/> | |
110 | + <text x="995" y="575" text-anchor="middle" font-size="26" fill="#8b5cf6">12MB</text> | |
111 | + <text x="995" y="593" text-anchor="middle" font-size="13" fill="#94a3b8" font-weight="400">resident memory</text> | |
112 | + </g> | |
113 | + | |
114 | + <!-- scanning line --> | |
115 | + <rect x="0" y="0" width="1200" height="3" fill="#22d3ee" opacity="0.12"> | |
116 | + <animate attributeName="y" values="0;627;0" dur="7s" repeatCount="indefinite"/> | |
117 | + </rect> | |
118 | +</svg> | |
0 | 119 | |
βΌassets/justapi-pipeline.svg
+150-0
| @@ -0,0 +1,150 @@ | ||
1 | +<svg width="1200" height="420" viewBox="0 0 1200 420" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
2 | + <defs> | |
3 | + <linearGradient id="bg2" x1="0" y1="0" x2="1200" y2="420" gradientUnits="userSpaceOnUse"> | |
4 | + <stop stop-color="#0b0e1a"/> | |
5 | + <stop offset="1" stop-color="#111631"/> | |
6 | + </linearGradient> | |
7 | + <linearGradient id="boxRust" x1="0" y1="0" x2="0" y2="1"> | |
8 | + <stop offset="0" stop-color="#e6522c"/> | |
9 | + <stop offset="1" stop-color="#b03a1c"/> | |
10 | + </linearGradient> | |
11 | + <linearGradient id="boxPy" x1="0" y1="0" x2="0" y2="1"> | |
12 | + <stop offset="0" stop-color="#22d3ee"/> | |
13 | + <stop offset="1" stop-color="#0e7490"/> | |
14 | + </linearGradient> | |
15 | + <filter id="glow2" x="-30%" y="-30%" width="160%" height="160%"> | |
16 | + <feDropShadow dx="0" dy="0" stdDeviation="6" flood-color="#22d3ee" flood-opacity="0.5"/> | |
17 | + </filter> | |
18 | + </defs> | |
19 | + | |
20 | + <rect width="1200" height="420" fill="url(#bg2)"/> | |
21 | + | |
22 | + <text x="600" y="40" text-anchor="middle" font-family="'Segoe UI', Arial, sans-serif" font-size="24" font-weight="700" fill="#e2e8f0" letter-spacing="1">One request, end to end</text> | |
23 | + | |
24 | + <!-- ============ ROW 1: Rust pipeline ============ --> | |
25 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
26 | + <!-- boxes: x positions --> | |
27 | + <!-- Kernel --> | |
28 | + <rect x="20" y="80" width="150" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
29 | + <text x="95" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">Kernel</text> | |
30 | + <text x="95" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">epoll / io_uring</text> | |
31 | + | |
32 | + <!-- tokio --> | |
33 | + <rect x="220" y="80" width="150" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
34 | + <text x="295" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">tokio</text> | |
35 | + <text x="295" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">async runtime</text> | |
36 | + | |
37 | + <!-- TLS --> | |
38 | + <rect x="420" y="80" width="150" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
39 | + <text x="495" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">TLS</text> | |
40 | + <text x="495" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">rustls</text> | |
41 | + | |
42 | + <!-- HTTP --> | |
43 | + <rect x="620" y="80" width="150" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
44 | + <text x="695" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">HTTP/1.1 Β· 2</text> | |
45 | + <text x="695" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">hyper</text> | |
46 | + | |
47 | + <!-- Router --> | |
48 | + <rect x="820" y="80" width="150" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
49 | + <text x="895" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">Router</text> | |
50 | + <text x="895" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">matchit Β· 51ns</text> | |
51 | + | |
52 | + <!-- Middleware --> | |
53 | + <rect x="1020" y="80" width="160" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
54 | + <text x="1100" y="105" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">Middleware</text> | |
55 | + <text x="1100" y="125" text-anchor="middle" font-size="11" fill="#94a3b8">auth Β· CORS Β· rate-limit</text> | |
56 | + | |
57 | + <!-- arrows row 1 --> | |
58 | + <line x1="170" y1="111" x2="218" y2="111" stroke="#22d3ee" stroke-width="2" marker-end="url(#arr)"> | |
59 | + <animate attributeName="stroke-opacity" values="0.2;1;0.2" dur="2s" repeatCount="indefinite"/> | |
60 | + </line> | |
61 | + <line x1="370" y1="111" x2="418" y2="111" stroke="#22d3ee" stroke-width="2"> | |
62 | + <animate attributeName="stroke-opacity" values="0.2;1;0.2" dur="2.3s" repeatCount="indefinite"/> | |
63 | + </line> | |
64 | + <line x1="570" y1="111" x2="618" y2="111" stroke="#22d3ee" stroke-width="2"> | |
65 | + <animate attributeName="stroke-opacity" values="0.2;1;0.2" dur="2.6s" repeatCount="indefinite"/> | |
66 | + </line> | |
67 | + <line x1="770" y1="111" x2="818" y2="111" stroke="#22d3ee" stroke-width="2"> | |
68 | + <animate attributeName="stroke-opacity" values="0.2;1;0.2" dur="2.9s" repeatCount="indefinite"/> | |
69 | + </line> | |
70 | + <line x1="970" y1="111" x2="1018" y2="111" stroke="#22d3ee" stroke-width="2"> | |
71 | + <animate attributeName="stroke-opacity" values="0.2;1;0.2" dur="3.2s" repeatCount="indefinite"/> | |
72 | + </line> | |
73 | + <marker id="arr" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> | |
74 | + <path d="M0 0 L10 5 L0 10 Z" fill="#22d3ee"/> | |
75 | + </marker> | |
76 | + | |
77 | + <!-- moving packet dots --> | |
78 | + <circle r="4" fill="#22d3ee" filter="url(#glow2)"> | |
79 | + <animateMotion dur="2s" repeatCount="indefinite" path="M170,111 L218,111"/> | |
80 | + </circle> | |
81 | + <circle r="4" fill="#22d3ee" filter="url(#glow2)"> | |
82 | + <animateMotion dur="2.3s" repeatCount="indefinite" path="M370,111 L418,111"/> | |
83 | + </circle> | |
84 | + <circle r="4" fill="#22d3ee" filter="url(#glow2)"> | |
85 | + <animateMotion dur="2.6s" repeatCount="indefinite" path="M570,111 L618,111"/> | |
86 | + </circle> | |
87 | + <circle r="4" fill="#22d3ee" filter="url(#glow2)"> | |
88 | + <animateMotion dur="2.9s" repeatCount="indefinite" path="M770,111 L818,111"/> | |
89 | + </circle> | |
90 | + <circle r="4" fill="#22d3ee" filter="url(#glow2)"> | |
91 | + <animateMotion dur="3.2s" repeatCount="indefinite" path="M970,111 L1018,111"/> | |
92 | + </circle> | |
93 | + </g> | |
94 | + | |
95 | + <!-- down arrow from middleware --> | |
96 | + <path d="M1100,142 L1100,170 L1000,170 L1000,195" stroke="#8b5cf6" stroke-width="2.5" fill="none" stroke-dasharray="6 5"> | |
97 | + <animate attributeName="stroke-dashoffset" values="0;-22" dur="1.5s" repeatCount="indefinite"/> | |
98 | + </path> | |
99 | + | |
100 | + <!-- ============ PYTHON BOUNDARY (glowing) ============ --> | |
101 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
102 | + <rect x="820" y="200" width="360" height="72" rx="12" fill="url(#boxPy)" filter="url(#glow2)"> | |
103 | + <animate attributeName="opacity" values="0.85;1;0.85" dur="3s" repeatCount="indefinite"/> | |
104 | + </rect> | |
105 | + <text x="1000" y="228" text-anchor="middle" font-size="16" font-weight="700" fill="#03141f">Python boundary</text> | |
106 | + <text x="1000" y="250" text-anchor="middle" font-size="12" fill="#0b3a4d">zero-copy PyO3 Β· GIL pool Β· free-threaded ready</text> | |
107 | + </g> | |
108 | + | |
109 | + <!-- down to handler --> | |
110 | + <path d="M1000,272 L1000,300 L700,300 L700,323" stroke="#8b5cf6" stroke-width="2.5" fill="none" stroke-dasharray="6 5"> | |
111 | + <animate attributeName="stroke-dashoffset" values="0;-22" dur="1.8s" repeatCount="indefinite"/> | |
112 | + </path> | |
113 | + | |
114 | + <!-- ============ YOUR CODE (highlighted) ============ --> | |
115 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
116 | + <rect x="560" y="325" width="280" height="72" rx="12" fill="#ffffff" fill-opacity="0.05" stroke="#f5a524" stroke-width="2"> | |
117 | + <animate attributeName="stroke-opacity" values="0.5;1;0.5" dur="2.2s" repeatCount="indefinite"/> | |
118 | + </rect> | |
119 | + <text x="700" y="350" text-anchor="middle" font-size="15" font-weight="700" fill="#f5a524">Your Python code</text> | |
120 | + <text x="700" y="372" text-anchor="middle" font-size="12" fill="#94a3b8">the only part that is yours</text> | |
121 | + </g> | |
122 | + | |
123 | + <!-- path from handler to serializer --> | |
124 | + <path d="M700,325 L700,300 L300,300 L300,272" stroke="#e6522c" stroke-width="2.5" fill="none" stroke-dasharray="6 5"> | |
125 | + <animate attributeName="stroke-dashoffset" values="0;-22" dur="1.6s" repeatCount="indefinite"/> | |
126 | + </path> | |
127 | + | |
128 | + <!-- ============ ROW 2: response path ============ --> | |
129 | + <g font-family="'Segoe UI', Arial, sans-serif"> | |
130 | + <rect x="20" y="200" width="240" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
131 | + <text x="140" y="225" text-anchor="middle" font-size="14" font-weight="700" fill="#e6522c">Serializer</text> | |
132 | + <text x="140" y="245" text-anchor="middle" font-size="11" fill="#94a3b8">serde_json Β· simd-json</text> | |
133 | + | |
134 | + <rect x="20" y="325" width="240" height="62" rx="10" fill="#1e293b" stroke="#334155"/> | |
135 | + <text x="140" y="350" text-anchor="middle" font-size="14" font-weight="700" fill="#22d3ee">Response write</text> | |
136 | + <text x="140" y="370" text-anchor="middle" font-size="11" fill="#94a3b8">back to the socket</text> | |
137 | + | |
138 | + <line x1="140" y1="262" x2="140" y2="323" stroke="#e6522c" stroke-width="2" stroke-dasharray="6 5"> | |
139 | + <animate attributeName="stroke-dashoffset" values="0;-22" dur="1.4s" repeatCount="indefinite"/> | |
140 | + </line> | |
141 | + </g> | |
142 | + | |
143 | + <!-- side legend --> | |
144 | + <g font-family="'Segoe UI', Arial, sans-serif" font-size="12"> | |
145 | + <rect x="420" y="225" width="16" height="16" rx="4" fill="#22d3ee"/> | |
146 | + <text x="445" y="238" fill="#94a3b8">Rust (everything except your code)</text> | |
147 | + <rect x="420" y="252" width="16" height="16" rx="4" fill="#f5a524"/> | |
148 | + <text x="445" y="265" fill="#94a3b8">Python (just your logic)</text> | |
149 | + </g> | |
150 | +</svg> | |
0 | 151 | |