@pond-ts/react
v0.74.0
Published
React hooks for pond-ts: subscribe to LiveSeries and derived views with throttled snapshots
Maintainers
Readme
pond-ts
Highly optimised, fully typed Timeseries library for TypeScript
Schema-driven events, composable batch transforms, push-based streaming ingest, multi-entity partitioning — and, optionally, React hooks and canvas charts that read the series directly. All strict TypeScript end to end, all immutable.
pond-ts is the TypeScript-first successor to pondjs, rewritten from scratch with a focus on type safety, composability, and the live-streaming patterns that pondjs never grew.
The packages
Three packages carry most projects. The core has no dependency on the other two; add them only if you render.
| Package | What it is | Needs |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| pond-ts — core | TimeSeries (batch) and LiveSeries (streaming) with one operator vocabulary: aggregate, rolling, align, fill, partition, join, typed columns. Node or browser, no React. | nothing |
| @pond-ts/charts — optional | Declarative React charts on a canvas data plane that consume a pond series with no adapter: line, area, band, bar, scatter, box, candlestick, heat map; cursors, selection, pan/zoom, annotations. | pond-ts, @pond-ts/react, React 18/19 |
| @pond-ts/react — optional | Hooks to own a series in a component and read live views on a throttled snapshot cadence (useLiveSeries, useSnapshot, …). | pond-ts, React 18/19 |
npm install pond-ts # core — enough for Node pipelines and non-React apps
npm install @pond-ts/charts @pond-ts/react pond-ts # add the React chart stackTwo domain packages (@pond-ts/financial for markets,
@pond-ts/fit for activity data) and one experimental
runtime (@pond-ts/process) sit on top — see
Domain packages below. All six release together under
one version; keep them in step.
- Typed schemas — declare once, every transform downstream narrows
off it.
event.get('cpu')returnsnumber | undefinedstraight from the schema; noascasts. - Batch + streaming with the same vocabulary —
filter,map,aggregate,rolling,diff,rate,fill,cumulative,sample,reduceall exist on bothTimeSeriesandLiveSeries. - Multi-entity by construction —
partitionBy('host')routes per entity;rolling/aggregate/fill/sampleover a partitioned view all become per-entity automatically. - Bounded-memory streaming — retention policies, eviction-aware views, and sampling decouple downstream window length from event rate at firehose loads (up to 500k events/sec on a single node.js instance.)
- Triggers — for control of rolling emission cadences. Synchronised partitioned rolling fires across partitions on every boundary.
- Typed column extraction —
series.column('cpu')returns a schema-narrowed typed column with single-pass reductions (min/max/sum/mean/stdev/median/percentile/minMax), index downsampling (bin), and a zero-copytoFloat64Array()for canvas / WebGL draw loops — no per-event allocation on the hot path. - No legacy baggage
Quick start: batch
import { Sequence, TimeSeries } from 'pond-ts';
const schema = [
{ name: 'time', kind: 'time' },
{ name: 'cpu', kind: 'number' },
{ name: 'requests', kind: 'number' },
{ name: 'host', kind: 'string' },
] as const;
const cpu = TimeSeries.fromJSON({
name: 'cpu',
schema,
rows: [
['2025-01-01T00:00:00Z', 0.31, 120, 'host1'],
['2025-01-01T00:01:00Z', 0.44, 135, 'host2'],
['2025-01-01T00:02:00Z', 0.52, 141, 'host1'],
['2025-01-01T00:03:00Z', 0.48, 128, 'host1'],
['2025-01-01T00:04:00Z', 0.63, 166, 'host3'],
],
});
const byMinute = cpu.aggregate(Sequence.every('1m'), {
cpu: 'avg',
requests: 'sum',
host: 'last',
});
const bands = cpu.baseline('cpu', { window: '2m', sigma: 2 });
// ^ appends rolling avg / sd / upper / lower in one pass.
const anomalies = cpu.outliers('cpu', { window: '2m', sigma: 2 });
// ^ schema-preserving filter — same columns, just the spikes.The full batch surface (align, rolling, smooth, groupBy, join,
reduce, diff, rate, fill, dedupe, materialize, sample,
partitionBy, pivotByGroup, …) follows the same shape: TimeSeries
in, TimeSeries out, schema preserved.
Quick start: live (streaming)
import { LiveSeries, Sequence } from 'pond-ts';
// 1. Same schema; this is a live append buffer with retention.
const live = new LiveSeries({
name: 'cpu',
schema,
retention: { maxAge: '10m' }, // keep only the last 10 minutes
});
// 2. Push as events arrive. Each push is validated against the schema.
live.push([Date.now(), 0.45, 128, 'api-1']);
// 3. Compose live views — incremental, push-driven, eviction-aware.
const recentAvg = live.rolling('5m', { cpu: 'avg' });
recentAvg.on('event', (e) => render(e.get('cpu')));
// 4. Snapshot to a TimeSeries for batch analytics at any time.
const snap = live.toTimeSeries();The full live surface (filter, map, select, window, aggregate,
rolling, reduce, diff, rate, pctChange, fill, cumulative,
sample) is incremental — events flow, views emit, retention bounds
memory.
Quick start: charts (React)
@pond-ts/charts reads a TimeSeries or LiveSeries directly — do the maths
in pond, hand the result to a layer. Rows share one x scale, so they pan,
zoom and track the cursor together.
import {
BandChart,
ChartContainer,
ChartRow,
Layers,
LineChart,
YAxis,
CrosshairCursor,
} from '@pond-ts/charts';
// `bands` is the baseline() result from the batch quick start:
// cpu + avg / sd / upper / lower columns.
export function CpuChart({ width }: { width: number }) {
return (
<ChartContainer width={width} panZoom>
<CrosshairCursor />
<ChartRow height={240}>
<YAxis id="cpu" format=".0%" />
<Layers>
<BandChart series={bands} lower="lower" upper="upper" axis="cpu" />
<LineChart series={bands} column="cpu" axis="cpu" />
</Layers>
</ChartRow>
</ChartContainer>
);
}Pass width="auto" to measure the parent instead. Live data renders through
the same layers: own the series with useLiveSeries from @pond-ts/react
and pass its snapshot as series.
Quick start: multi-entity
partitionBy routes events into per-key buffers. Every stateful
operator downstream of partitionBy runs per-partition automatically:
const perHost = cpu
.partitionBy('host')
.rolling('5m', { cpu: 'avg', cpu_sd: 'stdev' });
// .collect() fans the per-partition outputs back into a flat TimeSeries
// with the partition key auto-injected as a column.
const flat = perHost.collect();Same shape on the live side — live.partitionBy('host') returns a
LivePartitionedSeries whose rolling / fill / diff / sample
methods all maintain per-partition state.
Quick start: bounded-memory sampling
At firehose rates, a long rolling baseline blows the heap. sample({
stride: N }) decouples baseline length from event rate; chain it
between partitionBy and rolling:
// Per-host 1-in-10 stride feeding a per-host 5m baseline.
live
.partitionBy('host')
.sample({ stride: 10 })
.rolling('5m', { cpu_avg: 'avg', cpu_sd: 'stdev' });For visualization, the snapshot side ships reservoir sampling too — single-pass Algorithm R, sorted by key, fixed point count regardless of source size:
const points = series.sample({ reservoir: { size: 500 } }).toRows();
// 500 uncorrelated points drawn uniformly from the source.Performance
Measured against three reference points (snapshot 2026-07-30; full tables, methodology, and every losing number in the benchmark reference):
- vs pondjs (the predecessor): faster on all 54 measurable shared
operations — geometric mean 20.7×,
aggregateup to 453×,median157×, andselect/renameeffectively instant (O(1) column rebinds). - vs pandas (500k-bar workload): roughly even — ahead on
ema,mean,median/percentile; behind ~1.1–1.9× on the rolling studies. A five-study strategy pass runs 1.28× slower than pandas' Cython kernels. - vs polars, single-threaded: ahead on composite studies
(
bollinger0.53×, strategy stack 0.85×,ema0.32× — lower is pond-ts faster), behind 4–9× on whole-column reductions (the SIMD gap; half-closed already by blocked summation) and on raw ingest, where pond-ts front-loads validation the dataframe engines defer. - vs polars on all 10 cores: behind ~3.5× on the strategy stack —
pond-ts has no parallelism today; a measured 2.42× worker-thread path
is on the roadmap (
[PND-PROCPAR]).
The honest one-line version: the rewrite beat its predecessor by an order of magnitude, holds its own per-core against the native engines on the composite queries that dominate real workloads, and knows exactly where it is behind. Run locally:
npm run build && node packages/core/bench/vs-pondjs.cjsDomain packages
Optional, domain-specific, all on plain pond series:
@pond-ts/financial— sixty-plus oracle-verified technical studies (SMA, EMA, RSI, MACD, Bollinger, ATR, VWAP, …) that append columns to a bar series, a fluentbars.sma({ period: 20 }).rsi({ period: 14 })form, and aTradingCalendarso session-aligned bars, rolling windows and chart axes stop at the close.@pond-ts/fit— fitness and activity analytics: typed quantities with units, canonical activity series, geo (distance, elevation, best efforts), power (NP / IF / TSS, curves), heart-rate zones, splits.@pond-ts/process— experimental. Computations as data: processing graphs authored fluently or composed as JSON, resolved against a declared op vocabulary with content-addressed caching, provenance and per-node timings. The API is expected to move.
Documentation
The full guide is at https://pond-ts.org/.
- Start here — five-minute walkthrough with batch, live, and React examples.
- Concepts — temporal keys, sequences, windowing, partitioning, triggers, late data.
- Transforms reference — every batch operator (queries, aggregation, alignment, rolling, smoothing, sampling, cleaning, reshape, anomaly detection).
- Live reference
—
LiveSeries, live transforms, triggering. - How-to guides — building a dashboard, ingesting messy data.
- API reference (auto-generated) — TypeDoc output, every public class and method.
- CHANGELOG — what shipped in each release.
Versioning
All six packages release together under one version — keep them in step.
- Today (0.x): a minor release may contain breaking changes; each is marked breaking in the CHANGELOG with a migration note. Patch releases never break.
- From 1.0: semver — breaking changes only in a major release.
- Public API: the exports of each package's entry points, mapped in
API.md. Anything marked experimental (including all of
@pond-ts/process) or internal is excluded.
Details: pond-ts.org/docs/reference/versioning.
For coding agents
pond is built by agents and expects to be used by them. Three things exist so an agent can go from "never heard of pond" to working code without a human in the loop:
AGENTS.md+API.mdship inside every npm tarball —node_modules/pond-ts/AGENTS.mdis a one-read guide (which package for which task, the idioms, the mistakes agents make);API.mdmaps every public export to its source file. Source: docs/agents/USING_POND.md, API.md.- https://pond-ts.org/llms.txt — every docs page with a one-line
description, plus
llms-<area>.txtsingle-fetch dumps per package. - Claude Code plugin — skills for core, charts and financial, versioned
with the library:
/plugin marketplace add pond-ts/pond /plugin install pond-ts@pond-ts
Examples
- pond-ts-dashboard — a working React dashboard that streams synthetic per-host CPU / request metrics, computes per-host rolling baselines, flags anomalies against ±σ bands, and renders everything as live line and bar charts (~600 lines of TypeScript). Walked through end-to-end in Building a dashboard.
Develop
The repo is an npm-workspaces monorepo with six published packages
(pond-ts, @pond-ts/react, @pond-ts/charts, @pond-ts/financial,
@pond-ts/fit, @pond-ts/process). Node 18+ for runtime; Node 20+ for the
docs site (Docusaurus).
npm install # one-time, hoists deps for all packages
npm run build # build both packages
npm test # runtime + type-level tests on both packages
npm run format # prettier write across the repo
npm run verify # format check + build + test (CI parity)Each package lives under packages/<name>/ (core is pond-ts, the rest
match their scoped names). Docs live in website/ — its own npm root, not a
workspace.
License
MIT
