---
title: "Dash SDK"
description: "Live dashboards with real-time metrics, time-series aggregation, and widgets."
---

# @nolag/dash

Live dashboards with real-time metrics, time-series aggregation, and widgets.

## Overview

`@nolag/dash` powers live operational dashboards with streaming metrics and interactive widgets. Agents or servers publish metric data points and widget state snapshots to panels; viewers connected to the same panel receive updates instantly and can query rolling aggregations (average, min, max, sum, count, last) over any time window without hitting a separate time-series database. Your app owns one core NoLag client and injects it into `NoLagDash`; the wrapper attaches its behaviour to that connection.

### Key Features

- Streaming metric ingestion with per-stream data point buffering
- Client-side rolling aggregation over configurable time windows
- Widget state publishing for gauges, charts, counters, tables, text, and custom types
- Tag-based metadata on metrics for multi-dimensional filtering
- Automatic reconnection with panel subscriptions restored

## How It Works

`NoLagDash` attaches to an injected `@nolag/js-sdk` client. Calling `joinPanel()` creates a `DashboardPanel` that subscribes to two topics: `metrics` for time-series data points and `widgets` for widget state snapshots. A `MetricStore` inside the panel accumulates data points per stream ID and provides rolling-window aggregation, while a `WidgetManager` keeps the latest state for each widget keyed by widget ID. The app owns the socket lifecycle; the wrapper never opens or closes it.

| Topic | Purpose |
| --- | --- |
| `metrics` | Time-series data points: value, timestamp, unit, tags |
| `widgets` | Widget state snapshots: type, data payload, label, timestamp |

A viewer's buffer starts empty and fills from the data points published after it joined. A fresh subscribe or an ordinary reconnect never replays history; see [Replay](/docs/concepts/replay) for the one case where the broker replays messages. If new viewers need a starting state, have your publisher re-send its current widget snapshots on a timer.

**Before you start.** Create an app from the `nolag-dash-sdk` blueprint. That seeds the panel this SDK expects (`overview`) and the `online` lobby, and returns an app slug with a random suffix. That slug is the `appName` you pass to the wrapper. You can also do this in the portal: Apps, New App, pick the blueprint.

```typescript [Setup (server side, once)]
import { NoLagApi } from "@nolag/js-sdk";

const api = new NoLagApi(process.env.NOLAG_API_KEY); // nlg_live_...
const app = await api.apps.create({ name: "My App", blueprintId: "nolag-dash-sdk" });
console.log(app.slug); // e.g. "my-app-a3f9": this is your appName

const actor = await api.actors.create({ name: "web-client", actorType: "user" });
console.log(actor.accessToken); // shown once; keep it
```

## Installation

```bash [Terminal]
npm install @nolag/dash @nolag/js-sdk
```

**Shared connection.** One core NoLag client can back several wrapper SDKs at once, for example a dashboard, chat, and notify on a single socket, as long as each wrapper uses a distinct `appName`. Each wrapper attaches its handlers on construction and releases them with `detach()`, and never touches the socket itself. Your app owns `connect()` and `disconnect()`.

## Quick Start

```typescript [TypeScript]
import { NoLag } from '@nolag/js-sdk'
import { NoLagDash } from '@nolag/dash'

// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)

// Inject the client into the dash wrapper
const dash = new NoLagDash({
  client,
  appName: APP_SLUG, // the slug returned when you created the app
  username: 'Alice',
})

await client.connect()   // the app owns the connection
await dash.ready()       // wrapper setup complete

// Join the seeded panel
const panel = dash.joinPanel('overview')

// Publish a metric (typically called from a server or agent). The point is
// stamped with Date.now() and added to the publisher's own buffer.
panel.publishMetric('cpu.usage', 74.2, {
  unit: 'percent',
  tags: { host: 'web-01', region: 'us-east-1' },
})

// Listen for incoming metrics from other publishers
panel.on('metric', ({ streamId, value, timestamp }) => {
  console.log(`[${streamId}] ${value} @ ${new Date(timestamp).toISOString()}`)
})

// Get all buffered data points for a metric stream
const points = panel.getMetrics('cpu.usage')
console.log('Data points:', points.length)

// Get a rolling aggregation over the last 5 minutes
const agg = panel.getAggregation('cpu.usage', 5 * 60_000)
console.log('Avg CPU:', agg.avg.toFixed(1) + '%')
console.log('Max CPU:', agg.max.toFixed(1) + '%')

// Publish a widget state (e.g. a status counter)
panel.publishWidget('active-sessions', 'counter', { value: 1_248, trend: 'up' }, 'Active Sessions')

// Listen for widget updates
panel.on('widgetUpdate', ({ widgetId, type, data }) => {
  console.log(`Widget ${widgetId} (${type}):`, data)
})

// Read current widget state
const widget = panel.getWidget('active-sessions')

// Teardown: the wrapper releases its handlers; the app closes the socket.
dash.detach()
client.disconnect()
```

