Vaktor v0.1

Data binding

An extension for pages that render external/dynamic data declaratively, without domain logic on the client. By default a service is server-driven (the endpoint returns a ready contract). Binding is needed when the source is third-party JSON and you have no proxy of your own.

Source

The page declares a URL that is fetched (GET) before rendering; the response is available in interpolation as data:

"page": {
  "source": { "url": "https://{settings.domain}/api/v2/summary.json" },
  "items": [  ]
}

source can be an array of steps — a chain. The next step’s URL may reference the previous step’s data (data.<as>):

"source": [
  {
    "as": "geo",
    "url": "…/search?name={settings.city}&count=1"
  },
  {
    "as": "weather",
    "url": "…/forecast?latitude={data.geo.results.0.latitude}&longitude={data.geo.results.0.longitude}&current=temperature_2m"
  }
]

Interpolation {path | transform:arg | …}

Works in any string field. Paths are dot-separated, with indices: {data.status.description}, {data.components.0.name}. Unresolved → empty string.

Scopes:

ScopeWhat
datathe source body (in a chain — data.<as>.…)
settingspersisted fields (persist: true): {settings.domain}
contextthe current item inside a list section

List section (template + data)

data — a path to an array (or an inline array). For each item (with an optional where filter) template is rendered; the item is substituted as {context.*}:

{
  "type": "section",
  "header": "Components",
  "data": "data.components",
  "where": {
    "group": false
  },
  "template": {
    "type": "row",
    "title": "{context.name}",
    "status": "{context.status | map:compRole}",
    "wrap": true
  }
}

zip — stitching parallel columns

For APIs that return columns rather than an array of objects. The result length = the shortest column; each object is available as {context.<key>}:

{
  "type": "section",
  "header": "7-day forecast",
  "data": {
    "zip": {
      "day": "data.daily.time",
      "max": "data.daily.temperature_2m_max"
    }
  },
  "template": {
    "type": "row",
    "title": "{context.day | date:EEEE}",
    "value": "{context.max | round:0}°"
  }
}

Transforms (pipeline via |)

transformresult
date / date:short/medium/longISO8601 (and local forms) → localized date+time
date:<pattern>explicit Unicode pattern (yyyy-MM-dd, HH:mm, dd MMM yyyy HH:mm)
map:<table>via a table from page.maps (key _default — fallback)
numbergrouping by locale
round:<N>N digits after the decimal point
percent[:N]fraction (0..1) → “12%“
bytesbytes → KB/MB/…
default:<x>empty/nil → x

map tables

"maps": {
  "compRole": {
    "operational": "success",
    "major_outage": "error",
    "_default": "neutral"
  }
}

Security

source is fetched strictly from the manifest origin (same-origin, with authorization). An absolute cross-origin URL is rejected — no exceptions. If you need third-party data, proxy it through your own origin.