Cleaned dead code and updated documents
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -52,10 +52,6 @@ function extractParams(fn: Function): ParamDef[] {
|
||||
})
|
||||
}
|
||||
|
||||
function isResponseReturn(result: any): boolean {
|
||||
return result instanceof Response
|
||||
}
|
||||
|
||||
/**
|
||||
* Function wrapper — registers a standalone function.
|
||||
*
|
||||
|
||||
@@ -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/
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user