## API Reference

### NoLagDash

The main class. Attaches to the injected core client and manages the dashboard panel lifecycle.

#### Constructor Options

| Option | Type | Description |
| --- | --- | --- |
| `client` | `NoLagSocket` | **Required.** The injected core NoLag client the app owns and connects. |
| `username` | `string` | Optional display name for this viewer. |
| `metadata` | `Record<string, unknown>` | Optional custom viewer data attached to presence. |
| `appName` | `string` | The app slug returned when you created the app (default `'dash'`, which only works if an app with exactly that slug exists). |
| `panels` | `string[]` | Panels to subscribe to once the wrapper is ready. |
| `maxMetricPoints` | `number` | Max metric points kept in memory per stream (default `1000`). |
| `aggregationWindow` | `number` | Accepted and stored (default `60000`) but not read yet: `getAggregation()` uses 60000 ms whenever `windowMs` is omitted. |
| `debug` | `boolean` | Enable wrapper debug logging (default `false`). |

| Method | Returns | Description |
| --- | --- | --- |
| `ready()` | `Promise<void>` | Resolves once wrapper setup completed |
| `detach()` | `void` | Release this wrapper's handlers and topics; terminal, never closes the socket |
| `joinPanel(name, opts?)` | `DashboardPanel` | Join a dashboard panel; `opts.filters` limits metrics and widgets to matching values; throws before `ready()` |
| `leavePanel(name)` | `void` | Leave a panel and unsubscribe from its topics |
| `getPanels()` | `DashboardPanel[]` | All joined panels |
| `getOnlineViewers()` | `DashboardViewer[]` | All viewers currently online in the lobby |

### Events: NoLagDash

| Event | Payload | Description |
| --- | --- | --- |
| `connected` | none | Wrapper setup completed on a fresh connection |
| `disconnected` | `reason: string` | Connection closed |
| `reconnecting` | none | The client is attempting to reconnect |
| `reconnected` | none | Reconnection successful; panels are restored automatically |
| `error` | `error: Error` | Unrecoverable error occurred |
| `viewerOnline` | `viewer: DashboardViewer` | A viewer appeared in the lobby |
| `viewerOffline` | `viewer: DashboardViewer` | A viewer left the lobby |

### DashboardPanel

Returned by `joinPanel()`. Handles metric ingestion, aggregation, widget state, and per-panel viewer presence.

| Method | Returns | Description |
| --- | --- | --- |
| `publishMetric(streamId, value, opts?)` | `MetricPoint` | Publish a data point; `opts` accepts `unit`, `tags`, `filter`, `filters`. The timestamp is always `Date.now()` |
| `publishWidget(widgetId, type, data, label?, opts?)` | `WidgetUpdate` | Publish a widget snapshot; replaces the previous state for that widget ID. `type` is `'gauge' \| 'chart' \| 'counter' \| 'table' \| 'text' \| 'custom'` |
| `getMetrics(streamId?)` | `MetricPoint[]` | Buffered data points for one stream, or every stream when omitted |
| `getAggregation(streamId, windowMs?)` | `Aggregation` | `{ streamId, min, max, avg, sum, count, last, windowMs }` over the last `windowMs` ms (60000 when omitted) |
| `getWidget(widgetId)` | `WidgetUpdate \| undefined` | Latest state snapshot for a single widget |
| `getWidgets()` | `WidgetUpdate[]` | Latest state snapshots for all widgets in this panel |
| `getViewers()` | `DashboardViewer[]` | Remote viewers present in this panel |
| `setFilters(values, opts?)` | `void` | Replace the panel's subscription filters; see [Filters](/docs/concepts/filters) |

### Events: DashboardPanel

| Event | Payload | Description |
| --- | --- | --- |
| `metric` | `MetricPoint` | A data point arrived: `{ id, streamId, value, unit?, tags?, timestamp, isReplay }` |
| `widgetUpdate` | `WidgetUpdate` | A widget snapshot arrived: `{ id, widgetId, type, data, label?, timestamp, isReplay }` |
| `viewerJoined` | `viewer: DashboardViewer` | A viewer joined this panel |
| `viewerLeft` | `viewer: DashboardViewer` | A viewer left this panel |
