108 lines
5.7 KiB
Markdown
108 lines
5.7 KiB
Markdown
# Application Framework Interface Invariants
|
||
|
||
All invariants are absolute. Agents are not permitted to modify this file unless **DIRECTLY PROMPTED BY RYTH**.
|
||
|
||
If an invariant is not satisfiable by the backend's native functionality (for example, FastAPI is missing a native ORM for Shapes),
|
||
then a canonical technology must be proposed. The technology *MUST* be approved by Ryth before implementation.
|
||
|
||
## Backend Adapters
|
||
|
||
Django (python)
|
||
FastAPI (python)
|
||
Typescript (generic)
|
||
Rust/Axum (generic)
|
||
Tauri (Rust)
|
||
|
||
## Frontend Adapters
|
||
|
||
React (Typescript)
|
||
Vue (Typescript)
|
||
Svelte (Typescript)
|
||
Tauri (Rust)
|
||
|
||
### Client Function RPC
|
||
|
||
---
|
||
|
||
No REST endpoints.
|
||
|
||
Client functions are decorated functions (decorator or registration call at definition-site) that both receive and return HTTP & JSON compliant arguments.
|
||
The decoration mechanism must implement the full variadic or kwarg set (websocket, auth, context wiring).
|
||
|
||
### WebSocket Support
|
||
|
||
---
|
||
|
||
A client function declared `websocket=` is dispatched over a persistent connection rather than request/response. Server-initiated messages reach the subscribed contexts; invalidation travels the socket with the same semantics it has over HTTP.
|
||
|
||
The per-adapter transport differs — Django Channels, a native WebSocket route, a Tauri IPC subscription channel — but the declaration and the wire semantics do not. Mixing socket and non-socket transport within one context is a registration-time error.
|
||
|
||
### Named Contexts
|
||
|
||
---
|
||
|
||
Any string passed to `context=` is a named context. Functions sharing a context name are grouped at registration into one provider, one fetch, and one set of generated hooks — a single read request, never N round-trips. `context='global'` is the one reserved name: fetched once at the root and SSR-hydrated.
|
||
|
||
Shared parameters elevate to required provider props; non-shared params elevate to optional props with per-function override. A read context is GET-dispatched and cacheable, and it is the unit a mutation invalidates.
|
||
|
||
### Mutation Invalidation
|
||
|
||
---
|
||
|
||
A mutation declares what it `affects=` — a context name, a function reference, or a list — and that relationship is generated into the client. On success the affected contexts refetch; on failure nothing invalidates. The developer never writes a cache key, never calls an invalidate function, never maintains a query-key map.
|
||
|
||
Invalidation auto-scopes by matching parameter name: a mutation carrying `user_id=123` invalidates the `user_id=123` entry, not the whole context.
|
||
|
||
This is the invariant that separates the AFI from typed RPC. An adapter that dispatches calls and projects shapes but leaves the client hand-writing invalidation has not satisfied it. The client holds a server-reconciled view, never a parallel source of truth.
|
||
|
||
### API Shapes
|
||
|
||
---
|
||
|
||
A backend adapter supports the "API Shape" feature to the fullest extent:
|
||
|
||
- ORM Integration
|
||
- Auto-diffing (Receive a list of objects, check primary keys for add/modify/delete semantics, use Django as reference)
|
||
- Backend-for-Frontend Authoring DX (Shape schema must be easily authorable near used function)
|
||
|
||
### Auth
|
||
|
||
---
|
||
|
||
A function declaring `auth=` is enforced at dispatch on every adapter — the guard rejects before the function body runs, identically across transports. Authorization is a property of the declared function, carried in the IR, not middleware an adapter bolts on or omits.
|
||
|
||
### File Uploads
|
||
|
||
---
|
||
|
||
The `Upload` type is a first-class argument carried end to end — IR, codegen, and dispatch binding. Arguments are otherwise HTTP- and JSON-compliant; `Upload` is the one binary exception, bound from multipart over HTTP and from the envelope over IPC. The declaration is uniform; the transport binding is per-adapter.
|
||
|
||
### Canonical IR & Codegen
|
||
|
||
---
|
||
|
||
Every backend adapter emits the canonical KDL IR describing its functions, contexts, types, and invalidation graph. Every frontend client is generated from that IR. No REST envelope, no OpenAPI document, no per-backend converter sits between a backend and a frontend — the IR is the only contract.
|
||
|
||
This is the invariant that collapses the backends × frontends quadratic to one adapter per stack. A backend that does not emit the IR, or a frontend not generated from it, is outside the AFI: the boundary is the IR, and nothing crosses it untyped.
|
||
|
||
### Client Kernel
|
||
|
||
---
|
||
|
||
Every frontend adapter is a thin idiomatic wrapper over one shared kernel. The kernel owns the reconciled cache — context state, status, error, server-driven merge and invalidate, session init — and reaches the backend through a pluggable transport (HTTP, Tauri IPC, webview channel). Framework adapters subscribe and render in their own idiom (React hooks, Vue composables, Svelte runes); codegen targets the adapter surface, never the raw kernel.
|
||
|
||
No adapter keeps its own copy of the truth. The reconciled view lives once, in the kernel.
|
||
|
||
### SSR
|
||
|
||
---
|
||
|
||
Server rendering is the AFI's second product, orthogonal to RPC and composable with it — either ships standalone. A function's registered render strategy renders on the server through the bridge and hydrates on the client; the contexts a page reads are SSR-hydrated at the root, so first paint carries data rather than a loading state.
|
||
|
||
## Compositions
|
||
|
||
Stdlib over the invariants above, not invariants in themselves — named so the boundary is explicit and an adapter is never marked short for lacking them as primitives:
|
||
|
||
- **Forms** — three role-tagged client functions (schema / validate / submit) plus field validation. RPC and validation composed; not its own primitive.
|
||
- **Context classes (`send` / `receive`)** — the read/write class form with Shape diffing. Named Contexts + API Shapes + Mutation Invalidation composed into one declaration; the heavy DX surface over the primitives, not a new primitive.
|