@astrato-io/sdk
v0.6.0
Published
Browser SDK for connecting custom applications to Astrato APIs and runtime services.
Readme
@astrato-io/sdk
Browser SDK for connecting custom applications to Astrato APIs and runtime services.
Use this package for authentication, Astrato API requests, data and metadata access, events, variables, and group-scoped selection state. Visualization rendering is provided separately by @astrato-io/visualizations.
Installation
npm install @astrato-io/sdkThe package is distributed as ESM and requires a browser environment with fetch, Headers, and URL. RxJS is installed as a package dependency and is used by the public observable APIs.
Create A Client
import { createAstratoClient } from '@astrato-io/sdk';
const client = createAstratoClient({
baseUrl: 'https://your-astrato-instance.example/',
tenantId: 'tenant-id',
auth: {
token: 'personal-access-token',
},
visualizationEngine: true,
});baseUrl is required. tenantId is used as the default tenant for data and metadata requests.
Authentication can use a fixed token, an asynchronous token provider, or custom headers:
const client = createAstratoClient({
baseUrl,
tenantId,
auth: {
getAccessToken: async () => refreshAccessToken(),
headers: {
'x-client-name': 'customer-portal',
},
},
});When a token is available, the SDK sends it as Authorization: Bearer <token> unless an authorization header was supplied explicitly.
Embedded iframe bridge
An application embedded in a trusted host can use the same SDK client without receiving host credentials. The host establishes a versioned MessagePort connection with one iframe and handles requests through an allowlisted callback. This is an explicit bootstrap: merely being inside an iframe never enables the bridge.
In the host, create the bridge before the iframe application starts. The request callback must validate the relative SDK path, HTTP method, tenant, semantic layer, and operation against the host's own authorization rules, then execute the approved operation with credentials retained in the host. Do not forward child-supplied headers to a privileged network client without a deliberate allowlist. If the bridge is attached after the iframe has already loaded, pass initialLoadComplete: true; this lets the bridge distinguish the first load from a later navigation.
import { createAstratoIframeHostBridge } from '@astrato-io/sdk';
const bridge = createAstratoIframeHostBridge({
iframe,
childOrigin: 'null', // for an intentionally opaque sandbox; otherwise use the exact child origin
context: {
tenantId,
semanticLayer: { id: semanticLayerId, datasourceType },
capabilities: ['query', 'metadata'],
},
request: (request, signal) => executeAllowedSdkRequest(request, signal),
});
// Destroy when the iframe is removed or its authority changes.
bridge.destroy();The iframe's trusted application bootstrap supplies the exact parent origin and connects once. After that, shared application runtime code can discover the established connection synchronously and create the ordinary visualization-enabled client:
import { connectAstratoIframeBridge, getAstratoIframeBridge } from '@astrato-io/sdk';
await connectAstratoIframeBridge({ parentOrigin: trustedParentOrigin });
const connection = getAstratoIframeBridge();
if (!connection) throw new Error('Astrato iframe bridge is unavailable.');
const client = connection.createClient();createClient() never falls back to fetch. The bridge rejects absolute URLs and credential-bearing headers, bounds request and response sizes, and supports request aborts, timeouts, and disconnect cleanup. Host requests run up to maxConcurrentRequests at once (16 by default); excess requests wait in arrival order, with at most 32 active and queued requests combined. The request timeout includes time spent waiting. An aborted or timed-out active request keeps its concurrency slot until the host callback settles, so the host limit remains accurate even if that callback ignores abort. Host errors are returned as generic codes so private details are not sent to the iframe. The host callback remains the authorization boundary; the context's capabilities list is descriptive. The protocol version is exported as ASTRATO_IFRAME_BRIDGE_PROTOCOL_VERSION and incompatible versions do not connect. Destroying or replacing a connection destroys its SDK clients and invalidates their metadata caches.
Data And Metadata
The direct data service uses the current Astrato data and schema APIs. Query payloads are passed through without hidden filter, variable, or selection changes.
Semantic layer metadata is loaded from the tenant-scoped data view composite endpoint:
/api/message/tenants/{tenantId}/schema/dataviews/{semanticLayerId}/composite. This response provides engine field keys, semantic item keys and view connectivity without reconstructing identity from schema, view or field names.
const metadata = await client.services.data.getSemanticLayerMetadata?.('semantic-layer-id');
const result = await client.hostServices.engine.getData({
semanticLayerId: 'semantic-layer-id',
query: {
dimensions: [],
measures: [],
},
});When the visualization engine executes a query, it retains the latest successful query metadata for each visualization object ID. Use that metadata to show the executed SQL, duration, result size, vendor query link, or real warehouse query insights without issuing the data query again:
const metadata = client.hostServices.engine.getLastQueryMetadata?.('sales-by-manufacturer');
const insights =
metadata?.semanticLayerId && metadata.startTime !== undefined
? await client.hostServices.engine.getQueryInsights?.({
queryID: metadata.queryID,
semanticLayerId: metadata.semanticLayerId,
startTime: metadata.startTime,
})
: undefined;Query metadata is isolated by the request context's objectId. If multiple requests for one object overlap, only the
newest request may update its metadata. Query-insights availability and response details depend on the connected warehouse.
The visualization engine maps semanticLayerId to the current engine API's
legacy databaseID wire field. The low-level client.services.data.query()
method remains transparent and posts its query value without rewriting it.
Request context can be configured once on the client and overridden per query:
const client = createAstratoClient({
baseUrl,
tenantId,
auth: { token },
context: {
datasourceType: 'snowflake',
sheetId: 'sheet-id',
workbookState: 'draft',
workbookVersionContainerId: 'workbook-version-container-id',
},
});Authenticated Requests And Customization
Use client.request() for Astrato endpoints that do not have a dedicated service method. The request uses the configured base URL, authentication, transport events, and error logging:
const response = await client.request({
method: 'GET',
url: 'api/example',
});createAstratoClient() also accepts a custom transport, logger, event bus, data endpoint overrides, and host-service implementations. These options allow applications to integrate their own networking, diagnostics, or platform services without changing visualization code.
Visualization Engine
Current Astrato visualization wrappers require a small synchronous engine surface. Enable the SDK's visualization engine explicitly when the client is used with @astrato-io/visualizations:
const client = createAstratoClient({
baseUrl,
tenantId,
auth: { token },
visualizationEngine: true,
});
await client.hostServices.engine.fields.getFieldMetadata?.({
semanticLayerId: 'semantic-layer-id',
tenantId,
});Metadata snapshots are reused within one client for five minutes, with at most 16 completed
snapshots retained. Pending requests for the same tenant, semantic layer, datasource, and
authentication authority share one load. Configure metadataCache on createAstratoClient()
when a different bound is appropriate. Call client.hostServices.engine.fields.invalidateMetadata()
after a model change; pass the same metadata request to invalidate one layer, or omit it to clear
all snapshots. refreshMetadata(request) clears and reloads one layer. A page reload creates a
new client and therefore starts with an empty cache.
The default engine resolves authentication once per metadata operation or query and binds its
requests to the same headers. Concurrent operations share pending authentication resolution.
Callbacks that return a new token on each invocation are supported. When any SDK request observes
changed authentication headers, the default engine clears its cached and active metadata. Load
metadata again before reading synchronous field or connectivity APIs. Queries resolve authentication
before transformation so they cannot substitute fields from the previous authority's metadata.
A metadata operation or engine query rejects if a subsequent SDK operation observes changed
authentication before it completes; rejected query results do not update query metadata. An older
authentication lookup that finishes after a newer successful lookup rejects if its credentials differ from
the current ones. Ordinary requests continue resolving their own authentication.
Applications that provide their own transport authority without SDK authentication must call
invalidateMetadata() when that authority changes. Query results are never cached.
Destroying a client invalidates metadata only in an engine created by that client. An engine supplied
through hostServices.engine remains owned by its caller. Grouped-dimension choices survive cache
hits; a fresh metadata load initializes them from the model again.
The asynchronous metadata call loads the engine snapshot. Field getters and connectivity checks then read that snapshot synchronously, as required by the visualization wrappers. Fields are identified by engine fieldKey values; the SDK does not parse field keys into view/name references.
Semantic dimensions, measures and calculations use the keys and related views supplied by the composite response. View names are retained only where existing visualization APIs still request view-level connectivity.
Projected visualization fields temporarily include schema: 'legacy' because visualization packages
still require a nonempty value. This compatibility placeholder is independent of the source database
schema and overrides any incoming field schema; it does not change canonical engine keys or source
metadata. Consumers should use field keys for identity. Removal of this requirement and placeholder is
tracked in ASTA-15281.
Using Visualizations With The SDK
Install both packages and import the visualization stylesheet once in the host application:
npm install @astrato-io/sdk @astrato-io/visualizationsimport '@astrato-io/visualizations/styles.css';Create one SDK client and one visualization runtime for the application. The visualization engine must be enabled so the runtime can load data and semantic-layer metadata through the SDK:
import { createAstratoClient } from '@astrato-io/sdk';
import { createVisualizationRuntime, registerAstratoElements, registry } from '@astrato-io/visualizations';
const tenantId = 'tenant-id';
const semanticLayerId = 'semantic-layer-id';
const client = createAstratoClient({
baseUrl: 'https://your-astrato-instance.example/',
tenantId,
auth: { token: 'personal-access-token' },
visualizationEngine: true,
});
const runtime = createVisualizationRuntime({
client,
registry,
});
runtime.createInstance({
id: 'sales-by-manufacturer',
type: 'bar',
groupId: 'dashboard',
dataBinding: {
tenantId,
semanticLayerId,
datasourceType: 'snowflake',
},
});
registerAstratoElements({ runtime });Render the runtime-owned instance with the framework-neutral web component:
<astrato-visualization instance-id="sales-by-manufacturer"></astrato-visualization>The element must have non-zero dimensions:
astrato-visualization {
display: block;
width: 100%;
height: 400px;
}If vSpec is omitted, the runtime creates an empty default visualization. To create a useful initial visualization, use compileVisualizationDefinition() from @astrato-io/visualizations/definitions with semantic-layer metadata loaded through client.hostServices.engine.fields.getFieldMetadata(). Pass the compiled vSpec to runtime.createInstance(). Definitions use exact engine keys and return structured validation issues when compilation fails.
Applications that manage DOM lifecycle directly can mount without a web component:
const container = document.getElementById('chart');
if (!container) {
throw new Error('Missing chart container.');
}
const handle = await runtime.mount(container, 'sales-by-manufacturer');
const renderResult = await handle.ready;
console.log(renderResult.status);
handle.unmount();
runtime.removeInstance('sales-by-manufacturer');Instances with the same groupId share selection state. Removing a web component unmounts it automatically; remove runtime instances when they are no longer needed and call client.destroy() when the application disposes the SDK client.
See the @astrato-io/visualizations package README for supported visualization IDs, complete definition schemas, operations, getters, metadata discovery, and custom-element options.
Host Services
The client exposes the same host services through client.services and client.hostServices. SDK and transport events
are available separately through client.events.
| Service | Purpose |
| ------------ | ------------------------------------------------------------------------------------- |
| data | Queries, semantic layer metadata, datasource metadata, aggregations, and capabilities |
| engine | Optional visualization-compatible query and field adapter |
| selections | Group-scoped applied and pending selection state |
| variables | Variable lookup and value resolution |
| model | Visualization model save compatibility hooks |
Applications can replace individual services through hostServices:
const client = createAstratoClient({
baseUrl,
hostServices: {
variables: customVariableService,
},
});Data and engine defaults fail clearly when required configuration is missing. Selection and variable defaults use in-memory state.
Selection State
Selections are isolated by groupId. Value and associative selections use their kind together with the engine field key
as identity, so both kinds can coexist for one field.
client.services.selections.selectValues('dashboard', {
field: { fieldKey: '[Orders].[Country]' },
operator: 'inList',
values: ['Sweden'],
});
const subscription = client.services.selections.onActiveSelectionChangeApplied('dashboard').subscribe((selection) => {
console.log(selection.applied);
});
subscription.unsubscribe();Selection mode defaults to auto. In manual mode, changes remain pending until applyPendingSelections(groupId) is called.
Persistence Behavior
The SDK does not persist visualization or workbook state by default.
model.saveProperties()andmodel.saveLocalVSpec()are compatibility no-ops.- No implicit
localStorageor IndexedDB storage is used.
Events And Cleanup
const unsubscribe = client.events.on('transport:error', (event) => {
console.error(event.payload);
});
unsubscribe();
client.destroy();destroy() clears SDK event subscriptions. Applications should also unsubscribe from service observables they own.
Package Build
The package contains a minified ESM bundle, a single public TypeScript declaration file, this README, package metadata, the supplied LICENSE document, and any notice documents. It contains no source maps. RxJS remains external so application bundlers can deduplicate and tree-shake it.
The JavaScript implementation and public API types are visible to package users. Minification does not make client-side code confidential. Credentials and access rules must remain in the host application and Astrato APIs.
