i spent the last year just spinning

This commit is contained in:
Relism
2026-04-17 18:56:06 +02:00
parent 9efbe38c0c
commit e161497f2c
77 changed files with 2545 additions and 877 deletions
@@ -0,0 +1,29 @@
# Adapters
`ViewEngineAdapter` is the rendering boundary.
## Built-in
- `ViewEngineType.THYMELEAF`
## Custom adapter
```java
public final class MyAdapter implements ViewEngineAdapter {
@Override
public EngineCapabilities capabilities() {
return EngineCapabilities.NONE;
}
@Override
public RenderOutput render(ViewTarget target,
Map<String, Object> model,
Request req,
Response res) {
String body = "...";
return RenderOutput.html(body);
}
}
```
Adapter instances must be thread-safe after construction.
@@ -0,0 +1,23 @@
# Architecture
`flash-ext-view` runs in two layers:
1. **Boot-time**
- `ViewExtension` registers `ViewRuntime` and an annotation processor.
- `ViewTargetResolver` validates handlers and maps annotations to `ViewTarget`.
- Resolved targets are cached per handler class.
2. **Request-time**
- Handler builds local `ViewModel`.
- `ViewRuntime` injects extension globals under reserved `global` namespace, then merges local model.
- `ViewEngineAdapter` renders `RenderOutput`.
## Valid Handler Contract
- Must extend `ViewHandler`.
- Must have route annotation (`@Route`, `@GET`, `@POST`, ...).
- Must declare exactly one view annotation:
- `@Page`
- `@Partial`
Invalid configurations fail fast at startup.
@@ -0,0 +1,35 @@
# Handlers
Use `ViewHandler` for class-based SSR routes.
## Lifecycle
- `onViewInit()` runs once at boot.
- `render(...)` runs per request.
Use `onViewInit()` to cache dependencies via `require(...)`.
## Example
```java
@GET("/dashboard")
@Page("pages/dashboard")
public final class DashboardPage extends ViewHandler {
private DashboardService service;
@Override
protected void onViewInit() {
service = require(DashboardService.class);
}
@Override
public ViewModel render(dev.relism.models.Request req) {
return ViewModel.empty()
.with("title", "Dashboard")
.with("stats", service.stats());
}
}
```
Use `render(Request, Response)` only when you need response access while building the model.
@@ -0,0 +1,33 @@
# Migration from Legacy View API
Legacy API (`@View`, `ViewEngine`, `Renderer`, `Template`) has been removed.
## Replace annotations
- `@View("page")` -> `@Page("page")`
- `@View(value = "x", fragment = true)` -> `@Partial(template = "x")`
`@Page` and `@Partial` must be declared on classes extending `ViewHandler`.
## Replace handler return contract
Before (legacy):
```java
public Object handle(Request req, Response res) {
return Map.of("name", "Flash");
}
```
Now:
```java
public ViewModel render(Request req) {
return ViewModel.of("name", "Flash");
}
```
## Replace engine integration
- Implement `ViewEngineAdapter` directly.
- Or use `ViewEngineType.THYMELEAF`.
@@ -0,0 +1,31 @@
# Model and Globals
`ViewModel` is the per-request model builder.
## Merge Order
Runtime merge order is:
1. all extension globals under reserved `global` namespace
2. local handler model
Handlers cannot set top-level `global`; runtime throws fail-fast to prevent namespace collisions.
## Globals
Register globals on extension setup:
```java
new ViewExtension(ViewEngineType.THYMELEAF)
.addGlobal("appName", req -> "Flash")
.addGlobal("path", req -> req.path());
```
Template usage:
```html
<span th:text="${global.appName}"></span>
<span th:text="${global.path}"></span>
```
Keep globals cheap: no blocking I/O or heavy allocations.
@@ -0,0 +1,19 @@
# Partials
Use `@Partial` for fragment responses.
```java
@GET("/users/table")
@Partial(template = "fragments/users", slot = "rows")
public final class UsersRows extends ViewHandler {
@Override
public ViewModel render(dev.relism.models.Request req) {
return ViewModel.of("users", List.of());
}
}
```
If slot is empty, the adapter default slot is used.
If a slot is specified but the adapter does not support slot selection,
startup fails with a clear error.
@@ -0,0 +1,16 @@
# Performance
`flash-ext-view` is optimized for low overhead on request path.
## Current runtime choices
- Handler view metadata resolved once and cached.
- Global/local model merge done in a single pass.
- No legacy rendering branches in runtime pipeline.
## Best practices
- Cache services in `onViewInit()`.
- Keep `addGlobal(...)` resolvers cheap and side-effect free.
- Build only the model fields needed by template.
- Avoid blocking I/O in `render(...)`; delegate to precomputed service data when possible.