Docs/Live data in visuals and HTML artifacts
Live data in visuals and HTML artifacts
Chat visuals and HTML artifacts read workspace databases live through named, approved, read-only queries (astral.query). No tokens in the frame, no snapshots.
A chat visual (a visual fence in your reply) and an HTML artifact (artifact_exec create kind=html, or library.publish_app) can read a workspace database live. The data is never baked into the HTML and never cached: every call reads the database again.
How it works#
- You declare named read-only queries in the HTML, in one
<script type="application/astral+json">element. - A workspace member approves them once, on a card that the page draws above the frame. The card shows each query's SQL, params and database. The approver must be able to read every named database.
- The HTML calls
astral.query(name, params). The page runs the query as the person who looks at it, with that person's own database access.
The approval covers the exact HTML. Any edit (of the page or of a query) needs a new approval.
Declare queries#
<script type="application/astral+json">{"queries":{"orders_by_day":{ "database":"<database id from databases.list>", "sql":"select day, count(*) as n from orders where day > current_date - $1::int group by day order by day", "params":[{"name":"days","type":"integer"}], "max_rows":200, "description":"Orders per day"}}}</script>- One SELECT (or WITH … SELECT) per query. At most 20 queries.
- Params bind to
$1,$2, … in the order you list them. Types: text, integer, number, boolean, date, timestamp, uuid."required": falsemakes a param optional (null). max_rows1–1000 (default 500).- The database must be an Astral-hosted database of this workspace.
Call them#
const { columns, rows, truncated } = await astral.query("orders_by_day", { days: 30 })On failure the promise rejects with an Error that has err.code:
| code | meaning |
|---|---|
| approval_required | No member approved this exact HTML yet. Show a short note; the frame reloads after approval. |
| query_not_declared | The name is not in the declaration. |
| invalid_input | A param is missing, unknown or has the wrong type. |
| not_found | The viewer can not read the database. |
| rate_limited | Too many queries (60 a minute per viewer and source). |
| query_error | The SQL failed. |
Live updates#
const stop = astral.subscribe("orders_by_day", { days: 30 }, (result, err) => { if (err) return showError(err.code) render(result.rows)}, { interval: 30 })Astral runs the query at once, then again every interval seconds (10 to 3600, default 30) while the page is visible. Each run is a normal live query: the same approval, rate limit and Event Log row. A hidden page pauses; showing it runs each subscription at once. stop() ends it, and every subscription ends when the visual or artifact closes. Never poll with setInterval.
Call tools#
List catalog tools (app.tool) in the same declaration, and call them with astral.call:
<script type="application/astral+json">{"queries":{}, "tools":["sites.list","databases.run_sql","gmail.send_email"]}</script>const sites = await astral.call("sites.list", {})await astral.call("databases.run_sql", { id: "<database id>", sql: "insert into notes (text) values ('hi')" })- The tool runs as the person who looks at the page, with that person's own access and connections.
- A read (an Astral tool that only reads) runs at once.
- Any call that can change data (an Astral write, or any connected-app tool such as Gmail) first shows the viewer a card above the frame, with the tool and its exact input. It runs only when the person clicks "Run it". The frame can not answer the card. If the person declines, the call rejects with
err.codedenied. - Guests can not run calls that change data.
- Never available: billing, credentials, agents, approvals, docs, domains, marketplace, routines, skills, sidebar, workspaces, computers and sandboxes, and outbound calls or messages.
- More error codes:
tool_not_declared,not_found(not available here),permission_denied.
Limits and rules#
- Read-only transaction, 5 s statement timeout, row cap, 2 MB result cap.
- The frame has no network and can not navigate. It holds no token or cookie.
- Not available in inbox chats (outsiders can write there) or in a person's own messages.
- An app that declares queries does not get connector toolkit calls.
- Every call is in the Event Log:
astral_bridge.query_ran,astral_bridge.query_denied,astral_bridge.approved,astral_bridge.revoked.
Coming next#
astral.call (Astral tools and connections; writes behind an approval card) and public links.