Ryth Azhur d7ec13c43c Add MWT (Mizan Web Token) — protocol-owned identity layer
MWT is a standard JWT with Mizan-specific claims on X-Mizan-Token header:
- sub: user_id for HMAC cache key derivation
- pkey: deterministic hash of user's permission state (staff + superuser + perms)
- kid: key ID for future secret rotation
- aud: audience binding for cross-tenant protection

Executor checks X-Mizan-Token first, falls back to Authorization: Bearer
for legacy JWT compat. Invalid tokens return 401 (no session fallback).

New: mizan/mwt.py (create_mwt, decode_mwt, MWTUser, compute_permission_key)
New: mwt_obtain server function for session-to-MWT issuance
New: MIZAN_MWT_TTL setting (default 300s = 5 min permission staleness window)
11 new tests covering creation, decode, pkey determinism, auth integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:41:18 -04:00

mizan

Django + React server functions framework. RPC, not REST.

You define Python functions. mizan generates typed React hooks. No API routes, no serializers, no endpoint boilerplate.

# Django
@client(context='global')
def current_user(request) -> UserOutput:
    return UserOutput(email=request.user.email)
// React (generated)
const user = useCurrentUser()  // typed, SSR-hydrated, auto-refreshed

Packages

Package Path Install
mizan (Python) django/ uv add "mizan[channels] @ git+..."
@rythazhur/mizan (TypeScript) react/ npm install @rythazhur/mizan@git+...

Quick Start

1. Django setup

# settings.py
INSTALLED_APPS = [
    "mizan",
    "myapp",
]

# urls.py
from django.urls import include, path
urlpatterns = [
    path("api/mizan/", include("mizan.urls")),
]

# asgi.py (for WebSocket support)
from mizan import wrap_asgi
from django.core.asgi import get_asgi_application
application = wrap_asgi(get_asgi_application())

2. Define server functions

# myapp/mizan_clients.py
from django.http import HttpRequest
from mizan.client import client
from mizan.setup.registry import register
from pydantic import BaseModel

class EchoOutput(BaseModel):
    message: str

@client
def echo(request: HttpRequest, text: str) -> EchoOutput:
    return EchoOutput(message=text)

register(echo, "echo")

3. Register in apps.py

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        import myapp.mizan_clients  # noqa: F401

4. Generate TypeScript

# django.config.mjs
export default {
    source: {
        django: {
            managePath: '../backend/manage.py',
            command: ['uv', 'run', 'python'],
        },
    },
    output: 'src/api/generated.ts',
}
npx mizan-generate

This produces typed hooks, a typed provider, form hooks with Zod validation, and channel hooks.

5. Use in React

// layout.tsx
import { DjangoContext } from '@/api'

export default function Layout({ children }) {
    return <DjangoContext>{children}</DjangoContext>
}
// page.tsx
import { useEcho, useCurrentUser, DjangoError } from '@/api'

function MyComponent() {
    const user = useCurrentUser()
    const echo = useEcho()

    const handleClick = async () => {
        try {
            const result = await echo({ text: 'hello' })
            console.log(result.message) // typed
        } catch (e) {
            if (e instanceof DjangoError) {
                console.log(e.code) // NOT_FOUND, VALIDATION_ERROR, etc.
            }
        }
    }
}

Features

Backend Frontend (generated) Transport
@client useXxx() HTTP
@client(context='global') useXxx() + SSR hydration HTTP
@client(context='local') useXxx() with params HTTP
@client(websocket=True) useXxx() WebSocket RPC
@client(auth=True|'staff'|callable) Auth errors as DjangoError HTTP
mizanFormMixin useXxxForm() + Zod validation HTTP
ReactChannel useXxxChannel() WebSocket
@compose(...) Combined providers varies

Architecture

