fix: harden UCE runtime and starter

This commit is contained in:
udo
2026-06-11 13:44:24 +00:00
parent 71ddcaf7d4
commit 7f757654b6
128 changed files with 276200 additions and 872 deletions
+1
View File
@@ -1,5 +1,6 @@
Output / Invocation Functions
coming_from_react
1_RENDER
1_CLI
cli_input
+8
View File
@@ -0,0 +1,8 @@
SQLite
sqlite_connect
sqlite_disconnect
sqlite_error
sqlite_query
sqlite_insert_id
sqlite_affected_rows
+6
View File
@@ -7,6 +7,12 @@ filter
first
join
json_encode
list_every
list_find
map
list_some
list_sort
list_unique
nibble
print
strpos
+7
View File
@@ -3,6 +3,13 @@ Types
0_Request
array_merge
0_DTree
dtree_filter
dtree_group_by
dtree_keys
dtree_map
dtree_omit
dtree_pick
dtree_values
get_by_path
set_status
String
+8
View File
@@ -2,6 +2,14 @@ URI Functions
encode_query
redirect
request_context_params
request_base_url
request_query_path
request_query_route
request_script_url
route_path_is_safe
route_path_normalize
route_path_sanitize
session_id_create
parse_query
uri_decode
+412 -37
View File
@@ -2,61 +2,436 @@
Request
:sig
Request& context;
Request& context
:see
>types
request_context_params
request_script_url
request_base_url
request_query_path
request_query_route
set_status
component
component_render
unit_render
ws_message
unit_call
session_start
set_cookie
parse_query
parse_multipart
ws_message
ws_connection_id
ws_connections
ws_send
0_DTree
StringMap
UploadedFile
:content
`Request& context` is the request-local state object passed into UCE handlers. It carries incoming request data, response state, runtime metadata, and helper trees such as `context.cfg`, `context.props`, and `context.connection`.
`Request& context` is the request-local state object passed into every UCE handler:
## Core Fields
```cpp
RENDER(Request& context) { ... }
COMPONENT(Request& context) { ... }
WS(Request& context) { ... }
CLI(Request& context) { ... }
SERVE_HTTP(Request* req) { ... }
```
- `ServerState* server`: current server state
- `StringMap params`: FastCGI server parameters
- `StringMap get`: current request GET variables
- `StringMap post`: current request POST variables
- `StringMap cookies`: cookies sent by the browser
- `StringMap session`: current session data
- `String session_id`: session cookie ID
- `String session_name`: session cookie name
- `DTree cfg`: request-local configuration tree
- `DTree call`: invocation-local structured data for helpers such as component and unit calls
- `DTree connection`: broker-owned WebSocket connection state that persists across `WS(Request& context)` calls for the same socket
- `std::vector<UploadedFile> uploaded_files`: files uploaded in the current request
- `StringMap header`: headers to send back to the browser
- `StringList set_cookies`: cookies queued for the response
- `u64 random_seed`: current request noise seed
- `u64 random_index`: current request noise index position
It is the main bridge between the runtime and page code. It contains incoming request data, response state, output buffers, per-request scratch trees, session/cookie state, WebSocket metadata, and runtime diagnostics.
## Response Control
## Handler Lifetime
`context.set_status(s32 code[, String reason])` sets the HTTP status line and updates `context.flags.status`. When `reason` is omitted, UCE uses a built-in standard reason phrase for common status codes.
A fresh `Request` is created for each HTTP/CLI/custom-server request. Component and unit calls normally share that same object, so state placed on `context.call`, `context.header`, `context.session`, or `context.cfg` is visible to later components in the same request.
## Flags And Stats
For WebSockets, each incoming message is delivered as its own `Request`, but `context.connection` points at broker-owned per-socket state that persists for the lifetime of that WebSocket connection.
- `bool flags.log_request`: controls whether the request should be logged
- `u32 stats.bytes_written`
- `f64 stats.time_init`
- `f64 stats.time_start`
- `f64 stats.time_end`
`ONCE(Request& context)` hooks run once per request, per resolved unit file, before the first `RENDER`, `COMPONENT`, `CLI`, or matching entrypoint from that unit.
## Common Usage Notes
## Incoming Request Maps
- `context.cfg` is the usual place for structured configuration. Use `context.cfg.get_by_path("path/to/value")` for deep reads.
- `context.props` carries invocation data for component calls and unit calls.
- `context.in` carries the current request body, and for `WS(Request& context)` it is the current WebSocket message payload.
- `context.params["WS_..."]` exposes the current WebSocket message metadata directly on the request parameter map.
- `context.connection` is only meaningful for WebSocket traffic and persists for the lifetime of the connection.
### `context.params` — server/runtime parameters
`unit_render(String file_name, [Request& context])` invokes another UCE file using the current or supplied request context.
Type: `StringMap`
This is the low-level parameter map from FastCGI/direct HTTP plus UCE-populated convenience fields. It is closest to PHP `$_SERVER`.
Common CGI/FastCGI-style keys include:
- `REQUEST_METHOD`: `GET`, `POST`, etc.
- `REQUEST_URI`: raw request URI where available
- `DOCUMENT_URI`: normalized request path where available
- `SCRIPT_NAME`: script path where available
- `SCRIPT_FILENAME`: resolved filesystem path of the active UCE unit
- `QUERY_STRING`: raw query string
- `DOCUMENT_ROOT`: web root used by the frontend/backend
- `CONTENT_TYPE`: request body content type
- `CONTENT_LENGTH`: request body length
- `HTTP_COOKIE`: raw cookie header
- `HTTP_HOST`, `HTTP_USER_AGENT`, `HTTP_ACCEPT`, and other `HTTP_...` headers supplied by the frontend
UCE also populates convenience route/link fields before handlers run:
- `SCRIPT_URL`: canonical script URL; `/index.uce` is collapsed to the containing directory URL
- `BASE_URL`: canonical directory URL for the script
- `ROUTE_PATH`: sanitized first keyless query-string segment, defaulting to `index` when no route was supplied; empty when unsafe input was rejected
- `ROUTE_PAGE`: first segment of `ROUTE_PATH`
- `ROUTE_PATH_RAW`: normalized but untrusted route input, for diagnostics only
- `ROUTE_VALID`: `1` when route input is safe, `0` when the supplied route was rejected
See `request_context_params`, `request_script_url`, `request_base_url`, `request_query_path`, `request_query_route`, and `route_path_sanitize`.
### `context.get`
Type: `StringMap`
Parsed query-string key/value parameters. This is populated from `context.params["QUERY_STRING"]` with `parse_query()`.
```cpp
String theme = first(context.get["theme"], "default");
```
For front-controller route URLs such as `/?dashboard&theme=dark`, the keyless `dashboard` segment is represented by sanitized `ROUTE_PATH`; named parameters such as `theme=dark` are available in `context.get`.
### `context.post`
Type: `StringMap`
Parsed request body parameters for ordinary `POST` requests. URL-encoded bodies are parsed with `parse_query()`. Multipart form data is parsed with `parse_multipart()` and uploaded files are listed in `context.uploaded_files`.
```cpp
if(context.params["REQUEST_METHOD"] == "POST")
String email = context.post["email"];
```
### `context.cookies`
Type: `StringMap`
Cookies sent by the client, parsed from `HTTP_COOKIE`. `set_cookie()` also updates this map after queuing a response cookie, so later code in the same request can observe the new value.
### `context.in`
Type: `String`
Raw request body. For WebSocket handlers, this is the current message payload.
Use this for JSON APIs:
```cpp
DTree body = json_decode(context.in);
```
### `context.uploaded_files`
Type: `std::vector<UploadedFile>`
Each `UploadedFile` contains:
- `file_name`: original submitted filename
- `tmp_name`: temporary server-side upload path
- `size`: uploaded byte count
Use this with multipart form posts.
## Session State
### `context.session`
Type: `StringMap`
Session data loaded by `session_start()`. UCE does not load sessions automatically for every request; call `session_start()` before reading/writing session data.
```cpp
session_start();
context.session["user_id"] = "42";
```
At the end of a successful request, modified session data is saved automatically if a session is active.
### `context.session_id` and `context.session_name`
The active session ID and cookie name after `session_start()`.
Related helpers:
- `session_start()`
- `session_destroy()`
- `session_id_create()`
- `set_cookie()`
## Per-request Structured Trees
### `context.call`
Type: `DTree`
General request-local scratch/configuration tree. It is shared by the page, components, and unit calls participating in the current request. Use it for app-level request state, fragments, router results, page type, page title, and other values that need to be read by later components.
Examples from front-controller style apps:
```cpp
context.call["route"] = request_query_route(context);
context.call["app"]["page_type"] = "html";
context.call["fragments"]["main"] = captured_html;
```
Prefer clear top-level names when state is app-wide (`route`, `fragments`) and nested app names only when the state is truly owned by that app (`app/page_title`, `app/page_type`).
### `context.cfg`
Type: `DTree`
Request-local structured configuration. The runtime does not fill this with application config by default; application code may assign it during boot/setup:
```cpp
context.cfg = get_config();
```
This is separate from `context.server->config`, which is the runtime/server string config from `/etc/uce/settings.cfg`.
Use `get_by_path()` for non-mutating deep reads:
```cpp
String site_name = context.cfg.get_by_path("site/name").to_string();
```
### `context.props`
Type: `DTree`
Invocation-local props for `component()`, `component_render()`, and macro-style `unit_call()` entrypoints. During a component call, the runtime temporarily replaces `context.props` with the props passed to that component and restores the previous value after the call returns.
```cpp
DTree props;
props["title"] = "Dashboard";
print(component("components/card", props, context));
```
### `context.connection`
Type: `DTree`
WebSocket connection-local state. Mutations persist across `WS(Request& context)` calls for the same socket.
```cpp
context.connection["message_count"] = context.connection["message_count"].to_u64() + 1;
```
Only meaningful for WebSocket handlers.
## Response State
### `context.response_code`
Type: `String`
The raw status line. Usually use `context.set_status(...)` instead of writing this directly.
### `context.header`
Type: `StringMap`
Response headers to emit. Header names are case-sensitive as written.
```cpp
context.header["Content-Type"] = "application/json";
context.header["Location"] = "/info/";
```
### `context.set_cookies`
Type: `StringList`
Queued `Set-Cookie` header lines. Prefer `set_cookie()` instead of editing this directly.
### `context.set_status(code[, reason])`
Sets the HTTP response status and `context.flags.status`.
```cpp
context.set_status(404, "Not Found");
context.set_status(302, "Found");
context.header["Location"] = app_link("dashboard", context);
```
Related helpers:
- `redirect(url[, code])`
- `set_cookie(...)`
## Output Buffers
### `context.ob_stack` and `context.ob`
Internal output-buffer stack. Most code should use helpers instead of touching these directly:
- `print(...)`
- `out(...)`
- `ob_start()`
- `ob_get()`
- `ob_get_close()`
- `ob_close()`
Common capture pattern:
```cpp
ob_start();
print(component("views/dashboard", context));
String html = ob_get_close();
context.call["fragments"]["main"] = html;
```
### `context.out` and `context.err`
Runtime output/error artifacts used by some transports and failure paths. Normal page rendering should use `print()` / output buffers.
## Request Flags
`context.flags` contains runtime booleans and the numeric status:
- `log_request`: whether the request should be logged
- `is_finished`: internal completion marker
- `status`: numeric HTTP status set by `set_status()`
- `output_closed`: internal transport state
- `params_closed`: internal transport state
- `input_closed`: internal transport state
Most page code only reads `flags.status`, if anything.
## Request Stats
`context.stats` contains counters/timing for the request:
- `bytes_written`
- `time_init`
- `time_start`
- `time_end`
- `mem_high`
- `mem_alloc`
- `invoke_count`
These are useful for diagnostics, demos, and runtime instrumentation.
## Random / Noise State
- `random_seed`
- `random_index`
Used by UCE noise/random helpers to provide request-local deterministic progression.
Related helpers include functions in the noise/hash area such as `gen_int`, `gen_float`, `gen_noise64`, and `gen_sha1`.
## WebSocket Fields
In `WS(Request& context)`, the runtime mirrors WebSocket metadata into `context.params` and `context.resources`.
Convenience `context.params` keys include:
- `WS_MESSAGE`
- `WS_CONNECTION_ID`
- `WS_SCOPE`
- `WS_CONNECTION_COUNT`
- `WS_OPCODE`
- `WS_MESSAGE_TYPE`
- `WS_DOCUMENT_URI`
Prefer WebSocket helper functions where possible:
- `ws_message()`
- `ws_connection_id()`
- `ws_scope()`
- `ws_opcode()`
- `ws_is_binary()`
- `ws_connections()`
- `ws_connection_count()`
- `ws_send()`
- `ws_send_to()`
- `ws_close()`
Use `context.connection` for per-socket structured state.
## Runtime / Resource Fields
### `context.server`
Pointer to server state. Useful mainly for low-level/runtime code. Runtime config lives at:
```cpp
context.server->config["KEY"]
```
This is a `StringMap`, separate from app-owned `context.cfg`.
### `context.resources`
Internal runtime resources and transport state. Includes sockets, MySQL handles, WebSocket state, current unit file, and parser buffers. Application code should normally use public helpers instead of editing this directly.
Notable fields:
- `is_websocket`
- `is_cli`
- `websocket_connection_id`
- `websocket_scope`
- `websocket_scope_connection_ids`
- `current_unit_file`
## Common Patterns
### Minimal page
```cpp
RENDER(Request& context)
{
<><h1>Hello <?= context.get["name"] ?></h1></>
}
```
### JSON endpoint
```cpp
RENDER(Request& context)
{
context.header["Content-Type"] = "application/json";
DTree response;
response["ok"].set_bool(true);
print(json_encode(response));
}
```
### Redirect
```cpp
RENDER(Request& context)
{
context.set_status(302, "Found");
context.header["Location"] = "/info/";
}
```
### Component props
```cpp
DTree props;
props["title"] = "Welcome";
print(component("components/card", props, context));
```
### Front-controller route
```cpp
context.call["route"] = request_query_route(context);
String route_path = context.call["route"]["l_path"].to_string();
```
Or use runtime-populated params directly:
```cpp
String route_path = context.params["ROUTE_PATH"];
```
## Related Concepts
- PHP: `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, `$_SESSION`, `header()`, and `http_response_code()`
- JavaScript / Node.js: Express `req` and `res`, Fetch `Request`, `Headers`, cookies or session middleware, and per-connection state in WebSocket handlers
- PHP: `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, `$_SESSION`, `header()`, output buffering, and `http_response_code()`
- JavaScript / Node.js: Express `req`/`res`, Fetch `Request`/`Response`, route params, middleware-populated locals, and per-socket WebSocket state
+16
View File
@@ -26,6 +26,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
- Inside a literal block, `<?: expression ?>` emits `print(expression);` without HTML escaping.
- `#load "other.uce"` injects another UCE unit at compile time.
- `RENDER(Request& context)`, `COMPONENT(Request& context)`, `CLI(Request& context)`, `ONCE(Request& context)`, `INIT(Request& context)`, and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
- `ONCE`, `RENDER`, and `COMPONENT` may be followed by a preprocessor attribute line such as `@fragment head` before the opening `{`. The handler's output is then captured and appended to `context.call["fragments"]["head"]` instead of being emitted at the call site. `ONCE` defaults to `@fragment once` when no fragment is specified.
- `COMPONENT:NAME(Request& context)` is rewritten by the custom pass into an exported named component handler.
- `EXPORT` is also a normal C++ macro, but the custom pass additionally records exported declarations for metadata.
@@ -42,6 +43,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
- `<?: ... ?>` becomes `print(...);` and is intended for trusted markup or already-escaped content.
- `#load "file.uce"` is replaced with a generated C++ `#include` that points at the loaded unit's preprocessed `.cpp` file under `BIN_DIRECTORY`.
- Lines beginning with `EXPORT` are scanned so their declarations can be written to a sibling `.exports.txt` file.
- `@fragment slot-name` lines immediately following `ONCE`, `RENDER`, or `COMPONENT` are removed and replaced with an output-capture guard at the start of the handler body.
- Lines beginning with `RENDER:NAME(...)` are rewritten into exported `__uce_render_NAME(...)` functions.
- Lines beginning with `COMPONENT:NAME(...)` are rewritten into exported `__uce_component_NAME(...)` functions for the component helpers.
- The final generated source is written to `BIN_DIRECTORY + src_path + "/" + source_file + ".cpp"`.
@@ -130,6 +132,18 @@ ONCE(Request& context)
}
```
One-time page assets captured for a template-controlled slot:
```cpp
ONCE(Request& context)
@fragment head
{
?><link rel="stylesheet" href="/assets/page.css" /><?
}
```
The page template can then render `context.call["fragments"]["head"]` inside `<head>`.
## Rules
- Literal mode can start on either `<>` or `?>`.
@@ -155,6 +169,8 @@ ONCE(Request& context)
- Inspect the generated file under `BIN_DIRECTORY` first. That file shows the exact C++ produced by the UCE preprocessor.
- Compiler errors usually point back to the `.uce` source because the preprocessor inserts `#line 1`, but the generated `.cpp` is still the best place to inspect expansion problems.
- Compile failures are reported with the source path, generated C++ path, compile-output artifact path, an excerpt when UCE can identify a line, and the raw compiler output from the configured compile script.
- Runtime request failures include the request/script path, generated C++ path, a hint about inspecting template delimiters and recent component/unit calls, and a native trace when available.
- If a `#load` include looks wrong, check the current file's directory, the configured `BIN_DIRECTORY`, and whether the loaded page already produced its own generated `.cpp`.
## Related Concepts
+69
View File
@@ -0,0 +1,69 @@
:title
Coming from React, Next, or Remix
:sig
UCE orientation for React-framework developers
:see
1_RENDER
1_COMPONENT
component
unit_render
3_C++ Preprocessor
map
filter
dtree_filter
:content
UCE is server-first C++ with a small template preprocessor. It does not try to be React, but several concepts map cleanly.
## Concept Map
- `RENDER(Request& context)` is the page/server-render entrypoint.
- `COMPONENT(Request& context)` and `COMPONENT:NAME(Request& context)` are server-rendered components.
- `context.props` is the component invocation payload, similar to props.
- `context.call` is request-local scratch state shared across units during one request.
- `context.cfg` is structured app/config data.
- `ONCE(Request& context)` is per-request setup for a unit before its first render/component entry.
- `INIT(Request& context)` is worker-local setup when a unit is loaded.
- `<?= expression ?>` is escaped interpolation; prefer it for user-visible text.
- `<?: expression ?>` is trusted raw markup output, closer to a deliberate `dangerouslySetInnerHTML` decision.
- `unit_render()` renders another page unit; `component()` returns component HTML as a string.
## Routes and Layouts
UCE does not require a framework-level router. A front controller can keep routing explicit and app-local. The starter example demonstrates this in `site/examples/uce-starter/index.uce`: it resolves a request path by checking:
1. `views/<path>.uce`
2. `views/<path>/index.uce`
3. parent index handlers such as `views/workspace/index.uce` with the last segment as a route parameter
That keeps file-based and hierarchical routing in normal UCE code instead of hiding it in the runtime.
## Data Shaping Near Render Code
The function library includes small collection helpers for common route/menu/card transformations:
```cpp
auto visible = filter(routes, [](String route) { return(route != "admin"); });
auto labels = map(visible, [](String route) { return(to_upper(route)); });
DTree app_items = dtree_filter(menu, [](DTree item, String key) { return(item["section"].to_string() == "app"); });
DTree by_section = dtree_group_by(menu, [](DTree item, String key) { return(item["section"].to_string()); });
```
Use these when the transformation communicates intent. Prefer explicit loops when side effects or multi-step validation are the main concern.
## Assets and Islands
Global runtime APIs for assets and islands are intentionally not part of UCE core. The starter emits CSS and JavaScript from the owning unit's `ONCE(Request& context)` hook, with a few shared sibling asset components when multiple components need the same files. The only starter web-affordance helper left is `COMPONENT:island` in `components/theme/web_affordances.uce` for small progressive-enhancement modules. This keeps app policy in the app without an asset registry layer.
## Debugging
When a unit fails to compile, UCE reports the source path, generated C++ path, compile-output artifact, a source/generated excerpt when it can identify a line, and the raw compiler output. The generated C++ under `BIN_DIRECTORY` is the source of truth for what the configured compiler actually saw.
## What Not To Expect
- No client-side virtual DOM is built into UCE.
- No global file-router is imposed by the runtime.
- No JSX-like component tags are required for this workflow.
- Component children/slot syntax is intentionally deferred; use explicit props and component calls for now.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_filter
:sig
DTree dtree_filter(DTree tree, function<bool (DTree, String)> f)
:see
StringList
0_DTree
filter
:content
Keeps children for which f returns true. List-like input stays list-like.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree visible = dtree_filter(items, [](DTree item, String key) { return(item["hidden"].to_bool() == false); });
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_group_by
:sig
DTree dtree_group_by(DTree tree, function<String (DTree, String)> f)
:see
StringList
0_DTree
filter
:content
Groups children into list-like buckets by the string returned from f.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree by_section = dtree_group_by(menu, [](DTree item, String key) { return(item["section"].to_string()); });
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_keys
:sig
StringList dtree_keys(DTree tree)
:see
StringList
0_DTree
filter
:content
Returns map keys from a DTree. Scalar values produce an empty list.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
StringList keys = dtree_keys(context.cfg["menu"]);
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_map
:sig
DTree dtree_map(DTree tree, function<DTree (DTree, String)> f)
:see
StringList
0_DTree
filter
:content
Transforms each child. List-like input stays list-like; map input keeps keys.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree titles = dtree_map(items, [](DTree item, String key) { DTree out; out = item["title"].to_string(); return(out); });
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_omit
:sig
DTree dtree_omit(DTree tree, StringList keys)
:see
StringList
0_DTree
filter
:content
Copies a DTree map except for selected keys.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree safe_user = dtree_omit(user, {"password_hash"});
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_pick
:sig
DTree dtree_pick(DTree tree, StringList keys)
:see
StringList
0_DTree
filter
:content
Copies only selected keys from a DTree map.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree public_user = dtree_pick(user, {"name", "avatar"});
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
dtree_values
:sig
DTree dtree_values(DTree tree)
:see
StringList
0_DTree
filter
:content
Returns child values as a list-like DTree.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
DTree menu_items = dtree_values(context.cfg["menu"]);
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
list_every
:sig
bool list_every(StringList items, function<bool (String)> f)
:see
StringList
0_DTree
filter
:content
Returns true when every item matches.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
bool all_named = list_every(routes, [](String s) { return(s != ""); });
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+18
View File
@@ -0,0 +1,18 @@
:title
list_filter
:sig
Use filter(StringList items, function<bool (String)> f)
:see
filter
StringList
:content
`list_filter()` was removed because `StringList` is `std::vector<String>` and the generic `filter()` helper covers the same behavior without a second implementation to maintain.
Use:
```cpp
auto visible = filter(routes, [](String s) { return(s != "admin"); });
```
+26
View File
@@ -0,0 +1,26 @@
:title
list_find
:sig
String list_find(StringList items, function<bool (String)> f, String fallback = "")
:see
StringList
0_DTree
filter
:content
Returns the first matching item or fallback.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
String route = list_find(routes, [](String s) { return(str_starts_with(s, "dashboard")); }, "index");
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+19
View File
@@ -0,0 +1,19 @@
:title
list_map
:sig
Use map(StringList items, function<String (String)> f)
:see
map
filter
StringList
:content
`list_map()` was removed because `StringList` is `std::vector<String>` and the generic `map()` helper covers the same behavior without a second implementation to maintain.
Use:
```cpp
auto upper = map(names, [](String s) { return(to_upper(s)); });
```
+26
View File
@@ -0,0 +1,26 @@
:title
list_some
:sig
bool list_some(StringList items, function<bool (String)> f)
:see
StringList
0_DTree
filter
:content
Returns true when any item matches.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
bool has_dashboard = list_some(routes, [](String s) { return(s == "dashboard"); });
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
list_sort
:sig
StringList list_sort(StringList items)
:see
StringList
0_DTree
filter
:content
Returns a sorted copy of the list.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
auto sorted = list_sort(tags);
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+26
View File
@@ -0,0 +1,26 @@
:title
list_unique
:sig
StringList list_unique(StringList items)
:see
StringList
0_DTree
filter
:content
Returns the first occurrence of each string, preserving input order.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
```cpp
auto tags = list_unique({"uce", "docs", "uce"});
```
Prefer these helpers over open-coded loops when the transformation itself is the important part of the code. Use an explicit loop when mutation, error handling, or side effects are the main concern.
## Related Concepts
- JavaScript / React: `Array.map`, `Array.filter`, `Array.find`, object `pick`/`omit`, and grouping route or navigation records before rendering.
- PHP: `array_map`, `array_filter`, `array_unique`, and associative array projection.
+31
View File
@@ -0,0 +1,31 @@
:title
map
:sig
StringList map(StringList items, function<String (String)> f)
vector<R> map(vector<T> items, function<R (T)> f)
:params
items : list of items to transform
f : a function that returns the transformed value for each item
return value : a new list containing the transformed values
:see
>string
filter
StringList
0_DTree
:content
Returns a new list by calling `f` for each item in `items`.
```cpp
auto upper = map(names, [](String s) { return(to_upper(s)); });
```
Use `filter()` when you want to keep only matching items, and `map()` when you want to transform each item.
## Related Concepts
- JavaScript / React: `Array.prototype.map()`
- PHP: `array_map()`
+10 -1
View File
@@ -13,7 +13,16 @@ return value : a list of rows returned from executing the query
:content
Executes a MySQL query and returns the resulting data, if any.
`params` provides the query parameter values used by the statement.
`params` provides the query parameter values used by the statement. Use named `:name` placeholders only; positional `?` placeholders are rejected.
```cpp
StringMap params;
params["email"] = "ada@example.test";
DTree rows = mysql_query(m,
"select id, email from users where email = :email",
params
);
```
The result is returned as a `DTree`, which makes it easy to iterate through rows and read fields with the usual `DTree` accessors.
+21
View File
@@ -7,10 +7,31 @@ return value : a StringMap containing the parameters
:see
>uri
encode_query
request_context_params
:content
Decodes a query-string fragment such as `a=b&c=d` into a `StringMap`.
Parsing rules:
- pairs are separated on `&`
- each pair is split on the first raw `=` only
- both key and value are URL-decoded
- keyless flags such as `preview` are present with an empty string value
- empty pairs, including a trailing `&`, are ignored
- repeated keys use the last value seen
Examples:
```cpp
StringMap q = parse_query("alpha=1&token=a%3Db&preview&empty=");
// q["alpha"] == "1"
// q["token"] == "a=b"
// q["preview"] == ""
// q["empty"] == ""
```
This is useful when you need to work with URL parameter data outside the normal request parsing flow.
Related:
+16
View File
@@ -0,0 +1,16 @@
:title
request_base_url
:sig
String request_base_url(Request& context)
:see
request_script_url
request_query_route
:content
Returns the canonical directory base URL for the current script. This is useful for front-controller apps that generate links to sibling assets or query-routed pages.
```cpp
String base_url = request_base_url(context);
```
+28
View File
@@ -0,0 +1,28 @@
:title
Request Context Params
:sig
SCRIPT_URL, BASE_URL, ROUTE_PATH, ROUTE_PAGE, ROUTE_PATH_RAW, ROUTE_VALID
:see
request_script_url
request_base_url
request_query_path
request_query_route
route_path_sanitize
route_path_is_safe
0_Request
:content
UCE populates several convenience request parameters before invoking page, component, CLI, custom HTTP, or WebSocket handlers:
- `context.params["SCRIPT_URL"]`: canonical script URL, with `/index.uce` collapsed to the containing directory URL
- `context.params["BASE_URL"]`: canonical directory URL for the script
- `context.params["ROUTE_PATH"]`: sanitized first keyless query-string segment, defaulting to `index` when no route was supplied
- `context.params["ROUTE_PAGE"]`: first segment of `ROUTE_PATH`
- `context.params["ROUTE_PATH_RAW"]`: normalized but not trusted route input, for diagnostics only
- `context.params["ROUTE_VALID"]`: `1` when the supplied/defaulted route is safe, `0` when the supplied route was rejected
`ROUTE_PATH` is safe to compose under an application-controlled route root. Unsafe route input such as `..`, `.`, empty interior segments, backslashes, dots in filenames, or other non route-segment characters is rejected by the runtime and yields an empty `ROUTE_PATH` with `ROUTE_VALID=0`.
These are useful for front-controller apps that use URLs like `/?dashboard` or `/?workspace/projects` while still accepting ordinary named query parameters.
+27
View File
@@ -0,0 +1,27 @@
:title
request_query_path
:sig
String request_query_path(Request& context, String default_path = "index")
:see
request_query_route
request_context_params
route_path_sanitize
route_path_is_safe
parse_query
:content
Returns the first keyless query-string segment as a sanitized route path.
For a request such as `/?workspace/projects&theme=dark`, this returns `workspace/projects`.
When no keyless route is supplied, the result is `default_path`. When unsafe route input is supplied, the result is an empty string. Unsafe route input includes `.` or `..` segments, empty interior segments, backslashes, dots in filenames, or any character outside ASCII letters, digits, `_`, and `-`.
```cpp
String route_path = request_query_path(context);
if(route_path == "")
context.set_status(404, "Not Found");
```
The runtime-populated `context.params["ROUTE_PATH"]` uses the same sanitizer.
+29
View File
@@ -0,0 +1,29 @@
:title
request_query_route
:sig
DTree request_query_route(Request& context, String default_path = "index")
:see
request_query_path
request_context_params
route_path_sanitize
request_script_url
:content
Builds a small route tree from the first keyless query-string segment.
Fields:
- `raw_path`: normalized but untrusted route input, for diagnostics only
- `l_path`: sanitized full route path, or empty string when input was rejected
- `page`: first path segment of `l_path`, or empty string when input was rejected
- `valid`: boolean; true when `l_path` is safe
```cpp
DTree route = request_query_route(context);
if(route["valid"].to_bool())
print("route=", route["l_path"].to_string());
```
This supports front-controller apps that use URLs such as `/?dashboard` or `/?workspace/projects` while still allowing ordinary named query parameters alongside the route. The returned `l_path` is already sanitized for composing under an application-controlled route root.
+16
View File
@@ -0,0 +1,16 @@
:title
request_script_url
:sig
String request_script_url(Request& context)
:see
request_base_url
request_query_route
:content
Returns the request script URL from `DOCUMENT_URI` / `SCRIPT_NAME`, canonicalizing a front-controller URL ending in `/index.uce` to the containing directory URL.
```cpp
String script_url = request_script_url(context);
```
+24
View File
@@ -0,0 +1,24 @@
:title
route_path_is_safe
:sig
bool route_path_is_safe(String path)
:see
route_path_sanitize
route_path_normalize
request_query_path
request_query_route
:content
Returns whether a normalized route path is safe to use as a route-derived file path segment.
A safe route path contains only non-empty segments made from ASCII letters, digits, `_`, and `-`. The segments `.` and `..` are rejected.
```cpp
route_path_is_safe("workspace/projects"); // true
route_path_is_safe("workspace/../admin"); // false
route_path_is_safe("view.uce"); // false
```
Most request code should use runtime-populated `context.params["ROUTE_PATH"]` or `request_query_route()` instead of calling this directly.
+20
View File
@@ -0,0 +1,20 @@
:title
route_path_normalize
:sig
String route_path_normalize(String path)
:see
route_path_sanitize
route_path_is_safe
request_query_path
request_query_route
:content
Trims whitespace and removes leading/trailing `/` from a route path without deciding whether the path is safe.
```cpp
route_path_normalize(" /workspace/projects/ "); // "workspace/projects"
```
For route data that may be used to compose file paths, prefer `route_path_sanitize()` or runtime-populated `context.params["ROUTE_PATH"]`.
+34
View File
@@ -0,0 +1,34 @@
:title
route_path_sanitize
:sig
String route_path_sanitize(String path, String default_path = "index")
:see
route_path_is_safe
route_path_normalize
request_query_path
request_query_route
request_context_params
:content
Normalizes and validates a route path for file-backed routing.
Rules:
- leading and trailing `/` are removed
- an empty path becomes `default_path`
- every path segment must be non-empty
- `.` and `..` segments are rejected
- only ASCII letters, digits, `_`, and `-` are accepted inside a segment
- unsafe input returns an empty string
Use this instead of manually checking app routes before composing file paths.
```cpp
route_path_sanitize("/workspace/projects/"); // "workspace/projects"
route_path_sanitize("../secret"); // ""
route_path_sanitize(""); // "index"
```
`request_query_path()`, `request_query_route()`, and the runtime-populated `ROUTE_PATH` params already use this sanitizer.
+19
View File
@@ -0,0 +1,19 @@
:sig
u32 sqlite_affected_rows(SQLite* db)
:params
db : pointer to an active SQLite connection
return value : number of rows changed by the most recent statement
:see
>sqlite
sqlite_query
sqlite_insert_id
:content
Returns the number of rows changed by the most recent insert, update, or delete statement on the connection.
```cpp
sqlite_query(db, "update users set visits = visits + 1 where id = :id", params);
print(sqlite_affected_rows(db));
```
+28
View File
@@ -0,0 +1,28 @@
:sig
SQLite* sqlite_connect(String path)
:params
path : filesystem path to the SQLite database file
return value : pointer to a SQLite connection struct
:see
>sqlite
sqlite_query
sqlite_error
sqlite_disconnect
:content
Opens an SQLite database file and returns a connection handle.
UCE opens SQLite with serialized/full-mutex connection mode, sets a busy timeout, enables foreign keys, and applies WAL-oriented defaults:
```sql
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
```
SQLite itself handles file locking and one-writer/many-reader concurrency. UCE does not add a separate app-level database lock.
Connections are registered for request cleanup, but explicit `sqlite_disconnect()` is still preferred when you are done with the handle.
+14
View File
@@ -0,0 +1,14 @@
:sig
void sqlite_disconnect(SQLite* db)
:params
db : pointer to an active SQLite connection
:see
>sqlite
sqlite_connect
:content
Closes an SQLite connection and deletes the UCE connection wrapper.
UCE also cleans up SQLite connections that remain open at request end, but explicit disconnects keep resource lifetime local and clear.
+15
View File
@@ -0,0 +1,15 @@
:sig
String sqlite_error(SQLite* db)
:params
db : pointer to an SQLite connection
return value : latest connector/SQLite status message
:see
>sqlite
sqlite_query
:content
Returns the latest SQLite connector status or error message.
Successful calls usually set the message to `ok` or `connected`. Failed prepare, bind, step, pragma, or open operations include connector context plus SQLite's own error text.
+19
View File
@@ -0,0 +1,19 @@
:sig
u64 sqlite_insert_id(SQLite* db)
:params
db : pointer to an active SQLite connection
return value : last inserted rowid
:see
>sqlite
sqlite_query
sqlite_affected_rows
:content
Returns SQLite's last inserted rowid for the connection after an insert statement.
```cpp
sqlite_query(db, "insert into notes(body) values(:body)", params);
u64 id = sqlite_insert_id(db);
```
+37
View File
@@ -0,0 +1,37 @@
:sig
DTree sqlite_query(SQLite* db, String q)
DTree sqlite_query(SQLite* db, String q, StringMap params)
:params
db : pointer to an active SQLite connection
q : SQL statement
params : optional named parameter map
return value : list of result rows as a DTree
:see
>sqlite
sqlite_connect
sqlite_error
sqlite_insert_id
sqlite_affected_rows
0_DTree
:content
Executes one SQLite statement and returns result rows as a `DTree` array. Multi-statement SQL strings are rejected so migrations cannot silently run only their first statement.
Use named parameters with `:name` placeholders only. Positional `?` placeholders and SQLite's other named marker forms (`@name`, `$name`) are rejected so UCE SQLite queries use the same placeholder style as the MySQL helper. UCE binds parameters with SQLite prepared statements; it does not substitute values into the SQL string.
```cpp
SQLite* db = sqlite_connect("/tmp/app.sqlite");
StringMap params;
params["email"] = "ada@example.test";
DTree rows = sqlite_query(db,
"select id, email from users where email = :email",
params
);
```
Result rows are objects keyed by column name. SQLite integer, float, text, blob, and null values are converted to DTree values. Blob values are returned as byte strings.
For statements that do not return rows, inspect `sqlite_affected_rows()` or `sqlite_insert_id()` after the call.