Cleaned dead code and updated documents

This commit is contained in:
2026-06-04 02:42:13 -04:00
parent 578e124d67
commit ffdf9aa24d
31 changed files with 374 additions and 498 deletions

View File

@@ -144,40 +144,33 @@ Frontend gets `useChatChannel({ room })`.
## Generate the frontend
The codegen is `mizan-generate` (in `protocol/mizan-generate/`). From your
frontend project, point a config at the Django backend and run the CLI:
The codegen is the `mizan-generate` Rust binary (source at
`protocol/mizan-codegen/`; `protocol/mizan-generate/` is a thin npm
launcher that dispatches to the platform binary). From your frontend
project, point a `mizan.toml` at the Django backend and run the CLI:
```js
// frontend/django.config.mjs
import path from "path"
import { fileURLToPath } from "url"
```toml
# frontend/mizan.toml
output = "src/api"
targets = ["react"]
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, "..")
[source.django]
manage_path = "../backend/manage.py"
command = ["uv", "run", "python"] # optional — defaults to ["python"]
export default {
source: {
django: {
managePath: path.join(root, "backend/manage.py"),
command: ["uv", "run", "python"],
env: {
PYTHONPATH: path.join(root, "backend"),
DJANGO_SETTINGS_MODULE: "myproject.settings",
},
},
},
output: "src/api",
}
[source.django.env]
PYTHONPATH = "../backend"
DJANGO_SETTINGS_MODULE = "myproject.settings"
```
```bash
npx mizan-generate --config django.config.mjs
mizan-generate --config mizan.toml
```
The codegen drives Django's management command (`export_mizan_schema`) under
the hood, then emits Stage 1 (typed `callXxx`/`fetchXxx` over the runtime
kernel) + Stage 2 (`<MizanContext>` provider, per-context providers,
`use{Hook}()` hooks) into `src/api/`.
The codegen drives Django's management command (`export_mizan_ir`) under
the hood, parses the emitted KDL IR, then emits Stage 1 (typed
`callXxx`/`fetchXxx` over the runtime kernel) + Stage 2 (`<MizanContext>`
provider, per-context providers, `use{Hook}()` hooks) into `src/api/`.
```tsx
// app.tsx

View File

@@ -1,65 +1,40 @@
# Cache Module — Known Issues
Issues identified by 8-domain-expert review. Status tracked here.
Open issues against the current cache implementation. Resolved items are
removed once their fix lands.
## Critical (Security / Data Corruption)
## Correctness
### 1. ~~User-scoped content cached without user_id~~ FIXED
`context_fetch_view` now extracts `user_id` from `request.user.pk` and
passes it to `cache_get`/`cache_put`.
### Purge race condition (non-atomic index operations)
`cache_purge` reads the index and deletes as separate operations. A
concurrent `cache_put` between the two steps can orphan entries. Mitigated
by AND-intersection purge semantics, but full atomicity (Lua script or
`WATCH`/`MULTI` on the Redis backend) is still owed.
### 2. Purge race condition (non-atomic index operations)
`cache_purge` does index reads and deletes as separate operations.
Concurrent `cache_put` between steps can orphan entries.
**Status:** Partially mitigated by AND semantics fix. Full atomicity
(Lua script or WATCH/MULTI) still needed for Redis backend.
### Cross-language stringification divergence
Python `str(True)``"True"` vs JS `String(true)``"true"`. `_normalize`
canonicalizes `True`/`False`/`None` today, but the rules for the remaining
value types are not yet pinned in the protocol spec — so Python and
TypeScript HMAC keys can still diverge on an un-normalized type.
### 3. ~~No Redis error handling~~ FIXED
All cache operations in `executor.py` wrapped in try/except with
`logger.warning`. Redis failure falls through to uncached execution.
## Performance / Operability
### 4. ~~Scoped purge uses OR semantics~~ FIXED
Changed to AND (intersection). `{user_id: 5, org_id: 3}` now only
deletes entries matching BOTH params.
### Broad purge leaves per-param sub-indexes
A broad `cache_purge(context)` deletes the entries but not the per-param
sub-indexes — a slow Redis memory leak.
## High (Correctness / Operability)
### No thundering-herd protection
Concurrent cold misses on the same key all execute and write. No
single-flight / request-coalescing.
### 5. ~~No TTL on Redis entries~~ FIXED
`RedisCache.put` now sets `ex=86400` (24h safety-net TTL) by default.
## API shape
### 6. Cross-language str() vs String() divergence
Python `str(True)` -> `"True"`, JS `String(true)` -> `"true"`.
**Status:** Open. Needs canonical stringification rules in protocol spec.
### cache_get / cache_put argument inconsistency
`cache_get`/`cache_put` take explicit args while the executor resolves some
inputs from module globals — two access patterns for one concern.
### 7. Broad purge doesn't clean per-param sub-indexes
**Status:** Open. Slow memory leak in Redis.
## Coverage
### 8. ~~build_index_keys doesn't stringify values~~ FIXED
Now calls `str(v)` on all values, matching `derive_cache_key`.
### 9. ~~Silent exception swallowing in get_cache()~~ FIXED
Now logs warnings for partial config and connection failures.
### 10. ~~_initialized flag not thread-safe~~ FIXED
Now uses `threading.Lock` for thread-safe initialization.
## Medium (Design / Performance)
### 11. No thundering-herd protection
**Status:** Open. Concurrent cold misses all execute and write.
### 12. ~~Wire-protocol internals in __all__~~ FIXED
`derive_cache_key` and `build_index_keys` removed from `__all__`.
### 13. Inconsistent API pattern
**Status:** Open. `cache_get`/`cache_put` take explicit args but executor
fetches from globals.
### 14. ~~clear() uses SCAN + DELETE without pipeline~~ FIXED
Now uses pipeline with UNLINK for batched async deletes.
### 15. ~~No Redis connection timeouts~~ FIXED
`socket_connect_timeout=5`, `socket_timeout=5`, `health_check_interval=30`.
### 16. No RedisCache test coverage
**Status:** Open. Only MemoryCache is tested.
### RedisCache lacks test coverage
Only `MemoryCache` is exercised by the suite. `RedisCache` (connection
pooling, TTL, SCAN/UNLINK batching, socket timeouts) is untested.