React app
  └─ <DjangoContext>           ← generated provider (includes ChannelProvider)
       ├─ useCurrentUser()     ← generated context hook (SSR-hydrated)
       ├─ useEcho()            ← generated function hook
       ├─ useContactForm()     ← generated form hook (Zod + server validation)
       └─ useChatChannel()     ← generated channel hook (WebSocket)
            │
            ├─ HTTP: POST /api/mizan/call/  { fn: "echo", args: { text: "hi" } }
            └─ WS:   { action: "rpc", fn: "echo", args: { text: "hi" } }
                     │
                     Django executor
                       ├─ Pydantic input validation
                       ├─ Auth check (session, JWT, or custom)
                       ├─ Function execution
                       └─ Pydantic output serialization

The generated DjangoContext is the only provider needed. It wraps mizanProvider + ChannelProvider and handles session init, CSRF, context auto-fetching, and WebSocket connection.

Code Generation

npx mizan-generate reads Django schemas (no running server needed) and produces:

File Contents
generated.mizan.ts Pydantic model types (via openapi-typescript)
generated.django.tsx DjangoContext provider + all typed hooks
generated.django.server.ts SSR hydration helper (getDjangoHydration)
generated.forms.ts Form hooks with Zod schemas (useContactForm, etc.)
generated.channels.ts Channel message types
generated.channels.hooks.tsx Channel hooks (useChatChannel, etc.)
index.ts Consolidated re-exports

Error Handling

All errors from server functions are thrown as DjangoError:

try {
    await echo({ text: 'hello' })
} catch (e) {
    if (e instanceof DjangoError) {
        e.code        // 'NOT_FOUND' | 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'FORBIDDEN' | ...
        e.message     // Human-readable message
        e.details     // Field-level validation errors, etc.
        e.isAuthError()
        e.isValidationError()
        e.getFieldErrors('email')
    }
}

Error codes: NOT_FOUND, VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, BAD_REQUEST, INTERNAL_ERROR, NOT_IMPLEMENTED.

Forms

Django forms get typed React hooks with client-side Zod validation:

# Django
class ContactForm(mizanFormMixin, forms.Form):
    mizan = mizanFormMeta(
        name="contact",
        title="Contact Us",
        submit_label="Send",
        live_validation=True,
    )
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    def on_submit_success(self, request):
        send_email(self.cleaned_data)
        return {"sent": True}
// React (generated)
const form = useContactForm()

form.schema          // { fields: { name: {...}, email: {...} }, title, submit_label }
form.data            // { name: '', email: '', message: '' }
form.set('email', v) // typed setter
form.errors          // field-level errors (Zod + server)
form.submit()        // → { success: true, data: { sent: true } }

Channels

WebSocket channels with typed messages:

# Django
class ChatChannel(ReactChannel):
    class Params(BaseModel):
        room: str
    class ReactMessage(BaseModel):
        text: str
    class DjangoMessage(BaseModel):
        text: str
        user: str

    def authorize(self, params):
        return self.user.is_authenticated

    def group(self, params):
        return f"chat_{params.room}"

    def receive(self, params, msg):
        return self.DjangoMessage(text=msg.text, user=self.user.email)
// React (generated)
const chat = useChatChannel({ room: 'general' })

chat.status    // 'connecting' | 'connected' | 'disconnected'
chat.messages  // ChatDjangoMessage[]
chat.send({ text: 'hello' })

Testing

# Django unit tests
cd packages/mizan-django && uv sync --extra dev --extra channels && uv run pytest

# React unit tests
cd packages/mizan-react && npm test

# E2E integration tests (real browser, real backend)
docker compose -f examples/django-react-site/docker-compose.test.yml up -d
cd examples/django-react-site/harness && npm install && npx mizan-generate && npx vite --port 5174 &
npx playwright test

# All at once
make test-all

Project Structure

mizan/
  packages/
    mizan-runtime/     Client state engine (~150 lines, framework-agnostic)
    mizan-django/      Django server adapter (decorators, dispatch, contexts, SSR)
    mizan-react/       React adapter (thin wrapper around runtime)
  examples/
    django-react-site/         E2E tests + Django backend
    django-react-desktop-app/  PyWebView desktop app
Description
No description provided
Readme 4.1 MiB
Languages
Python 45.2%
TypeScript 32.2%
Rust 20.8%
Jinja 1.3%
JavaScript 0.3%
Other 0.2%