@pond-ts/react 0.11.3 → 0.11.5

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 (4) hide show
  1. package/CHANGELOG.md +1092 -0
  2. package/LICENSE +21 -0
  3. package/README.md +460 -0
  4. package/package.json +4 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Murphy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # pond-ts - A modern typescript timeseries library
2
+
3
+ TypeScript-first time series primitives built around typed events, typed schemas, and explicit temporal keys.
4
+
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
63
+
64
+ ```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.
132
+
133
+ ```ts
134
+ import { Sequence, TimeSeries } from 'pond-ts';
135
+
136
+ const schema = [
137
+ { name: 'time', kind: 'time' },
138
+ { name: 'cpu', kind: 'number' },
139
+ { name: 'requests', kind: 'number' },
140
+ { name: 'host', kind: 'string' },
141
+ ] as const;
142
+
143
+ const cpu = TimeSeries.fromJSON({
144
+ name: 'cpu',
145
+ schema,
146
+ 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'],
152
+ ],
153
+ });
154
+
155
+ const perMinute = cpu.align(Sequence.every('1m'), {
156
+ method: 'hold',
157
+ });
158
+
159
+ const fiveMinute = cpu.aggregate(Sequence.every('5m'), {
160
+ cpu: 'avg',
161
+ requests: 'sum',
162
+ host: 'last',
163
+ });
164
+
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
+ });
174
+
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'));
179
+ ```
180
+
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.
189
+
190
+ ## JSON ingest
191
+
192
+ Use `TimeSeries.fromJSON(...)` for external data and ambiguous local timestamps.
193
+
194
+ ```ts
195
+ import { TimeSeries } from 'pond-ts';
196
+
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({
204
+ name: 'cpu',
205
+ 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',
303
+ });
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
+
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
+ ```
326
+
327
+ Aggregate into buckets:
328
+
329
+ ```ts
330
+ const aggregated = series.aggregate(Sequence.every('5m'), {
331
+ cpu: 'avg',
332
+ host: 'last',
333
+ });
334
+ ```
335
+
336
+ Built-in aggregations:
337
+
338
+ - `sum`
339
+ - `avg`
340
+ - `min`
341
+ - `max`
342
+ - `count`
343
+ - `first`
344
+ - `last`
345
+
346
+ ## Joins
347
+
348
+ Join two aligned or bucketed series:
349
+
350
+ ```ts
351
+ const joined = left.join(right, { type: 'outer' });
352
+ ```
353
+
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
+ });
367
+ ```
368
+
369
+ Conflict handling:
370
+
371
+ - default: `onConflict: "error"`
372
+ - optional prefixing:
373
+
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:
384
+
385
+ ```ts
386
+ const rolled = series.rolling('5m', {
387
+ cpu: 'avg',
388
+ host: 'last',
389
+ });
390
+ ```
391
+
392
+ Sequence-driven rolling:
393
+
394
+ ```ts
395
+ const rolledOnGrid = series.rolling(Sequence.every('1m'), '5m', { cpu: 'avg' });
396
+ ```
397
+
398
+ Rolling alignment options:
399
+
400
+ - `trailing`
401
+ - `centered`
402
+ - `leading`
403
+
404
+ ## Smoothing
405
+
406
+ Smoothing targets one numeric column at a time.
407
+
408
+ Replace the source column:
409
+
410
+ ```ts
411
+ const smoothed = series.smooth('cpu', 'ema', { alpha: 0.2 });
412
+ ```
413
+
414
+ Append the smoothed output:
415
+
416
+ ```ts
417
+ const smoothed = series.smooth('cpu', 'movingAverage', {
418
+ window: '5m',
419
+ alignment: 'centered',
420
+ output: 'cpuAvg',
421
+ });
422
+ ```
423
+
424
+ Supported smoothing methods:
425
+
426
+ - `ema`
427
+ - `movingAverage`
428
+ - `loess`
429
+
430
+ For interval-like keys, smoothing uses the key midpoint as the internal anchor.
431
+
432
+ ## Calendar-aware helpers
433
+
434
+ Primitive helpers normalize local calendar inputs into absolute time:
435
+
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
+ });
445
+ ```
446
+
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
457
+
458
+ ## License
459
+
460
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.11.3",
3
+ "version": "0.11.5",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,10 +21,12 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "files": [
24
- "dist"
24
+ "dist",
25
+ "CHANGELOG.md"
25
26
  ],
26
27
  "scripts": {
27
28
  "build": "tsc -p tsconfig.json",
29
+ "prepack": "cp ../../README.md ./README.md && cp ../../LICENSE ./LICENSE && cp ../../CHANGELOG.md ./CHANGELOG.md && npm run build",
28
30
  "test": "vitest run",
29
31
  "test:runtime": "vitest run"
30
32
  },