Data Binding¶
Component instances declare named data inputs; bindings say where each input's value comes from. The component's manifest declares which inputs exist and which are required.
"data": {
"primary": {
"source": "sales-data",
"operation": "totalRevenue",
"parameters": { "customerId": "${context.customerId}" },
"transform": "${result.total}",
"default": 0
}
}
Resolution tiers¶
static—parameters.valueis the value. Synchronous.state— readsparameters.pathfrom the page state store. Synchronous, and re-renders reflect state changes.- A registered connector — any other
sourcematching aDataSourceConnector's id resolves asynchronously (see below). - No connector — falls back to the binding's
default. Pages always render, even with zero data infrastructure.
Connectors¶
A connector is the entire host-side contract:
interface DataSourceConnector {
id: string
execute(operation: string, params: Record<string, unknown>): Promise<unknown>
}
Pass connectors to the renderer — <uiforge-page .dataSources=${[connector]}> (Lit) or <PageRenderer dataSources={[connector]}> (React). Binding parameters are ${...}-evaluated against {context, state} before execute; a transform expression post-processes the result with ${result...} available.
Loading, error, and caching semantics¶
While a connector binding is in flight the component renders a data-uiforge-loading="<input>" marker; on rejection, data-uiforge-data-error="<input>" with the failure message; on success, the value. Results are cached per component + input — interaction-driven re-renders do not re-fetch.
To re-fetch declaratively, target the component with the component.refresh action:
{
"when": { "component": "refresh-button", "event": "click" },
"then": [{ "target": "orders-table", "action": "component.refresh" }]
}
This invalidates the target's cache; its bindings re-resolve on the next render. The demo (examples/demo/) shows the full loop with a deliberately slow connector.