xania 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xania-0.1.2/LICENSE +22 -0
- xania-0.1.2/PKG-INFO +485 -0
- xania-0.1.2/README.md +461 -0
- xania-0.1.2/pyproject.toml +41 -0
- xania-0.1.2/setup.cfg +4 -0
- xania-0.1.2/xania/__init__.py +23 -0
- xania-0.1.2/xania/__main__.py +8 -0
- xania-0.1.2/xania/cli.py +330 -0
- xania-0.1.2/xania/engine/runtime.py +50 -0
- xania-0.1.2/xania/engine/serializer.py +60 -0
- xania-0.1.2/xania/example/counter.py +49 -0
- xania-0.1.2/xania/renderer/__init__.py +12 -0
- xania-0.1.2/xania/renderer/component.py +46 -0
- xania-0.1.2/xania/renderer/elements.py +323 -0
- xania-0.1.2/xania/renderer/index.html +64 -0
- xania-0.1.2/xania/renderer/registry.py +34 -0
- xania-0.1.2/xania/renderer/render.py +12 -0
- xania-0.1.2/xania/renderer/state.py +154 -0
- xania-0.1.2/xania/renderer/test.html +511 -0
- xania-0.1.2/xania/runtime/__init__.py +22 -0
- xania-0.1.2/xania/runtime/compiler.py +95 -0
- xania-0.1.2/xania/runtime/spa.py +119 -0
- xania-0.1.2/xania/static/app.js +1115 -0
- xania-0.1.2/xania/static/runtime.js +113 -0
- xania-0.1.2/xania/static/spa_runtime.js +271 -0
- xania-0.1.2/xania/web/app.py +40 -0
- xania-0.1.2/xania/web/auth.py +239 -0
- xania-0.1.2/xania/web/config.py +43 -0
- xania-0.1.2/xania/web/ratelimit.py +46 -0
- xania-0.1.2/xania/web/routes.py +248 -0
- xania-0.1.2/xania/web/schemas.py +24 -0
- xania-0.1.2/xania/web/serve.py +61 -0
- xania-0.1.2/xania.egg-info/PKG-INFO +485 -0
- xania-0.1.2/xania.egg-info/SOURCES.txt +36 -0
- xania-0.1.2/xania.egg-info/dependency_links.txt +1 -0
- xania-0.1.2/xania.egg-info/entry_points.txt +2 -0
- xania-0.1.2/xania.egg-info/requires.txt +8 -0
- xania-0.1.2/xania.egg-info/top_level.txt +1 -0
xania-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
xania-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xania
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A minimal React-like Python UI framework on FastAPI (CSR runtime + component model).
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: xania,ui,spa,fastapi,vdom,framework
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Framework :: FastAPI
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: click>=8.3.3
|
|
17
|
+
Requires-Dist: fastapi>=0.136.1
|
|
18
|
+
Requires-Dist: pydantic>=2.13.3
|
|
19
|
+
Requires-Dist: uvicorn>=0.46.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.5.0; extra == "dev"
|
|
22
|
+
Requires-Dist: twine>=6.2.0; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Xania — Python UI Framework for SPAs
|
|
26
|
+
|
|
27
|
+
**Build fast, interactive web apps entirely in Python. No JavaScript required.**
|
|
28
|
+
|
|
29
|
+
Xania compiles Python code to standalone Single-Page Applications (SPAs) that run 100% in the browser.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Write UI in Python** – VDOM elements, components, state management
|
|
34
|
+
- **Compile to JavaScript** – Your Python spec → `index.html` + `app.js`
|
|
35
|
+
- **Deploy anywhere** – Static SPA with no server needed
|
|
36
|
+
- **Production-ready** – Built on FastAPI with auth, rate limiting, CSRF protection
|
|
37
|
+
- **Zero client-side code** – Framework handles all routing, state, and rendering
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Requirements
|
|
42
|
+
- Python **3.12+**
|
|
43
|
+
|
|
44
|
+
### 1. Create a new project
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install xania
|
|
48
|
+
xania init my_website
|
|
49
|
+
cd my_website
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Edit your site
|
|
53
|
+
|
|
54
|
+
Edit `app.py` to add pages and content:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from xania import SpaApp, StaticPage, Div, H1, Button
|
|
58
|
+
|
|
59
|
+
app = SpaApp(name="MySite")
|
|
60
|
+
|
|
61
|
+
app.route(
|
|
62
|
+
"/",
|
|
63
|
+
StaticPage(
|
|
64
|
+
title="Home",
|
|
65
|
+
html='<h1>Welcome!</h1><p>Built with Xania</p>'
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Compile
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python app.py
|
|
74
|
+
# Generates: index.html + static/app.js + static/spa_runtime.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 4. Run
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
xania serve .
|
|
81
|
+
# 🚀 Serving SPA from /path/to/my_website
|
|
82
|
+
# 📍 http://127.0.0.1:8000
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Open your browser to `http://127.0.0.1:8000`
|
|
86
|
+
|
|
87
|
+
### 5. Deploy
|
|
88
|
+
|
|
89
|
+
Copy the generated files to any static host:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Upload to Netlify, Vercel, GitHub Pages, S3, etc.
|
|
93
|
+
cp -r . /path/to/deployment/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How It Works
|
|
97
|
+
|
|
98
|
+
### SPA Mode (Recommended)
|
|
99
|
+
|
|
100
|
+
1. **Define your app in Python** – Use `SpaApp` with routes and pages
|
|
101
|
+
2. **Compile** – Xania generates JavaScript that runs entirely in the browser
|
|
102
|
+
3. **Deploy** – Copy `index.html` + `static/` to any static host
|
|
103
|
+
|
|
104
|
+
No server needed for the SPA itself. Client-side routing via History API.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from xania import SpaApp, StaticPage
|
|
108
|
+
|
|
109
|
+
app = SpaApp(name="MySite")
|
|
110
|
+
app.route("/", StaticPage(title="Home", html="..."))
|
|
111
|
+
app.route("/about", StaticPage(title="About", html="..."))
|
|
112
|
+
|
|
113
|
+
# Then: python app.py → generates index.html + app.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Server Mode (Advanced)
|
|
117
|
+
|
|
118
|
+
If you need a backend, integrate with FastAPI:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from fastapi import FastAPI
|
|
122
|
+
from xania import mount_spa
|
|
123
|
+
|
|
124
|
+
app = FastAPI()
|
|
125
|
+
|
|
126
|
+
# Add API routes
|
|
127
|
+
@app.get("/api/data")
|
|
128
|
+
def get_data():
|
|
129
|
+
return {"items": [...]}
|
|
130
|
+
|
|
131
|
+
# Mount compiled SPA
|
|
132
|
+
mount_spa(app, ".")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then: `xania serve .` or `uvicorn app:app`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Two Modes Explained
|
|
140
|
+
|
|
141
|
+
**SPA Mode** (Default)
|
|
142
|
+
- HTML shell + JavaScript SPA
|
|
143
|
+
- Client-side routing (History API)
|
|
144
|
+
- No server needed for UI
|
|
145
|
+
- Perfect for: static sites, blogs, documentation, dashboards
|
|
146
|
+
- Deploy to: Netlify, Vercel, GitHub Pages, S3
|
|
147
|
+
|
|
148
|
+
**Server Mode** (Optional)
|
|
149
|
+
- FastAPI backend + SPA frontend
|
|
150
|
+
- Add custom APIs, auth, data
|
|
151
|
+
- Still compiles to static files
|
|
152
|
+
- Perfect for: full-stack apps with complex backends
|
|
153
|
+
- Deploy to: Heroku, Railway, AWS, DigitalOcean
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Core Concepts
|
|
158
|
+
|
|
159
|
+
**VDOM Elements** – Python classes representing HTML:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from xania import Div, H1, Button, Input
|
|
163
|
+
|
|
164
|
+
Div(
|
|
165
|
+
H1("Welcome"),
|
|
166
|
+
Input(type="text", placeholder="Enter name"),
|
|
167
|
+
Button("Click me", onclick="App.dispatch('Counter', 'click')"),
|
|
168
|
+
class_name="flex flex-col gap-4"
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Components** – Stateful UI units:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from xania import Component, Div, Span, Button
|
|
176
|
+
|
|
177
|
+
class Counter(Component):
|
|
178
|
+
def initial_state(self):
|
|
179
|
+
return {"count": 0}
|
|
180
|
+
|
|
181
|
+
def render(self, state):
|
|
182
|
+
return Div(
|
|
183
|
+
Span(str(state.count)),
|
|
184
|
+
Button("+", onclick="App.dispatch('Counter', 'inc')")
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def on_inc(self, state, payload):
|
|
188
|
+
state.count += 1
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Pages** – Routes in your SPA:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from xania import SpaApp, StaticPage, TemplatePage, JsExpr
|
|
195
|
+
|
|
196
|
+
app = SpaApp()
|
|
197
|
+
|
|
198
|
+
# Static page
|
|
199
|
+
app.route("/", StaticPage(
|
|
200
|
+
title="Home",
|
|
201
|
+
html="<h1>Welcome</h1>"
|
|
202
|
+
))
|
|
203
|
+
|
|
204
|
+
# Template page with placeholders
|
|
205
|
+
app.route("/user/{id}", TemplatePage(
|
|
206
|
+
title="User",
|
|
207
|
+
template="<h1>User {name}</h1>",
|
|
208
|
+
placeholders={"name": "John"}
|
|
209
|
+
))
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Project Structure
|
|
215
|
+
|
|
216
|
+
**Core Modules:**
|
|
217
|
+
- `renderer/` – VDOM elements, components, state management
|
|
218
|
+
- `runtime/` – SPA compiler (Python → JavaScript)
|
|
219
|
+
- `web/` – FastAPI integration, serving, auth
|
|
220
|
+
- `static/` – Browser runtime (`spa_runtime.js`)
|
|
221
|
+
- `cli.py` – Command-line interface
|
|
222
|
+
|
|
223
|
+
**See also:** [documentation/](documentation/) for full tutorial SPA built with Xania
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Examples
|
|
228
|
+
|
|
229
|
+
### Counter with State
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from xania import Component, Div, Button, Span
|
|
233
|
+
|
|
234
|
+
class Counter(Component):
|
|
235
|
+
def initial_state(self):
|
|
236
|
+
return {"count": 0}
|
|
237
|
+
|
|
238
|
+
def render(self, state):
|
|
239
|
+
return Div(
|
|
240
|
+
Span(f"Count: {state.count}"),
|
|
241
|
+
Button("+", onclick="App.dispatch('Counter', 'inc')"),
|
|
242
|
+
Button("-", onclick="App.dispatch('Counter', 'dec')"),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def on_inc(self, state, payload):
|
|
246
|
+
state.count += 1
|
|
247
|
+
|
|
248
|
+
def on_dec(self, state, payload):
|
|
249
|
+
state.count -= 1
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Multi-Page SPA
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from xania import SpaApp, StaticPage, TemplatePage
|
|
256
|
+
|
|
257
|
+
app = SpaApp(name="MyBlog")
|
|
258
|
+
|
|
259
|
+
# Home page
|
|
260
|
+
app.route("/", StaticPage(
|
|
261
|
+
title="Home",
|
|
262
|
+
html="<h1>Welcome to My Blog</h1>"
|
|
263
|
+
))
|
|
264
|
+
|
|
265
|
+
# About page
|
|
266
|
+
app.route("/about", StaticPage(
|
|
267
|
+
title="About",
|
|
268
|
+
html="<h1>About Me</h1><p>I build with Xania.</p>"
|
|
269
|
+
))
|
|
270
|
+
|
|
271
|
+
# Post page with placeholders
|
|
272
|
+
app.route("/post/{id}", TemplatePage(
|
|
273
|
+
title="Post",
|
|
274
|
+
template="<h1>{title}</h1><p>{content}</p>",
|
|
275
|
+
placeholders={"title": "My First Post", "content": "..."}
|
|
276
|
+
))
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Deployment
|
|
282
|
+
|
|
283
|
+
### Option 1: Static Hosting (Simplest)
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# Compile
|
|
287
|
+
python app.py
|
|
288
|
+
|
|
289
|
+
# Upload everything to your host
|
|
290
|
+
# - Netlify (drag & drop)
|
|
291
|
+
# - Vercel (git integration)
|
|
292
|
+
# - GitHub Pages
|
|
293
|
+
# - S3 + CloudFront
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Option 2: FastAPI Server
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
# serve.py
|
|
300
|
+
from fastapi import FastAPI
|
|
301
|
+
from xania import mount_spa
|
|
302
|
+
|
|
303
|
+
app = FastAPI()
|
|
304
|
+
|
|
305
|
+
@app.get("/api/hello")
|
|
306
|
+
def hello():
|
|
307
|
+
return {"message": "Hello from API!"}
|
|
308
|
+
|
|
309
|
+
mount_spa(app, ".")
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
# Local: uvicorn serve:app --reload
|
|
314
|
+
# Production: gunicorn serve:app (on Heroku, Railway, etc.)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Development
|
|
320
|
+
|
|
321
|
+
Clone and install:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
git clone https://github.com/xania/framework.git
|
|
325
|
+
cd xania
|
|
326
|
+
python -m venv .venv
|
|
327
|
+
source .venv/bin/activate
|
|
328
|
+
pip install -e ".[dev]"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Run examples:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
xania serve documentation # Full tutorial
|
|
335
|
+
xania dev # Dev server
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
PRs welcome! See [GitHub](https://github.com/xania/framework)
|
|
343
|
+
- DOM events calling `App.dispatch('Counter', 'increment')`
|
|
344
|
+
|
|
345
|
+
## Tags / Elements (VDOM)
|
|
346
|
+
|
|
347
|
+
Xania’s “tags” are Python functions/classes that build a VDOM tree in `renderer/elements.py`.
|
|
348
|
+
|
|
349
|
+
- **Use built-in tag helpers**: `Div(...)`, `Span(...)`, `Button(...)`, `H1(...)`, etc.
|
|
350
|
+
- **Use `Element(tag, ...)` for any HTML tag** (even if there is no helper yet):
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from renderer.elements import Element
|
|
354
|
+
|
|
355
|
+
node = Element("section", "Hello")
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Children
|
|
359
|
+
- **Text children**: strings (they are HTML-escaped by `engine/serializer.py`)
|
|
360
|
+
- **Element children**: nested `Element` instances
|
|
361
|
+
- **Lists**: spread them with `*items` when building children
|
|
362
|
+
|
|
363
|
+
### Attributes (“props”)
|
|
364
|
+
Attributes are passed as keyword args:
|
|
365
|
+
|
|
366
|
+
- **`class_name` → `class`**
|
|
367
|
+
- **`for_` → `for`**
|
|
368
|
+
- **`http_equiv` → `http-equiv`**
|
|
369
|
+
- **Any `_` becomes `-`** (e.g. `data_component="Counter"` → `data-component="Counter"`)
|
|
370
|
+
- **Booleans**:
|
|
371
|
+
- `disabled=True` renders `disabled`
|
|
372
|
+
- `disabled=False` omits it
|
|
373
|
+
|
|
374
|
+
Example:
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from renderer.elements import Button
|
|
378
|
+
|
|
379
|
+
Button(
|
|
380
|
+
"Click",
|
|
381
|
+
class_name="px-3 py-2 rounded",
|
|
382
|
+
data_component="Counter",
|
|
383
|
+
disabled=False,
|
|
384
|
+
)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Registering components
|
|
388
|
+
|
|
389
|
+
In `app.py`:
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from renderer.registry import ComponentRegistry
|
|
393
|
+
from example.counter import Counter
|
|
394
|
+
|
|
395
|
+
ComponentRegistry.register("Counter", Counter(id="counter"))
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The string `"Counter"` must match what the client dispatches:
|
|
399
|
+
|
|
400
|
+
```html
|
|
401
|
+
onclick="App.dispatch('Counter','increment')"
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Web API
|
|
405
|
+
|
|
406
|
+
### `GET /`
|
|
407
|
+
Returns the HTML shell (mount points + `<script src="/static/runtime.js">`).
|
|
408
|
+
|
|
409
|
+
### `POST /event`
|
|
410
|
+
Request (`web/schemas.py`):
|
|
411
|
+
|
|
412
|
+
```json
|
|
413
|
+
{
|
|
414
|
+
"component": "Counter",
|
|
415
|
+
"action": "increment",
|
|
416
|
+
"payload": {}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Response:
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{
|
|
424
|
+
"updates": [
|
|
425
|
+
{ "id": "counter", "html": "<div>...</div>" }
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The client applies updates by doing `document.getElementById(id).innerHTML = html`.
|
|
431
|
+
|
|
432
|
+
## Publish readiness (honest checklist)
|
|
433
|
+
|
|
434
|
+
This codebase is **a solid architectural baseline**, but it is **not ready to publish as a production framework** yet. Here’s what’s missing / risky:
|
|
435
|
+
|
|
436
|
+
- **Session/user isolation**: `ComponentRegistry` stores **singletons** in-process.
|
|
437
|
+
- In production you need per-user (cookie/session) component instances or a state store.
|
|
438
|
+
- **Concurrency & scaling**:
|
|
439
|
+
- Multiple workers/processes will not share component state.
|
|
440
|
+
- Need a storage layer (Redis/DB) or stateless model (client-owned state + patches).
|
|
441
|
+
- **Security**:
|
|
442
|
+
- `onclick="App.dispatch(...)"` is safe here because HTML is generated server-side, but any user-provided text/attrs must be carefully validated.
|
|
443
|
+
- Consider CSRF protection for `/event`.
|
|
444
|
+
- Demo auth exists for stress-testing, but production needs strong secrets + HTTPS-only cookies + real user storage.
|
|
445
|
+
- **Performance**:
|
|
446
|
+
- Current updates replace `innerHTML` of the whole component.
|
|
447
|
+
- Next step: **diffing + patching** (you already have a base architecture for it).
|
|
448
|
+
- **Testing**:
|
|
449
|
+
- No unit tests or integration tests yet.
|
|
450
|
+
- **Packaging polish**:
|
|
451
|
+
- Ensure `pyproject.toml` metadata is correct (name/urls/authors).
|
|
452
|
+
- Add a changelog and decide versioning strategy.
|
|
453
|
+
|
|
454
|
+
## Roadmap (recommended next steps)
|
|
455
|
+
|
|
456
|
+
- Add per-session registry/state store (cookie -> component state)
|
|
457
|
+
- Add diff/patch engine (update minimal DOM, not full `innerHTML`)
|
|
458
|
+
- Add router + multipage CSR navigation
|
|
459
|
+
- Add test suite + CI
|
|
460
|
+
- Add docs site and examples gallery
|
|
461
|
+
|
|
462
|
+
## Publish checklist (minimal)
|
|
463
|
+
|
|
464
|
+
- **Package identity**: confirm `pyproject.toml` name/version/description are correct.
|
|
465
|
+
- **License**: ensure `LICENSE` exists and matches `pyproject.toml` (`MIT` here).
|
|
466
|
+
- **Versioning**: adopt SemVer (`0.x` while APIs are changing quickly).
|
|
467
|
+
- **README**: keep Quick start + minimal example runnable.
|
|
468
|
+
- **Security**: decide on CSRF/auth strategy for `/event` before public deploys.
|
|
469
|
+
- **State model**: decide per-user state persistence (sessions/Redis) before scaling.
|
|
470
|
+
- **CI**: add at least `python -m py_compile` + basic tests on every push.
|
|
471
|
+
|
|
472
|
+
## Demo auth (stress testing only)
|
|
473
|
+
|
|
474
|
+
This repo includes a minimal cookie-session auth demo used by the SPA stress page:
|
|
475
|
+
|
|
476
|
+
- Login endpoint: `POST /api/auth/login` (demo user: `admin/admin`)
|
|
477
|
+
- Logout endpoint: `POST /api/auth/logout`
|
|
478
|
+
- Current user: `GET /api/auth/me`
|
|
479
|
+
- Protected stress endpoints: `GET /api/private/big-json`, `POST /api/private/write-echo`
|
|
480
|
+
|
|
481
|
+
Environment variables:
|
|
482
|
+
|
|
483
|
+
- `XANIA_ENV=dev|prod` (default `dev`)
|
|
484
|
+
- `XANIA_SECRET_KEY=...` (required when `XANIA_ENV` is not dev)
|
|
485
|
+
- `XANIA_COOKIE_SECURE=true|false` (defaults to true outside dev)
|