Data App runtime reference
@astrato-io/app-runtime is the canonical browser entry point for Data Apps. It bootstraps
the App’s authorized context from the current hosting origin, creates an Astrato SDK client, and
manages metadata access and direct requests to the selected data exit node.
It is required for Data Apps and is not used by externally hosted apps or Workbooks.
Install
Section titled “Install”Install the runtime in your frontend project:
npm install @astrato-io/app-runtimeThe package is ESM-only and browser-only. createAstratoApp() runs at the origin printed by
astrato dev or an enabled hosted publication URL. It fails at a raw Vite origin, during server-side rendering,
or anywhere location.origin and fetch are unavailable.
Create the Data App runtime
Section titled “Create the Data App runtime”import { createAstratoApp } from '@astrato-io/app-runtime';
async function start(): Promise<void> { const app = await createAstratoApp(); // Load field metadata, then create and mount objects.}
start().catch((error) => console.error(error)); // show it in the page; see ErrorsThe factory accepts an optional signal and bootstrap timeoutMs for lifecycle control. Pass them
in the call in start:
async function start(): Promise<void> { const controller = new AbortController(); const app = await createAstratoApp({ signal: controller.signal, timeoutMs: 15_000, });}It does not accept a token, tenant ID, semantic-layer ID, datasource type, API URL or custom transport. The hosting boundary derives and authorizes this context for your configured semantic layer.
It resolves to:
interface AstratoAppRuntime { readonly client: AstratoClient; readonly context: { readonly version: 2; readonly publication: { readonly id: string; readonly version: number }; readonly tenantId: string; readonly semanticLayer: { readonly id: string; readonly datasourceType: string; }; readonly capabilities: readonly AstratoAppCapabilityV1[]; }; readonly dataBinding: { readonly semanticLayerId: string; readonly datasourceType: string; }; destroy(): void;}The context values are available at runtime for SDK and visualization calls, but they are not
authored browser configuration and must not be copied into source files. Only the semantic-layer ID
is selected in astrato.json; the CLI resolves its tenant and datasource type through Astrato. version: 2 identifies the hosted
bootstrap contract, not a semantic-layer revision.
Create one app runtime per browser application and retain it for the page lifetime. Call
app.destroy() when the application is permanently torn down. Destruction is idempotent and
releases the runtime transport and SDK client.
Use the SDK client
Section titled “Use the SDK client”app.client is already configured with the runtime transport and visualization engine. Do not call
createAstratoClient() again.
Share this client across charts. The SDK shares matching visualization metadata loads and reuses
completed snapshots. Query results remain live. If your app also declares @astrato-io/sdk
directly, keep it compatible with the SDK dependency declared by your installed runtime and
update both dependencies and the lockfile together. Preview bootstrap and the runtime must
resolve to the same SDK module to share their iframe connection. See
metadata reuse in the SDK reference.
App Runtime 0.4.0 requires SDK ^0.6.0, including support for visualization fields from custom
SQL views and tables without a physical schema. Runtime 0.3.1 requires SDK ^0.5.0; update
both packages together when moving an app to SDK 0.6.0.
import type { AstratoAppRuntime } from '@astrato-io/app-runtime';
async function loadFieldMetadata(app: AstratoAppRuntime) { const getFieldMetadata = app.client.services.engine.fields.getFieldMetadata; if (!getFieldMetadata) { throw new Error('Astrato field metadata is unavailable.'); }
const metadata = await getFieldMetadata({ datasourceType: app.dataBinding.datasourceType, semanticLayerId: app.dataBinding.semanticLayerId, }); return metadata;}Call it from start with app, and await it before mounting native visualizations. Metadata
collections are optional; do not assume metadata.fields exists. Pass the returned metadata to
createVisualizationAvailableFields when compiling chart definitions.
The rest of the SDK surface is documented in the SDK reference. When it shows a configured
client, use app.client. When it requires semantic-layer or datasource context, use the current
values from app.dataBinding or app.context; never replace them with hard-coded values.
Builder preview connection
Section titled “Builder preview connection”The Builder preview integration uses the iframe connection introduced in SDK 0.5.0. SDK 0.4.0 does not provide this integration.
In a compatible Builder, trusted bootstrap establishes the connection before your app starts.
Use the same createAstratoApp() call and app.client as in local development and hosted apps.
Do not add a preview-specific data helper, credentials, platform IDs or iframe detection to app
source. Being inside an iframe alone does not select preview mode.
The preview context uses the existing version 1 context shape and has no publication identifier.
It binds one semantic layer and exposes read-only metadata and queries. Procedure and writeback
methods reject before transport. runtime: 'preview' explicitly requires an established connection;
a missing connection fails without trying a different environment. An established preview also
rejects an explicit request for local or publication mode.
The platform must provide a compatible, frozen SDK and runtime pair. Existing apps do not automatically upgrade when a new runtime is released.
Local write methods
Section titled “Local write methods”app.executeProcedure and app.writeback need @astrato-io/app-runtime 0.2.0 and @astrato-io/cli
0.1.1 or later, and work only during astrato dev. Published Apps reject both. They run under your
Astrato permissions, OAuth scopes and warehouse permissions; astrato.json lists no procedures or
views.
Call a procedure on the connected datasource without supplying a datasource ID:
async function saveStrategy(app: AstratoAppRuntime): Promise<void> { const result = await app.executeProcedure({ schema: 'DEMO.PUBLIC', procedure: 'SP_SAVE_STRATEGY', params: [JSON.stringify({ allocations: [{ symbol: 'EXAMPLE', target: 100 }] })], }); console.log(result.response.data);}This example assumes you have a SP_SAVE_STRATEGY procedure accepting that JSON string.
Parameters support strings, finite numbers, booleans and null. Serialize object parameters yourself.
The procedure and engine own validation and execution; the runtime does not generate SQL.
Insert rows into a writable model view:
async function insertTrade(app: AstratoAppRuntime): Promise<void> { const result = await app.writeback({ view: 'Trades', operation: 'insert', fields: [{ name: 'trade_id', valueType: 'string' }], values: [['example-trade']], }); console.log(result.response.rowsAffected);}The example requires a writable Trades view with a trade_id field. Updates also require
keys: { fields, values }, with one key row for every values row. Deletes require keys and omit
fields and values. Fields use string, number, date or bool; values use the same scalar
parameter types as procedures. This preview accepts flat field lists only.
Both methods accept an optional second argument { signal } and an optional queryTimeout in the
request, from 1 to 60 seconds. The runtime binds row writes to the current layer and procedures to
its datasource. It returns procedure data and write counts while omitting warehouse links and query IDs; the
engine SQL statement is not passed to the App (see
Showing the SQL behind a visualization).
Row results retain failure counts. Procedure and per-row errors use generic text so warehouse
diagnostics are not exposed to the App.
Writes are not automatically replayed after an execution failure, timeout or lost connection. Cancellation and timeout do not prove that a warehouse change was rolled back. Check its outcome before retrying. Refresh affected queries after a confirmed change; writes do not refresh charts.
Render a standalone visualization
Section titled “Render a standalone visualization”import { createAstratoApp } from '@astrato-io/app-runtime';import { createVisualizationRuntime, registry } from '@astrato-io/visualizations';
async function start(): Promise<void> { const app = await createAstratoApp(); const visualizations = createVisualizationRuntime({ client: app.client, registry });
visualizations.createInstance({ id: 'sales-chart', type: 'bar', vSpec, dataBinding: { ...app.dataBinding }, });}
start().catch((error) => console.error(error)); // show it in the page; see ErrorsBuild vSpec from a visualization definition and live field metadata as described in the
Visualizations reference. Do not construct visualization field keys from names.
Capabilities
Section titled “Capabilities”app.context.capabilities reports the operations the App may request through its Data App runtime:
semantic-layer:read;datasource-metadata:read;query:execute;procedure:execute(local runtime, when enabled);data:writeback(local runtime, when enabled);
See Writeback and stored procedures for transport limits.
The list describes the App boundary, not a promise that every signed-in account can perform every operation. Astrato also checks account permissions, OAuth scopes and data access on every remote request. Authored code cannot expand the list, and the runtime ignores capability names it does not recognise.
Transport and session behavior
Section titled “Transport and session behavior”Published apps load their authorized model, obtain a short-lived data token and query the selected data exit node directly from the browser. The publication host exposes no generic message API or query proxy. Rebuild older hosted bundles with the current package.
Astrato governs data-token permissions, including writeback and export; the SDK does not determine them. Main Astrato OAuth credentials and refresh tokens remain on the server. Data tokens remain in memory, and requests to the data node omit browser cookies and reject redirects.
The data connection must select a supported non-legacy exit node. Publication hosting can differ from data placement, including another region or private infrastructure. The browser must be able to reach the selected data node. A node failure is reported without trying another execution path.
Local loopback origins use the local development adapter automatically. For another local host,
use createAstratoApp({ runtime: 'local' }). The local adapter uses same-origin requests and is
unavailable on hosted publications. Local development queries
the current model without revision pinning, so reload after model edits.
Writeback and stored procedures
Section titled “Writeback and stored procedures”During astrato dev, the local App runtime provides app.executeProcedure() and
app.writeback() through the CLI development gateway. They require the corresponding
procedure:execute or data:writeback capability, resource permissions and a supported
datasource. These methods are unavailable through the hosted App publication transport;
SQL SELECT requests are also unavailable through the CLI development gateway.
For an externally hosted app with an authenticated platform transport, use the generic SDK requests in Writeback and SQL. Those examples require that platform connection. OAuth scopes authorize the corresponding platform APIs, subject to resource permissions; they do not add routes or capabilities to an App transport.
Errors
Section titled “Errors”Data connection sign-in
Section titled “Data connection sign-in”When a published App cannot obtain data access because your data connection needs OAuth authorization, the runtime displays a centered recovery dialog over a blurred backdrop. Choose Sign in to data connection to open Astrato’s sign-in flow in a new tab. Complete authorization there, return to the App, and refresh your browser tab. The runtime never opens the sign-in tab or reloads automatically, and does not automatically replay the failed request.
Choose Continue without data or press Escape to dismiss the dialog; charts using the connection may remain unavailable. Concurrent failures with the same recovery details share one dialog, including after it is dismissed.
If the connection needs administrator configuration, the prompt asks you to contact the App owner or your Astrato administrator instead. Ordinary permission errors and exit-node failures do not produce a datasource sign-in prompt.
If the authorization response has no recovery decision, the prompt explains that no sign-in link was provided. This does not establish that administrator configuration is required.
For a recognized mint failure, error.response?.error.code is
datasource_authorization_required. Its optional details contains provider, reason
(missing_grant, expired_grant, or insufficient_scope) and the validated recovery
decision. A sign-in recovery includes connectionId and authorizationUrl; a configuration
recovery has status configuration-required. Credentials are never included. These details
describe connection authorization, separately from your App viewer session.
Existing published builds need to be rebuilt with the updated runtime and published again to display this prompt.
Runtime errors
Section titled “Runtime errors”import { AstratoAppRuntimeError, createAstratoApp } from '@astrato-io/app-runtime';
async function start(): Promise<void> { try { const app = await createAstratoApp(); } catch (error) { if (error instanceof AstratoAppRuntimeError) { console.error(error.code, error.status, error.response?.error.requestId); } else { throw error; } }}
start().catch((error) => console.error(error));Runtime error codes:
| Code | Meaning |
|---|---|
unsupported_environment |
Required browser origin or fetch is unavailable |
bootstrap_failed |
The bootstrap request failed |
invalid_bootstrap |
The boundary returned an invalid versioned context |
runtime_context_changed |
The publication or refreshed session’s data context changed |
runtime_request_failed |
An SDK request failed; inspect status and the structured response |
timed_out |
The bootstrap request exceeded its configured timeout |
aborted |
Runtime work was cancelled |
When Astrato returns a structured response, error.response.error contains code, message,
requestId and retryable. Display the message and retain the request ID for
support. Do not automatically retry non-retryable errors or invent alternate API routes.
Hosted app sessions
Section titled “Hosted app sessions”Hosted apps built with @astrato-io/app-runtime 0.2.1 or later tolerate a device clock up to five
seconds behind Astrato. When a data grant appears too far in the future, expired or too close to expiry, the runtime reports a
runtime_request_failed error with guidance to check automatic date and time synchronization,
reload, and contact support if the problem persists. This does not establish that the device clock
is the cause of every expiry error. Larger clock changes during a session still require resynchronizing
the device and reloading.
Existing app bundles need a runtime dependency update, rebuild and republish to get this behavior. Builder projects also need their saved runtime version updated.
When publication hosting is enabled for your workspace, open the app’s assigned URL and sign in
with Astrato. The app uses the same createAstratoApp() entry point as local development. Your
publication and semantic-layer permissions still apply. Hosted browser-IP restrictions are not
enforced in the current preview.
The app receives an independent session; your Astrato credentials remain on the server.
Hosted sessions currently last at most five minutes. If the session expires, reopen or reload the app to sign in again. Replacing a publication also requires a reload. An unpublished app cannot serve its files, including files previously cached by your browser.
Reload after an app changes
Section titled “Reload after an app changes”The hosted runtime automatically reloads the current page when a request reports
409 app_changed. This loads the updated application and establishes its fresh session. No
reload hook is required for this default behavior. Automatic reload can discard unsaved input;
register onPublicationChanged when your app needs to preserve state or ask first.
Availability: use a runtime build that exposes CreateAstratoAppOptions.onPublicationChanged.
Published runtime versions 0.2.1 and earlier do not provide this hook or automatic recovery.
Existing apps need an updated runtime dependency, rebuild and republish. Check the installed package types before generating
code that uses the hook. Do not silence a missing property with a type assertion.
The failed operation still rejects with runtime_context_changed. The runtime stops issuing
requests and serving cached metadata, and later operations retain that error. Reload detection
happens on a runtime request or data-token renewal; there is no push notification to idle apps.
This behavior applies to hosted publications, including a failure during initial bootstrap.
The local development adapter (runtime: 'local'), Builder previews and SDK-only apps do not
invoke this hook.
onPublicationChanged
Section titled “onPublicationChanged”Register this optional callback when calling createAstratoApp(), before the first request.
It is called once per runtime that detects a publication change and is awaited before automatic
navigation. It receives an AstratoAppPublicationChange:
| Field | Meaning |
|---|---|
error |
The AstratoAppRuntimeError with code runtime_context_changed. A server error includes its status and structured response/request ID. |
automaticReloadBlocked |
Whether the loop guard currently blocks automatic reload, including when session storage cannot be read. Storage write failures can also block reload after the callback. |
reload() |
Explicitly reload the current page. Retain this function if you return false, and call it after the user agrees or state is saved. Repeated calls on the same event navigate only once. |
| Callback result | Runtime behavior |
|---|---|
undefined / no return, or true |
Reload automatically after the callback completes, subject to the loop guard. |
false |
Stay on the page. Your app owns the recovery UI and can call reload() later. |
| A promise | Wait for its result with the same rules. There is no timeout that forces navigation while saving or asking the user. |
| Throw or rejected promise | Stay on the page and show the built-in manual reload prompt. Failed persistence does not trigger automatic navigation. |
Requests reject promptly while the callback is pending. Do not wait for requests on the failed
runtime, reinitialize it in a retry loop, or retry the failed operation to recover. Save local app
state and reload instead. The callback must not rely on createAstratoApp() having resolved:
bootstrap itself can detect the change. App teardown does not cancel a pending recovery callback.
Save and restore session state. This example expects a <textarea id="notes"> in the page.
Chart setup is omitted; keep the returned runtime for your normal app lifecycle.
Browser storage can be unavailable, for example when browser settings block it, and reading it then
throws. Wrap every storage access in try/catch, and keep the app working without saved state.
The App Builder preview has no storage; see Constraints on app code.
import { AstratoAppRuntimeError, createAstratoApp } from '@astrato-io/app-runtime';
const notes = document.querySelector<HTMLTextAreaElement>('#notes');if (!notes) throw new Error('The notes field is missing.');const draftKey = 'example-app:notes:v1';let saved: string | null = null;try { saved = sessionStorage.getItem(draftKey);} catch { // Continue without a restored draft when storage is unavailable.}if (saved !== null) notes.value = saved;
const start = async (): Promise<void> => { const app = await createAstratoApp({ onPublicationChanged() { // No return: reload after saving. If storage is unavailable this throws, and the runtime // keeps the page and shows its manual reload prompt, so the notes are not lost. sessionStorage.setItem(draftKey, notes.value); }, }); // Load metadata and mount objects as usual.};
start().catch((error) => { // Recovery is already in progress or deferred: keep its UI. if (error instanceof AstratoAppRuntimeError && error.code === 'runtime_context_changed') return; console.error(error); // show other failures in the page; see Errors});For asynchronous persistence, make the callback async and await your save operation before
returning. Store app-owned state only; never copy runtime credentials or data tokens into storage.
Use a namespaced, versioned key and validate restored structured data against your app’s schema.
Ask the user and defer navigation. This example adds an app-owned recovery message and button.
It works even if the change occurs before bootstrap completes. You can replace the DOM controls
with your own dialog; retain reload and return false immediately if the dialog handles its
answer later.
import { AstratoAppRuntimeError, createAstratoApp } from '@astrato-io/app-runtime';
async function start(): Promise<void> { const app = await createAstratoApp({ onPublicationChanged({ automaticReloadBlocked, reload }) { const notice = document.createElement('section'); notice.setAttribute('role', 'status'); const message = document.createElement('p'); message.textContent = automaticReloadBlocked ? 'The app still needs an update after a recent reload. Copy your work before trying again.' : 'An updated app is available. Copy your unsaved work before continuing.'; const button = document.createElement('button'); button.type = 'button'; button.textContent = 'Reload when ready'; button.addEventListener('click', reload, { once: true }); notice.append(message, button); document.documentElement.append(notice); return false; }, }); // Load metadata and mount objects as usual.}
start().catch((error) => { // Recovery is already in progress or deferred: keep its UI. if (error instanceof AstratoAppRuntimeError && error.code === 'runtime_context_changed') return; console.error(error); // show other failures in the page; see Errors});In both examples, the startup error handling recognizes AstratoAppRuntimeError with code
runtime_context_changed: recovery is already in progress or has been deferred. Keep the recovery
UI mounted and do not replace it with a generic sign-in failure. Handle other initialization errors
normally.
Reload loop prevention
Section titled “Reload loop prevention”The runtime records each reload attempt in session storage for the current publication origin
and browser tab. It permits at most one automatic reload in 60 seconds, does not reset the guard
on successful bootstrap, and never schedules a delayed retry. A subsequent change after the
window can trigger a new attempt. If the guard blocks, or storage cannot retain it, the default
fallback is This application has changed with a Reload application button. The prompt
survives app teardown. A callback returning false supplies its own UI instead.
The callback’s reload() and the fallback button are explicit actions which can bypass the guard;
they also record an attempt to guard the next page. Do not call reload() unconditionally from the
callback: that defeats loop prevention. Return normally for the guarded automatic path, or call
reload() after a deliberate user decision. Do not edit the runtime’s storage marker.
Other failures do not invoke the hook or trigger automatic reload:
| Error | Recovery |
|---|---|
409 app_unavailable |
Ask the app author to republish against the current semantic layer. |
401 unauthenticated |
Reopen the app and sign in. |
403 forbidden |
Ask the owner to check your publication and data permissions. |
404 not_found |
The publication or its data is no longer available; contact its owner. |
409 publication_conflict |
Contact the app author; the service could not classify the conflict. |
Datasource and hosting-location failures also keep their own structured error codes and recovery messages. Retain the request ID when reporting a failure.
A hosted publication binds to the model revision selected when it was published. Model reads and token issuance return
409 app_unavailable if that revision is no longer the current published model or the engine model
has changed. Publish the app again against the current model. Historical model execution is not
available. This restriction differs from local development, which uses the current engine model.
Publication access is rechecked when issuing or renewing a data token. Already-issued data tokens retain their centrally defined expiry, up to five minutes; unpublishing does not retroactively cancel a token already held by a browser.