Cleaned dead code and updated documents
This commit is contained in:
@@ -9,18 +9,31 @@ Tree organized by role.
|
||||
|
||||
```
|
||||
backends/ server protocol adapters
|
||||
mizan-django/ Django adapter
|
||||
mizan-fastapi/ FastAPI adapter (AFI-common scope)
|
||||
mizan-ts/ TypeScript adapter (proves the protocol is language-agnostic)
|
||||
frontends/ client kernel + per-framework adapters
|
||||
mizan-base/ framework-agnostic kernel; owns data, status, error; adapters subscribe
|
||||
mizan-react/ React contexts + hooks over the kernel
|
||||
mizan-vue/ Vue composables over the kernel
|
||||
mizan-svelte/ Svelte stores/runes over the kernel
|
||||
mizan-django/ Django adapter
|
||||
mizan-fastapi/ FastAPI adapter (AFI-common scope)
|
||||
mizan-rust-axum/ Rust/Axum adapter (handlers, errors, IR export)
|
||||
mizan-tauri/ Tauri adapter — Mizan calls served in-process
|
||||
mizan-ts/ TypeScript adapter (proves the protocol is language-agnostic)
|
||||
frontends/ client kernel + per-framework adapters + transports
|
||||
mizan-base/ framework-agnostic kernel (@mizan/base); owns data, status,
|
||||
error; adapters subscribe through the MizanTransport interface
|
||||
mizan-react/ React contexts + hooks over the kernel
|
||||
mizan-vue/ Vue composables over the kernel
|
||||
mizan-svelte/ Svelte stores/runes over the kernel
|
||||
mizan-rust/ Rust client kernel
|
||||
mizan-tauri-transport/ MizanTransport over Tauri IPC
|
||||
mizan-webview-transport/ MizanTransport over a webview message channel
|
||||
mizan-webview-channels/ channel transport over a webview bridge
|
||||
cores/ shared language-level primitives
|
||||
mizan-python/ @client decorator, registry, MWT, HMAC cache keys
|
||||
mizan-python/ @client decorator, registry, MWT, HMAC cache keys
|
||||
mizan-rust/ Rust core — IR build (build_ir()), registry
|
||||
mizan-rust-macros/ #[derive(Mizan)] / #[mizan::client] proc-macros
|
||||
protocol/ protocol-level tooling
|
||||
mizan-generate/ codegen — schema in, typed client out
|
||||
mizan-codegen/ codegen — Rust binary (crate `mizan-codegen`); reads KDL IR,
|
||||
emits typed clients. Targets: stage1, react, vue, svelte,
|
||||
channels, python, rust. Askama templates under templates/.
|
||||
mizan-generate/ thin npm-package launcher (bin/launcher.mjs) dispatching to
|
||||
the compiled mizan-codegen binary per platform
|
||||
workers/ runtime workers / bridges
|
||||
mizan-ssr/ Bun subprocess used by the Django template backend
|
||||
```
|
||||
@@ -35,11 +48,16 @@ compose.
|
||||
|
||||
## Kernel model
|
||||
|
||||
The client kernel (`mizan-base`) is the one hard thing. Per-
|
||||
framework adapters are thin idiomatic wrappers around it. Codegen
|
||||
emits typed bindings against the framework adapter's surface, not
|
||||
against the raw kernel — so a React developer gets `useEcho()` and
|
||||
`<MizanContext>`, a Vue developer gets `useEcho()` composables, a
|
||||
The client kernel (`@mizan/base`) is the one hard thing. It owns
|
||||
`ContextState<T> = {data, status, error}`, the context registry
|
||||
(`registerContext`), `mizanCall` / `mizanFetch`, server-driven `merge`
|
||||
and `invalidate`, and `initSession`. It reaches the backend through a
|
||||
pluggable `MizanTransport` (`call` / `fetch`); the default is the
|
||||
HTTP `httpTransport()`, swapped via `configure({ transport })` for
|
||||
Tauri / webview hosts. Per-framework adapters are thin idiomatic
|
||||
wrappers that subscribe to the kernel. Codegen emits typed bindings
|
||||
against the framework adapter's surface — a React developer gets
|
||||
`useEcho()` hooks, a Vue developer gets `useEcho()` composables, a
|
||||
Svelte developer gets readable stores. Same kernel underneath.
|
||||
|
||||
## KDL is the IR
|
||||
@@ -56,20 +74,23 @@ divergence between adapters is what the IR exists to prevent.
|
||||
|
||||
Forward-direction primitives:
|
||||
|
||||
- `cores/mizan-python` builds the IR from registered functions
|
||||
(`build_ir()` walks `mizan_core.registry`, emits KDL)
|
||||
- A `mizan-schema` package (forthcoming) holds the canonical KDL
|
||||
grammar / type system definition that every adapter targets
|
||||
- Each backend adapter emits KDL on stdout from an IR-export command:
|
||||
FastAPI `python -m mizan_fastapi.ir <module>`, Django
|
||||
`python manage.py export_mizan_ir`, Rust a consumer-side cargo bin
|
||||
that calls `mizan_core::build_ir()`. Python's `build_ir()` walks
|
||||
`mizan_core.registry`. The IR grammar (`type` / `function` /
|
||||
`context` / `channel` nodes) is parsed by `mizan-codegen`'s
|
||||
`src/ir.rs`; fixtures live at
|
||||
`protocol/mizan-codegen/tests/fixtures/*.kdl`.
|
||||
- `protocol/mizan-codegen/src/fetch.rs` spawns the configured source
|
||||
command and parses the KDL it writes.
|
||||
- Codegen reads KDL directly — no OpenAPI envelope, no
|
||||
`openapi-typescript`, no per-backend converter divergence
|
||||
- Edge manifest, MWT claims, and other protocol artifacts all derive
|
||||
from the same KDL
|
||||
|
||||
**Current implementation is transitional.** Today the codegen consumes
|
||||
OpenAPI 3.0 (`x-mizan-functions` + `x-mizan-contexts` extensions over
|
||||
Pydantic→JSON-Schema), produced via Django Ninja or FastAPI's native
|
||||
generator. That layered indirection is what introduces adapter
|
||||
divergence (see the AFI conformance suite). KDL-as-IR collapses it.
|
||||
`openapi-typescript`, no per-backend converter divergence. The
|
||||
former JavaScript/Node two-stage codegen (`openapi-typescript` plus
|
||||
`.mjs` adapters) has been deleted; codegen is now the single Rust
|
||||
binary.
|
||||
- Edge manifest, MWT claims, and other protocol artifacts derive from
|
||||
the same registry/IR.
|
||||
|
||||
## Launch surface
|
||||
|
||||
|
||||
@@ -16,14 +16,22 @@ standardized replacement exists.
|
||||
## Resolution: HMAC cache key (JSON-canonical form)
|
||||
|
||||
```
|
||||
HMAC-SHA256(secret, JSON.stringify({
|
||||
ctx:{context}:HMAC-SHA256(secret, json.dumps({
|
||||
"c": context,
|
||||
"p": sorted_params,
|
||||
"p": sorted_params, // values normalized to JSON-native strings
|
||||
"r": rev,
|
||||
"u": user_id // omitted for public content
|
||||
}, sort_keys=True))
|
||||
"u": user_id // omitted for public content
|
||||
}, sort_keys=True, separators=(",", ":")))
|
||||
```
|
||||
|
||||
`derive_cache_key(secret, context, params, user_id=None, rev=0)` →
|
||||
`"ctx:{context}:{hmac_hex}"`. The `ctx:{context}:` prefix lets broad
|
||||
purge SCAN by prefix. Param values are normalized for cross-language
|
||||
consistency (`True`→`"true"`, `None`→`"null"`) before stringification.
|
||||
Implemented in `cores/mizan-python/src/mizan_core/cache/keys.py` and
|
||||
`backends/mizan-ts/src/cache/keys.ts` (`deriveCacheKey`); pin tests
|
||||
verify identical output.
|
||||
|
||||
### Key derivation rules
|
||||
|
||||
- **Public content** — URL path + query params (standard CDN).
|
||||
@@ -45,20 +53,24 @@ Mizan claims on `X-Mizan-Token` header. Replaces the old
|
||||
**Not a compiled binary ABI. Not a pluggable Python protocol.**
|
||||
|
||||
Each backend adapter (Python, TypeScript, future PHP/C#/Go)
|
||||
implements the cache protocol in its own language, backed by Redis.
|
||||
implements the cache protocol in its own language.
|
||||
**Conformance verified by a shared test suite.**
|
||||
|
||||
### Required operations
|
||||
|
||||
- `cache_get`
|
||||
- `cache_put`
|
||||
- `cache_purge`
|
||||
- `cache_purge_user`
|
||||
- `cache_purge` (scoped recomputes the key; broad SCANs the
|
||||
`ctx:{context}:*` prefix)
|
||||
|
||||
### Storage
|
||||
|
||||
Redis only. Handles persistence, cross-worker sharing, crash
|
||||
recovery.
|
||||
Two backends behind a `CacheBackend` protocol
|
||||
(`mizan_core/cache/backend.py`):
|
||||
|
||||
- `MemoryCache` — dict-based, for testing.
|
||||
- `RedisCache` — production; persistence, cross-worker sharing, crash
|
||||
recovery. Broad purge via SCAN, delete via UNLINK.
|
||||
|
||||
## Deploy invalidation
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@ detection.
|
||||
| `pkey` | Deterministic hash of user's permission state at issuance |
|
||||
| `exp` | Configurable short TTL — controls permission staleness window (Django setting) |
|
||||
| `iat` | Issued at |
|
||||
| `kid` | Key ID — for secret rotation |
|
||||
| `kid` | Key ID — for secret rotation. Carried in the JOSE header (RFC 7515), not the payload |
|
||||
| `aud` | Audience binding — prevents cross-tenant replay |
|
||||
| `nbf` | Not-before — tolerates clock skew |
|
||||
| `staff` / `super` | `is_staff` / `is_superuser`, used to build `MWTUser` without a DB query |
|
||||
|
||||
## Key decisions
|
||||
|
||||
@@ -25,10 +27,14 @@ detection.
|
||||
- **`X-Mizan-Token` header, not `Authorization: Bearer`.** Avoids
|
||||
collision with DRF, allauth, and existing JWT systems. Cloudflare
|
||||
WAF/Access do not inspect custom headers.
|
||||
- **Replaces `JWTUser` + `_try_jwt_auth` entirely.** Old approach is
|
||||
deleted.
|
||||
- **`MWTUser`** is a minimal, DB-free request user built from the
|
||||
token claims (`cores/mizan-python/src/mizan_core/mwt.py`).
|
||||
> A separate JWT module (`mizan/jwt/`) still exists for standard
|
||||
> user-auth access/refresh tokens; MWT is the cache-keying identity
|
||||
> layer, not a replacement for that module.
|
||||
- **App handles authentication** (session, social, etc.). Mizan
|
||||
issues MWT *from* the authenticated identity.
|
||||
issues MWT *from* the authenticated identity
|
||||
(`create_mwt(user, secret, ttl, audience, kid)`).
|
||||
- **Edge Worker** validates MWT, extracts `sub` for HMAC cache key,
|
||||
checks `exp`.
|
||||
- **`pkey` computation must be deterministic:**
|
||||
@@ -43,9 +49,11 @@ detection.
|
||||
JSON with sorted keys:
|
||||
|
||||
```
|
||||
HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "u": user_id}))
|
||||
ctx:{context}:HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "r": rev, "u": user_id}))
|
||||
```
|
||||
|
||||
See [CACHE_KEYING.md](CACHE_KEYING.md) for the full derivation.
|
||||
|
||||
## What this solves
|
||||
|
||||
- DRF token collision
|
||||
@@ -55,5 +63,8 @@ HMAC(secret, JSON.stringify({"c": context, "p": sorted_params, "u": user_id}))
|
||||
|
||||
## Usage rule
|
||||
|
||||
All cache-layer auth code uses MWT, not Django session or raw JWT.
|
||||
The `@client(auth=...)` parameter gates on MWT validity.
|
||||
MWT is the identity Edge/cache layers key on. The `@client(auth=...)`
|
||||
parameter is enforced server-side in `mizan/client/executor.py`
|
||||
(`_check_auth_requirement`), which checks `request.user` against the
|
||||
auth requirement (`required` / `staff` / `superuser` / callable);
|
||||
`request.user` may be an `MWTUser` (stateless) or a session user.
|
||||
|
||||
@@ -22,17 +22,16 @@ multi-state privacy. ~$5–8K legal costs.
|
||||
TS "Deploy" exists via Workers for Platforms at no additional
|
||||
compliance cost.
|
||||
|
||||
## Free framework: mizan-cache (origin-side cache)
|
||||
## Free framework: origin-side cache (`mizan.cache`)
|
||||
|
||||
Python package implementing the **full cache protocol locally** —
|
||||
same HMAC key derivation, metadata schema, and purge semantics as
|
||||
Edge.
|
||||
Shipped in `mizan_core.cache` (re-exported as `mizan.cache` from the
|
||||
Django adapter) implementing the **full cache protocol locally** —
|
||||
same HMAC key derivation and purge semantics as Edge.
|
||||
|
||||
Three backends:
|
||||
Two backends behind a `CacheBackend` protocol:
|
||||
|
||||
- In-memory dict (default)
|
||||
- Redis
|
||||
- SQLite
|
||||
- `MemoryCache` — in-memory dict (testing)
|
||||
- `RedisCache` — production
|
||||
|
||||
### Dual purpose
|
||||
|
||||
@@ -44,8 +43,8 @@ Three backends:
|
||||
## Spec additions
|
||||
|
||||
- `@client(cache=False)` — uncacheable; emits `Cache-Control: no-store`.
|
||||
- Cache ABI: `get(key)`, `put(key, response, metadata)`,
|
||||
`purge(context, params)`.
|
||||
- Cache ABI (`mizan.cache`): `cache_get(secret, backend, context, params)`,
|
||||
`cache_put(...)`, `cache_purge(backend, context, params=…, secret=…)`.
|
||||
|
||||
## Launch compliance (Render only)
|
||||
|
||||
|
||||
@@ -14,6 +14,15 @@ Works on a $5 VPS with local Bun. **No Edge required.** PSR is part
|
||||
of the protocol; it's available to every Mizan deployment regardless
|
||||
of hosting.
|
||||
|
||||
> Current state: the Edge manifest records each context's
|
||||
> `render_strategy` (`"psr"` for public, `"dynamic_cached"` for
|
||||
> user-scoped) — see `mizan/export/` and the `export_edge_manifest`
|
||||
> management command — and the SSR bridge can render a component to
|
||||
> HTML. The render-on-mutation orchestration that wires those together
|
||||
> (mutation → trigger local render → store HTML) is not yet present in
|
||||
> the open-source backends; it is the manifest-driven behavior the
|
||||
> Edge layer consumes.
|
||||
|
||||
## Edge Delivery — Mizan Render (Paid Product)
|
||||
|
||||
Pre-rendered HTML cached globally on Cloudflare CDN.
|
||||
|
||||
@@ -10,23 +10,31 @@ rendering engine.
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'mizan.ssr.MizanTemplates',
|
||||
...
|
||||
'DIRS': [BASE_DIR / 'frontend'],
|
||||
'OPTIONS': {
|
||||
'worker': 'path/to/mizan-ssr/src/worker.tsx',
|
||||
'timeout': 5,
|
||||
},
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Then `render(request, 'ProfilePage', context)` calls the Bun
|
||||
Then `render(request, 'components/Hello.tsx', context)` calls the Bun
|
||||
subprocess bridge instead of rendering a Django/Jinja2 template.
|
||||
**The component name IS the template name.**
|
||||
**The template name IS a `.tsx`/`.jsx` file path**, resolved against
|
||||
`DIRS`; `get_template` returns a `MizanTemplate` wrapping the absolute
|
||||
file path. The context dict becomes the component's props (`request`
|
||||
and `csrf_token` stripped). Rendered output is wrapped in
|
||||
`<div id="mizan-root">…</div>` plus a
|
||||
`<script>window.__MIZAN_SSR_DATA__=…</script>` hydration payload.
|
||||
|
||||
## AFI boundary
|
||||
|
||||
| Side | Responsibility |
|
||||
|---|---|
|
||||
| Backend adapter | Implements `mizan.ssr()` — executes context functions, gathers data |
|
||||
| Frontend adapter | Implements `renderToHTML()` — takes component + props, produces HTML |
|
||||
| Bun subprocess | Hosts the frontend adapter |
|
||||
| stdin/stdout JSON-RPC | Transport between the two |
|
||||
| Backend adapter (`SSRBridge`) | Manages the Bun subprocess lifecycle; gathers props |
|
||||
| Bun worker (`worker.tsx`) | `import()`s the file path, `renderToString(createElement(Component, props))` |
|
||||
| stdin/stdout JSON-RPC | Newline-delimited; `{id, method:"render", params:{file, props}}` → `{id, html}` / `{id, error}`; `ping` → `{id, pong:true}` |
|
||||
|
||||
## Why template backend
|
||||
|
||||
@@ -35,16 +43,22 @@ subprocess bridge instead of rendering a Django/Jinja2 template.
|
||||
- Django developers already use `render(request, template, context)`
|
||||
— no new API to learn.
|
||||
- URL routing, views, middleware, auth — all unchanged.
|
||||
- The template tag `{% mizan_render %}` is a convenience for
|
||||
developers who *also* use Django templates (e.g., a base.html shell
|
||||
with Mizan components inside).
|
||||
|
||||
> A `templatetags/` package exists for a future `{% mizan_render %}`
|
||||
> convenience tag (base.html shell with Mizan components inside), but
|
||||
> it is currently empty — no tag is implemented yet.
|
||||
|
||||
## Implementation surface
|
||||
|
||||
The SSR bridge module implements Django's template backend interface:
|
||||
The SSR backend (`mizan/ssr/backend.py`) implements Django's template
|
||||
backend interface:
|
||||
|
||||
- `BaseEngine` subclass
|
||||
- `Template` class with `.render(context, request)`
|
||||
- `MizanTemplates(BaseEngine)` — requires `OPTIONS['worker']` (path to
|
||||
`worker.tsx`); `get_template(name)` resolves a file under `DIRS`
|
||||
- `MizanTemplate` with `.render(context, request)` → calls the bridge
|
||||
- `SSRBridge` (`bridge.py`) — spawns `bun run <worker>`, holds the
|
||||
persistent subprocess, correlates requests by message id, thread-safe,
|
||||
auto-restarts on crash, waits for the worker's ready signal
|
||||
|
||||
Everything Django expects from a template backend, but the actual
|
||||
rendering routes to Bun.
|
||||
|
||||
Reference in New Issue
Block a user