View File

@@ -56,7 +56,7 @@ def generate_edge_manifest(
manifest: dict[str, Any] = {"version": 1, "contexts": {}, "mutations": {}}
for ctx_name, fn_names in groups.items():
for ctx_name, fn_names in sorted(groups.items()):
param_names: set[str] = set()
functions_meta: list[dict[str, Any]] = []
page_routes: list[str] = []
@@ -107,7 +107,7 @@ def generate_edge_manifest(
manifest["contexts"][ctx_name] = ctx_entry
for fn_name, fn_cls in all_functions.items():
for fn_name, fn_cls in sorted(all_functions.items()):
meta = getattr(fn_cls, "_meta", {})
if not meta.get("affects"):
continue

View File

@@ -14,9 +14,6 @@ from django.conf import settings as django_settings
class mizanSettings:
"""mizan configuration."""
# Whether to expose function names in DEBUG mode errors
debug_expose_names: bool
# Cache HMAC signing secret (required when cache is enabled)
cache_secret: str | None
@@ -36,12 +33,10 @@ def get_settings() -> mizanSettings:
Load mizan settings from Django settings.
Settings:
mizan_DEBUG_EXPOSE_NAMES: Show function names in errors when DEBUG=True (default: True)
MIZAN_CACHE_SECRET: HMAC signing key for cache keys (default: None)
MIZAN_CACHE_REDIS_URL: Redis connection URL (default: None)
"""
return mizanSettings(
debug_expose_names=getattr(django_settings, "mizan_DEBUG_EXPOSE_NAMES", True),
cache_secret=getattr(django_settings, "MIZAN_CACHE_SECRET", None),
cache_redis_url=getattr(django_settings, "MIZAN_CACHE_REDIS_URL", None),
mwt_secret=getattr(django_settings, "MIZAN_MWT_SECRET", None),

View File

@@ -108,37 +108,30 @@ anonymous request. The executor branches on those for `auth=True`,
## Generate the frontend
The codegen is `mizan-generate` (in `protocol/mizan-generate/`). Point a
config at your FastAPI app and run the CLI:
The codegen is the `mizan-generate` Rust binary (source at
`protocol/mizan-codegen/`; `protocol/mizan-generate/` is a thin npm
launcher that dispatches to the platform binary). Point a `mizan.toml` at
your FastAPI app and run the CLI:
```js
// frontend/fastapi.config.mjs
import path from "path"
import { fileURLToPath } from "url"
```toml
# frontend/mizan.toml
output = "src/api"
targets = ["react"]
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, "..")
export default {
source: {
fastapi: {
module: "main", // module to import for @client side effects
cwd: path.join(root, "backend"), // python cwd for module resolution
command: ["uv", "run", "python"], // optional — defaults to ["python"]
},
},
output: "src/api",
}
[source.fastapi]
module = "main" # module to import for @client side effects
cwd = "../backend" # python cwd for module resolution
command = ["uv", "run", "python"] # optional — defaults to ["python"]
```
```bash
npx mizan-generate --config fastapi.config.mjs
mizan-generate --config mizan.toml
```
The codegen drives `python -m mizan_fastapi.cli <module>` under the hood,
then emits Stage 1 (typed `callXxx`/`fetchXxx` over the runtime kernel) +
Stage 2 (`<MizanContext>` provider, per-context providers, `use{Hook}()`
hooks) into `src/api/`.
The codegen drives `python -m mizan_fastapi.ir <module>` under the hood,
parses the emitted KDL IR, then emits Stage 1 (typed `callXxx`/`fetchXxx`
over the runtime kernel) + Stage 2 (`<MizanContext>` provider, per-context
providers, `use{Hook}()` hooks) into `src/api/`.
```tsx
// app.tsx
@@ -171,13 +164,13 @@ uv run pytest
For codegen consumption (or any tooling that wants the Mizan schema):
```bash
python -m mizan_fastapi.cli <module>
python -m mizan_fastapi.ir <module>
```
Imports the named module (which must register every `@client` function as
import-time side effects), then prints the OpenAPI schema as JSON to stdout.
Mirrors mizan-django's `manage.py export_mizan_schema` so the codegen
consumes either backend the same subprocess way.
import-time side effects), then prints the Mizan KDL IR to stdout.
Mirrors mizan-django's `manage.py export_mizan_ir` so the codegen consumes
either backend the same subprocess way.
## Architecture

View File

@@ -52,10 +52,6 @@ function extractParams(fn: Function): ParamDef[] {
})
}
function isResponseReturn(result: any): boolean {
return result instanceof Response
}
/**
* Function wrapper — registers a standalone function.
*

View File

@@ -22,10 +22,6 @@ export interface MizanResponse {
headers: Record<string, string>
}
function sortedStringify(data: any): string {
return JSON.stringify(data, Object.keys(data).sort())
}
/**
* Handle GET /api/mizan/ctx/:contextName/
*