fix: harden UCE runtime and starter
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
Output / Invocation Functions
|
||||
|
||||
coming_from_react
|
||||
1_RENDER
|
||||
1_CLI
|
||||
cli_input
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
SQLite
|
||||
|
||||
sqlite_connect
|
||||
sqlite_disconnect
|
||||
sqlite_error
|
||||
sqlite_query
|
||||
sqlite_insert_id
|
||||
sqlite_affected_rows
|
||||
@@ -7,6 +7,12 @@ filter
|
||||
first
|
||||
join
|
||||
json_encode
|
||||
list_every
|
||||
list_find
|
||||
map
|
||||
list_some
|
||||
list_sort
|
||||
list_unique
|
||||
nibble
|
||||
print
|
||||
strpos
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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"); });
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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)); });
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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()`
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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"]`.
|
||||
@@ -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.
|
||||
@@ -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));
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user