@pond-ts/react 0.17.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +142 -382
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,10 +7,82 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
7
7
  file covers both packages. Pre-1.0: minor bumps may include new features and
8
8
  type-level changes; patch bumps are strictly additive.
9
9
 
10
- [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.17.0...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.17.1...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.17.1] — 2026-05-11
15
+
16
+ Bug fix: `live.partitionBy()` now default-inherits `ordering`,
17
+ `graceWindow`, and `retention` from the source `LiveSeries`. Surfaced
18
+ by the gRPC experiment's
19
+ [M4 late-data friction note](https://github.com/pjm17971/pond-grpc-experiment/blob/main/friction-notes/M4.md),
20
+ which measured `99.5%` of late events crashing the partition router
21
+ under `source = LiveSeries({ ordering: 'reorder', graceWindow })`
22
+ followed by bare `partitionBy('host')`.
23
+
24
+ ### Fixed
25
+
26
+ - **`LiveSeries.partitionBy(by)` default-inherits source config**
27
+ ([#TBD](https://github.com/pjm17971/pond-ts/pull/TBD)). Pre-fix,
28
+ per-partition sub-series were constructed with default
29
+ `ordering: 'strict'` regardless of source mode. Under a `'reorder'`
30
+ source, late events that the source accepted via its reorder path
31
+ were routed into the partition's `#insert` and threw with a
32
+ strict-mode error; the throw propagated back through the source's
33
+ listener fan-out into `live.push()`.
34
+
35
+ Post-fix, `partitionBy(by)` defaults each per-partition sub-series'
36
+ `ordering`, `graceWindow`, and `retention` to the source's values.
37
+ Explicit options on `partitionBy(by, { ordering, ... })` override
38
+ per-field. `graceWindow` inheritance is gated on effective ordering
39
+ being `'reorder'` (LiveSeries rejects strict + graceWindow combos).
40
+
41
+ ```ts
42
+ // Pre-0.17.1: crashed the partition router
43
+ const live = new LiveSeries({
44
+ name: 'metrics',
45
+ schema,
46
+ ordering: 'reorder',
47
+ graceWindow: '30s',
48
+ });
49
+ live.partitionBy('host'); // ← partition was strict regardless
50
+
51
+ // Post-0.17.1: partitions inherit reorder + 30s grace; late events
52
+ // accept correctly via the reorder path.
53
+ ```
54
+
55
+ Existing callers with explicit `partitionBy(by, { ordering, ... })`:
56
+ unchanged. Existing callers on `'strict'` sources: unchanged.
57
+ Existing callers on `'reorder'` sources with bare `partitionBy`:
58
+ the previously-thrown late events now accept correctly — bug fix,
59
+ not a behavior change anyone could rely on.
60
+
61
+ - **`collect()` and `apply()` on `LivePartitionedSeries` default-
62
+ inherit `ordering` and `graceWindow`** from the partitioned series
63
+ (which inherits from source). Pre-fix, the unified buffer defaulted
64
+ to `'strict'`, so partition fan-in on a `'reorder'` source could
65
+ deliver events out-of-order to a strict unified buffer and throw.
66
+ Retention stays caller-explicit on these per the existing append-
67
+ only fan-in semantics.
68
+
69
+ ### Notes
70
+
71
+ - **Six regression tests pin the new defaults** in
72
+ `LivePartitionedSeries.test.ts`: inherited ordering, inherited
73
+ graceWindow within reorder, inherited retention on partitions,
74
+ explicit override of inheritance, strict-source no-change, and the
75
+ edge case where overriding ordering to strict suppresses graceWindow
76
+ inheritance. `collect()` inheritance pinned separately.
77
+ - The gRPC experiment's M4 friction note also surfaced milestone B
78
+ (capability-based late repair) as **driver-light by empirical test**
79
+ after Codex's adversarial pass caught simulator RNG leakage across
80
+ A/B legs. Drift signal collapsed to within noise on every host once
81
+ all randomness sources were seeded — milestone B's library design
82
+ stays sound, but the gRPC experiment's measurement style (last-tick
83
+ `.value()` reads) doesn't surface its payoff. Milestone B sequencing
84
+ updated in PLAN.md to reflect this finding.
85
+
14
86
  ## [0.17.0] — 2026-05-08
15
87
 
16
88
  `sample({...})` operator wave: bounded-memory stream thinning, surfaced
@@ -385,6 +457,7 @@ compaction); any downstream code reading `#entries` directly would
385
457
  break, but those fields are private. Public APIs and types are
386
458
  unchanged.
387
459
 
460
+ [0.17.1]: https://github.com/pjm17971/pond-ts/compare/v0.17.0...v0.17.1
388
461
  [0.17.0]: https://github.com/pjm17971/pond-ts/compare/v0.16.1...v0.17.0
389
462
  [0.16.1]: https://github.com/pjm17971/pond-ts/compare/v0.16.0...v0.16.1
390
463
  [0.16.0]: https://github.com/pjm17971/pond-ts/compare/v0.15.2...v0.16.0
package/README.md CHANGED
@@ -1,134 +1,39 @@
1
- # pond-ts - A modern typescript timeseries library
1
+ # pond-ts
2
2
 
3
- TypeScript-first time series primitives built around typed events, typed schemas, and explicit temporal keys.
3
+ **Highly optimised, fully typed Timeseries library for TypeScript**
4
+
5
+ Schema-driven events, composable batch transforms, push-based streaming
6
+ ingest, multi-entity partitioning, and an optional React integration —
7
+ all strict TypeScript end to end, all immutable.
4
8
 
5
- **pond-ts** is the successor to [pondjs](https://github.com/esnet/pond), rewritten from scratch in TypeScript with a focus on performance, type safety, and composable live streaming.
6
-
7
- - typed `TimeSeries` construction and immutable `Event` objects
8
- - `Time`, `TimeRange`, and `Interval` temporal keys
9
- - alignment, aggregation, joins, rolling windows, and smoothing
10
- - `LiveSeries` with push-based ingestion, retention policies, and subscriptions
11
- - `LiveView`, `LiveAggregation`, and `LiveRollingAggregation` for streaming composition
12
- - timezone-aware calendar sequences and ingest helpers
13
-
14
- The package is intended to work in modern Node and frontend projects.
15
-
16
- ## Performance
17
-
18
- pond-ts is **7.6x faster** than pondjs on average across all comparable operations,
19
- with no regressions. The advantage grows with data size.
20
-
21
- | Category | Speedup (N=16k) | Notes |
22
- | ----------------- | --------------- | --------------------------------------------- |
23
- | **Aggregation** | 25–32x | O(N+B) bucketing vs O(N×B) Pipeline |
24
- | **Alignment** | 32x | Forward cursor vs repeated binary search |
25
- | **Rate/diff** | 18x | Direct array walk vs Pipeline materialization |
26
- | **Fill** | 10–11x | Single-pass vs Pipeline per strategy |
27
- | **Transforms** | 3–16x | Pre-validated constructor skips re-validation |
28
- | **Construction** | 7x | Plain objects vs ImmutableJS wrapping |
29
- | **Statistics** | 7–9x | Direct computation vs ImmutableJS iteration |
30
- | **Serialization** | 4x | Simpler internal representation |
31
- | **Event access** | 23x | Array indexing vs ImmutableJS `get()` |
32
-
33
- See the [full benchmark results](website/docs/reference/benchmarks.mdx) for detailed numbers.
34
- Run locally: `npm run build && node packages/core/bench/vs-pondjs.cjs`
35
-
36
- ## Install
37
-
38
- ```sh
39
- npm install pond-ts
40
- ```
41
-
42
- ## Build
43
-
44
- The repo toolchain should work on Node 18, but use `nvm` to verify against newer stable Node releases when needed.
45
-
46
- ```sh
47
- npm run build
48
- ```
49
-
50
- ## Format
51
-
52
- ```sh
53
- npm run format
54
- ```
55
-
56
- ## Test
57
-
58
- ```sh
59
- npm test
60
- ```
61
-
62
- ## Verify
9
+ **pond-ts** is the TypeScript-first successor to
10
+ [pondjs](https://github.com/esnet/pond), rewritten from scratch with a
11
+ focus on type safety, composability, and the live-streaming patterns
12
+ that pondjs never grew.
63
13
 
64
14
  ```sh
65
- npm run verify
66
- ```
67
-
68
- ## Docs site
69
-
70
- The documentation website lives in [`website/`](./website) and is built with Docusaurus.
71
-
72
- ## Examples
73
-
74
- - **[pond-ts-dashboard](https://github.com/pjm17971/pond-ts-dashboard)**
75
- a working React dashboard that streams synthetic per-host CPU/request
76
- metrics, computes per-host rolling baselines, flags anomalies against
77
- ±σ bands, and renders everything as live line and bar charts (~600
78
- lines of TypeScript). The repo's README is also published as a docs-
79
- site guide: [Building a dashboard](website/docs/how-to-guides/dashboard-guide.mdx).
80
-
81
- ## License
82
-
83
- MIT
84
-
85
- ## Core model
86
-
87
- The key types are:
88
-
89
- - `Time`: a point in time
90
- - `TimeRange`: an unlabeled interval
91
- - `Interval`: a labeled interval
92
-
93
- An `Event` is a key plus typed data.
94
-
95
- A `TimeSeries` is an ordered immutable collection of events sharing one schema.
96
-
97
- ## Quick start
98
-
99
- ```ts
100
- import { TimeSeries } from 'pond-ts';
101
-
102
- const schema = [
103
- { name: 'time', kind: 'time' },
104
- { name: 'cpu', kind: 'number' },
105
- { name: 'host', kind: 'string' },
106
- { name: 'healthy', kind: 'boolean' },
107
- ] as const;
108
-
109
- const series = new TimeSeries({
110
- name: 'cpu',
111
- schema,
112
- rows: [
113
- [new Date('2025-01-01T00:00:00.000Z'), 0.42, 'api-1', true],
114
- [new Date('2025-01-01T00:01:00.000Z'), 0.51, 'api-2', true],
115
- ],
116
- });
117
-
118
- const event = series.at(1);
119
- if (!event) {
120
- throw new Error('missing event');
121
- }
122
-
123
- event.key();
124
- event.timeRange();
125
- event.get('cpu');
126
- event.data().host;
127
- ```
128
-
129
- ## Worked example
130
-
131
- This is the kind of flow `pond-ts` is built for: start with typed events, then derive aligned, aggregated, and smoothed analytical views without mutating the original series.
15
+ npm install pond-ts # core
16
+ npm install @pond-ts/react # React hooks (optional)
17
+ ```
18
+
19
+ - **Typed schemas** — declare once, every transform downstream narrows
20
+ off it. `event.get('cpu')` returns `number | undefined` straight from
21
+ the schema; no `as` casts.
22
+ - **Batch + streaming with the same vocabulary** — `filter`, `map`,
23
+ `aggregate`, `rolling`, `diff`, `rate`, `fill`, `cumulative`,
24
+ `sample`, `reduce` all exist on both `TimeSeries` and `LiveSeries`.
25
+ - **Multi-entity by construction** `partitionBy('host')` routes per
26
+ entity; `rolling` / `aggregate` / `fill` / `sample` over a partitioned
27
+ view all become per-entity automatically.
28
+ - **Bounded-memory streaming** retention policies, eviction-aware
29
+ views, and sampling decouple downstream window length
30
+ from event rate at firehose loads (up to 500k events/sec on a
31
+ single node.js instance.)
32
+ - **Triggers** — for control of rolling emission cadences. Synchronised
33
+ partitioned rolling fires across partitions on every boundary.
34
+ - **No legacy baggage**
35
+
36
+ ## Quick start: batch
132
37
 
133
38
  ```ts
134
39
  import { Sequence, TimeSeries } from 'pond-ts';
@@ -144,316 +49,171 @@ const cpu = TimeSeries.fromJSON({
144
49
  name: 'cpu',
145
50
  schema,
146
51
  rows: [
147
- ['2025-01-01T00:00:00Z', 0.31, 120, 'api-1'],
148
- ['2025-01-01T00:01:00Z', 0.44, 135, 'api-1'],
149
- ['2025-01-01T00:02:00Z', 0.52, 141, 'api-1'],
150
- ['2025-01-01T00:03:00Z', 0.48, 128, 'api-1'],
151
- ['2025-01-01T00:04:00Z', 0.63, 166, 'api-1'],
52
+ ['2025-01-01T00:00:00Z', 0.31, 120, 'host1'],
53
+ ['2025-01-01T00:01:00Z', 0.44, 135, 'host2'],
54
+ ['2025-01-01T00:02:00Z', 0.52, 141, 'host1'],
55
+ ['2025-01-01T00:03:00Z', 0.48, 128, 'host1'],
56
+ ['2025-01-01T00:04:00Z', 0.63, 166, 'host3'],
152
57
  ],
153
58
  });
154
59
 
155
- const perMinute = cpu.align(Sequence.every('1m'), {
156
- method: 'hold',
157
- });
158
-
159
- const fiveMinute = cpu.aggregate(Sequence.every('5m'), {
60
+ const byMinute = cpu.aggregate(Sequence.every('1m'), {
160
61
  cpu: 'avg',
161
62
  requests: 'sum',
162
63
  host: 'last',
163
64
  });
164
65
 
165
- const rolling = cpu.rolling('3m', {
166
- cpu: 'avg',
167
- requests: 'sum',
168
- });
169
-
170
- const smoothed = cpu.smooth('cpu', 'ema', {
171
- alpha: 0.35,
172
- output: 'cpuTrend',
173
- });
66
+ const bands = cpu.baseline('cpu', { window: '2m', sigma: 2 });
67
+ // ^ appends rolling avg / sd / upper / lower in one pass.
174
68
 
175
- console.log(perMinute.first()?.key().asString());
176
- console.log(fiveMinute.first()?.data());
177
- console.log(rolling.last()?.data());
178
- console.log(smoothed.last()?.get('cpuTrend'));
69
+ const anomalies = cpu.outliers('cpu', { window: '2m', sigma: 2 });
70
+ // ^ schema-preserving filter — same columns, just the spikes.
179
71
  ```
180
72
 
181
- From one typed source series, you can derive:
182
-
183
- - aligned interval views for dashboards and joins
184
- - bucketed aggregates for reporting
185
- - rolling metrics for short-term behavior
186
- - smoothed trends for visualization or alerting
187
-
188
- All of those remain fully typed and immutable.
73
+ The full batch surface (`align`, `rolling`, `smooth`, `groupBy`, `join`,
74
+ `reduce`, `diff`, `rate`, `fill`, `dedupe`, `materialize`, `sample`,
75
+ `partitionBy`, `pivotByGroup`, …) follows the same shape: TimeSeries
76
+ in, TimeSeries out, schema preserved.
189
77
 
190
- ## JSON ingest
191
-
192
- Use `TimeSeries.fromJSON(...)` for external data and ambiguous local timestamps.
78
+ ## Quick start: live (streaming)
193
79
 
194
80
  ```ts
195
- import { TimeSeries } from 'pond-ts';
81
+ import { LiveSeries, Sequence } from 'pond-ts';
196
82
 
197
- const schema = [
198
- { name: 'time', kind: 'time' },
199
- { name: 'value', kind: 'number' },
200
- { name: 'status', kind: 'string', required: false },
201
- ] as const;
202
-
203
- const series = TimeSeries.fromJSON({
83
+ // 1. Same schema; this is a live append buffer with retention.
84
+ const live = new LiveSeries({
204
85
  name: 'cpu',
205
86
  schema,
206
- rows: [
207
- ['2025-01-01T09:00', 0.42, 'ok'],
208
- ['2025-01-01T10:00', 0.51, null],
209
- ],
210
- parse: { timeZone: 'Europe/Madrid' },
211
- });
212
- ```
213
-
214
- Export back into the same JSON-friendly shape:
215
-
216
- ```ts
217
- const rows = series.toJSON();
218
- const objectRows = series.toJSON({ rowFormat: 'object' });
219
- ```
220
-
221
- For normalized in-memory export helpers:
222
-
223
- ```ts
224
- const normalizedRows = series.toRows();
225
- const normalizedObjects = series.toObjects();
226
- ```
227
-
228
- ## Event and series transforms
229
-
230
- Event-level transforms:
231
-
232
- - `get(...)`
233
- - `set(...)`
234
- - `merge(...)`
235
- - `select(...)`
236
- - `rename(...)`
237
- - `collapse(...)`
238
- - `asTime(...)`
239
- - `asTimeRange()`
240
- - `asInterval(...)`
241
-
242
- Series-level transforms:
243
-
244
- - `map(...)`
245
- - `select(...)`
246
- - `rename(...)`
247
- - `collapse(...)`
248
- - `asTime(...)`
249
- - `asTimeRange()`
250
- - `asInterval(...)`
251
-
252
- Example:
253
-
254
- ```ts
255
- const renamed = series.rename({ cpu: 'usage' });
256
- const selected = renamed.select('usage', 'healthy');
257
- ```
258
-
259
- ## Temporal selection
260
-
261
- `TimeSeries` includes both positional and temporal selection methods:
262
-
263
- - `slice(...)`
264
- - `filter(...)`
265
- - `find(...)`
266
- - `first()`
267
- - `last()`
268
- - `before(...)`
269
- - `after(...)`
270
- - `within(...)`
271
- - `overlapping(...)`
272
- - `containedBy(...)`
273
- - `trim(...)`
274
- - `includesKey(...)`
275
- - `bisect(...)`
276
- - `atOrBefore(...)`
277
- - `atOrAfter(...)`
278
-
279
- Vocabulary is intentionally distinct:
280
-
281
- - `within(...)`: fully contained
282
- - `overlapping(...)`: intersects without clipping
283
- - `trim(...)`: intersects and clips event extents
284
-
285
- ## Sequences
286
-
287
- Use `Sequence` for unbounded grids and `BoundedSequence` for explicit finite interval lists.
288
-
289
- Fixed-step sequences:
290
-
291
- ```ts
292
- import { Sequence } from 'pond-ts';
293
-
294
- const minuteGrid = Sequence.every('1m');
295
- const hourlyGrid = Sequence.hourly();
296
- ```
297
-
298
- Calendar-aware sequences:
299
-
300
- ```ts
301
- const localDays = Sequence.calendar('day', {
302
- timeZone: 'America/New_York',
87
+ retention: { maxAge: '10m' }, // keep only the last 10 minutes
303
88
  });
304
- ```
305
-
306
- Explicit bounded sequences:
307
-
308
- ```ts
309
- import { BoundedSequence, Interval } from 'pond-ts';
310
-
311
- const buckets = new BoundedSequence([
312
- new Interval({ value: 'a', start: 0, end: 10 }),
313
- new Interval({ value: 'b', start: 20, end: 30 }),
314
- ]);
315
- ```
316
89
 
317
- ## Alignment and aggregation
318
-
319
- Align onto a sequence:
320
-
321
- ```ts
322
- const aligned = series.align(Sequence.every('1m'), {
323
- method: 'hold',
324
- });
325
- ```
90
+ // 2. Push as events arrive. Each push is validated against the schema.
91
+ live.push([Date.now(), 0.45, 128, 'api-1']);
326
92
 
327
- Aggregate into buckets:
93
+ // 3. Compose live views — incremental, push-driven, eviction-aware.
94
+ const recentAvg = live.rolling('5m', { cpu: 'avg' });
95
+ recentAvg.on('event', (e) => render(e.get('cpu')));
328
96
 
329
- ```ts
330
- const aggregated = series.aggregate(Sequence.every('5m'), {
331
- cpu: 'avg',
332
- host: 'last',
333
- });
97
+ // 4. Snapshot to a TimeSeries for batch analytics at any time.
98
+ const snap = live.toTimeSeries();
334
99
  ```
335
100
 
336
- Built-in aggregations:
337
-
338
- - `sum`
339
- - `avg`
340
- - `min`
341
- - `max`
342
- - `count`
343
- - `first`
344
- - `last`
101
+ The full live surface (`filter`, `map`, `select`, `window`, `aggregate`,
102
+ `rolling`, `reduce`, `diff`, `rate`, `pctChange`, `fill`, `cumulative`,
103
+ `sample`) is incremental — events flow, views emit, retention bounds
104
+ memory.
345
105
 
346
- ## Joins
106
+ ## Quick start: multi-entity
347
107
 
348
- Join two aligned or bucketed series:
108
+ `partitionBy` routes events into per-key buffers. Every stateful
109
+ operator downstream of `partitionBy` runs per-partition automatically:
349
110
 
350
111
  ```ts
351
- const joined = left.join(right, { type: 'outer' });
352
- ```
112
+ const perHost = cpu
113
+ .partitionBy('host')
114
+ .rolling('5m', { cpu: 'avg', cpu_sd: 'stdev' });
353
115
 
354
- Supported join types:
355
-
356
- - `outer`
357
- - `left`
358
- - `right`
359
- - `inner`
360
-
361
- Join many:
362
-
363
- ```ts
364
- const wide = TimeSeries.joinMany([cpu, memory, errors], {
365
- type: 'outer',
366
- });
116
+ // .collect() fans the per-partition outputs back into a flat TimeSeries
117
+ // with the partition key auto-injected as a column.
118
+ const flat = perHost.collect();
367
119
  ```
368
120
 
369
- Conflict handling:
121
+ Same shape on the live side — `live.partitionBy('host')` returns a
122
+ `LivePartitionedSeries` whose `rolling` / `fill` / `diff` / `sample`
123
+ methods all maintain per-partition state.
370
124
 
371
- - default: `onConflict: "error"`
372
- - optional prefixing:
125
+ ## Quick start: bounded-memory sampling
373
126
 
374
- ```ts
375
- const joined = left.join(right, {
376
- onConflict: 'prefix',
377
- prefixes: ['left', 'right'] as const,
378
- });
379
- ```
380
-
381
- ## Rolling windows
382
-
383
- Event-driven rolling:
127
+ At firehose rates, a long rolling baseline blows the heap. `sample({
128
+ stride: N })` decouples baseline length from event rate; chain it
129
+ between `partitionBy` and `rolling`:
384
130
 
385
131
  ```ts
386
- const rolled = series.rolling('5m', {
387
- cpu: 'avg',
388
- host: 'last',
389
- });
132
+ // Per-host 1-in-10 stride feeding a per-host 5m baseline.
133
+ live
134
+ .partitionBy('host')
135
+ .sample({ stride: 10 })
136
+ .rolling('5m', { cpu_avg: 'avg', cpu_sd: 'stdev' });
390
137
  ```
391
138
 
392
- Sequence-driven rolling:
139
+ For visualization, the snapshot side ships reservoir sampling too —
140
+ single-pass Algorithm R, sorted by key, fixed point count regardless of
141
+ source size:
393
142
 
394
143
  ```ts
395
- const rolledOnGrid = series.rolling(Sequence.every('1m'), '5m', { cpu: 'avg' });
144
+ const points = series.sample({ reservoir: { size: 500 } }).toRows();
145
+ // 500 uncorrelated points drawn uniformly from the source.
396
146
  ```
397
147
 
398
- Rolling alignment options:
399
-
400
- - `trailing`
401
- - `centered`
402
- - `leading`
148
+ ## Performance
403
149
 
404
- ## Smoothing
150
+ pond-ts is **7.6x faster** than pondjs on average across all comparable
151
+ operations, with no regressions. The advantage grows with data size.
405
152
 
406
- Smoothing targets one numeric column at a time.
153
+ | Category | Speedup (N=16k) | Notes |
154
+ | ----------------- | --------------- | --------------------------------------------- |
155
+ | **Aggregation** | 25–32x | O(N+B) bucketing vs O(N×B) Pipeline |
156
+ | **Alignment** | 32x | Forward cursor vs repeated binary search |
157
+ | **Rate/diff** | 18x | Direct array walk vs Pipeline materialization |
158
+ | **Fill** | 10–11x | Single-pass vs Pipeline per strategy |
159
+ | **Transforms** | 3–16x | Pre-validated constructor skips re-validation |
160
+ | **Construction** | 7x | Plain objects vs ImmutableJS wrapping |
161
+ | **Statistics** | 7–9x | Direct computation vs ImmutableJS iteration |
162
+ | **Serialization** | 4x | Simpler internal representation |
163
+ | **Event access** | 23x | Array indexing vs ImmutableJS `get()` |
407
164
 
408
- Replace the source column:
165
+ See the [full benchmark results](website/docs/reference/benchmarks.mdx)
166
+ for detailed numbers. Run locally:
409
167
 
410
- ```ts
411
- const smoothed = series.smooth('cpu', 'ema', { alpha: 0.2 });
168
+ ```sh
169
+ npm run build && node packages/core/bench/vs-pondjs.cjs
412
170
  ```
413
171
 
414
- Append the smoothed output:
172
+ ## Documentation
415
173
 
416
- ```ts
417
- const smoothed = series.smooth('cpu', 'movingAverage', {
418
- window: '5m',
419
- alignment: 'centered',
420
- output: 'cpuAvg',
421
- });
422
- ```
174
+ The full guide is at **<https://pjm17971.github.io/pond-ts/>**.
423
175
 
424
- Supported smoothing methods:
176
+ - **[Start here](https://pjm17971.github.io/pond-ts/docs/)**
177
+ — five-minute walkthrough with batch, live, and React examples.
178
+ - **[Concepts](https://pjm17971.github.io/pond-ts/docs/start-here/concepts)**
179
+ — temporal keys, sequences, windowing, partitioning, triggers, late
180
+ data.
181
+ - **[Transforms reference](https://pjm17971.github.io/pond-ts/docs/pond-ts/transforms/queries)**
182
+ — every batch operator (queries, aggregation, alignment, rolling,
183
+ smoothing, sampling, cleaning, reshape, anomaly detection).
184
+ - **[Live reference](https://pjm17971.github.io/pond-ts/docs/pond-ts/live/live-series)**
185
+ — `LiveSeries`, live transforms, triggering.
186
+ - **[How-to guides](https://pjm17971.github.io/pond-ts/docs/how-to-guides)**
187
+ — building a dashboard, ingesting messy data.
188
+ - **[API reference (auto-generated)](https://pjm17971.github.io/pond-ts/generated-api/core/)**
189
+ — TypeDoc output, every public class and method.
190
+ - **[CHANGELOG](./CHANGELOG.md)** — what shipped in each release.
425
191
 
426
- - `ema`
427
- - `movingAverage`
428
- - `loess`
192
+ ## Examples
429
193
 
430
- For interval-like keys, smoothing uses the key midpoint as the internal anchor.
194
+ - **[pond-ts-dashboard](https://github.com/pjm17971/pond-ts-dashboard)**
195
+ — a working React dashboard that streams synthetic per-host CPU /
196
+ request metrics, computes per-host rolling baselines, flags anomalies
197
+ against ±σ bands, and renders everything as live line and bar charts
198
+ (~600 lines of TypeScript). Walked through end-to-end in
199
+ [Building a dashboard](website/docs/how-to-guides/dashboard-guide.mdx).
431
200
 
432
- ## Calendar-aware helpers
201
+ ## Develop
433
202
 
434
- Primitive helpers normalize local calendar inputs into absolute time:
203
+ The repo is an npm-workspaces monorepo with two published packages
204
+ (`pond-ts`, `@pond-ts/react`). Node 18+ for runtime; Node 20+ for the
205
+ docs site (Docusaurus).
435
206
 
436
- ```ts
437
- import { Interval, Time, TimeRange } from 'pond-ts';
438
-
439
- const time = Time.parse('2025-01-01T09:00', { timeZone: 'Europe/Madrid' });
440
- const day = TimeRange.fromDate('2025-01-01', { timeZone: 'UTC' });
441
- const month = Interval.fromCalendar('month', '2025-01', {
442
- timeZone: 'UTC',
443
- value: '2025-01',
444
- });
207
+ ```sh
208
+ npm install # one-time, hoists deps for both packages
209
+ npm run build # build both packages
210
+ npm test # runtime + type-level tests on both packages
211
+ npm run format # prettier write across the repo
212
+ npm run verify # format check + build + test (CI parity)
445
213
  ```
446
214
 
447
- ## Current scope
448
-
449
- The library provides both batch analytics (`TimeSeries`) and live streaming
450
- (`LiveSeries`, `LiveView`, `LiveAggregation`, `LiveRollingAggregation`).
451
-
452
- - type-safe construction with schema types that flow through every operation
453
- - temporal modeling with `Time`, `TimeRange`, and `Interval` keys
454
- - composable batch analytics (aggregate, align, join, rolling, smooth, fill, diff, rate, groupBy)
455
- - push-based live ingestion with retention policies and subscriptions
456
- - live composition: filter, map, select, window, diff, rate, fill, cumulative, aggregate, rolling
215
+ `packages/core/` is the `pond-ts` package; `packages/react/` is
216
+ `@pond-ts/react`. Docs live in `website/`.
457
217
 
458
218
  ## License
459
219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {