@pond-ts/react 0.11.5 → 0.11.7

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.
package/CHANGELOG.md CHANGED
@@ -7,10 +7,75 @@ 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.11.5...HEAD
10
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.11.7...HEAD
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.11.7] — 2026-04-29
15
+
16
+ ### Added
17
+
18
+ - **`LiveView.count()` and `LiveView.eventRate()` terminal accessors.**
19
+ Read the current event count and events-per-second over a windowed
20
+ view directly — closes the
21
+ `useCurrent(live, { cpu: 'count' }, { tail: '1m' }).cpu / 60`
22
+ boilerplate surfaced by the gRPC experiment.
23
+ ```ts
24
+ const eventsPerSec = live.window('1m').eventRate(); // events/sec
25
+ const eventsInWindow = live.window('1m').count();
26
+ ```
27
+ `eventRate()` requires a time-based window (`window('1m')`) and
28
+ throws on count-based windows (`window(100)`) — there's no
29
+ denominator to use. Distinct from `LiveView.rate(columns)`,
30
+ which is the per-column derivative operator (rate-of-change of
31
+ values).
32
+ - `LiveView.{filter,map,select}` now propagate the parent's window
33
+ duration to the child view, so chains like
34
+ `live.window('1m').filter(...).eventRate()` work as expected.
35
+ - `@pond-ts/react` ships **`useEventRate(source, '1m')`** — a
36
+ reactive hook returning the events-per-second number, throttled
37
+ on `'event'` like `useSnapshot`. Hooks mounted on already-
38
+ populated sources render the actual rate on first paint via
39
+ lazy `useState` init.
40
+ ```tsx
41
+ const eventsPerSec = useEventRate(liveSeries, '1m');
42
+ // <div>EVENT RATE {eventsPerSec.toFixed(1)}/s</div>
43
+ ```
44
+
45
+ [0.11.7]: https://github.com/pjm17971/pond-ts/compare/v0.11.6...v0.11.7
46
+
47
+ ## [0.11.6] — 2026-04-29
48
+
49
+ ### Added
50
+
51
+ - **`LiveSeries.toJSON()` return-type narrowing on `rowFormat`.**
52
+ Overloads keyed on `rowFormat: 'array' | 'object'` so consumers
53
+ read `result.rows` without a cast. Tuple form returns
54
+ `TimeSeriesJsonOutputArray<S>`; object form returns
55
+ `TimeSeriesJsonOutputObject<S>`. Both new types exported from
56
+ `pond-ts/types`. The companion narrowing on `TimeSeries.toJSON`
57
+ is still parked — it cascades TS2394 errors through unrelated
58
+ overload sets in `TimeSeries.ts`. See PLAN.md.
59
+ - New types: `TimeSeriesJsonOutputArray<S>` and
60
+ `TimeSeriesJsonOutputObject<S>`. Use these for typed assignment
61
+ (`const out: TimeSeriesJsonOutputArray<S> = ts.toJSON()`) or
62
+ cast (`ts.toJSON() as TimeSeriesJsonOutputArray<S>`) until the
63
+ `TimeSeries.toJSON` narrowing lands.
64
+
65
+ ### Documentation
66
+
67
+ - `count` reducer JSDoc clarifies that **duplicate temporal keys
68
+ do not collapse** — multiple events sharing one `Time` key each
69
+ contribute independently to the count. Walks the per-column
70
+ value array, not unique keys. Behavior is consistent across
71
+ `reduce`, `aggregate`, `rolling`, `LiveAggregation`, and
72
+ `LiveRollingAggregation` — pinned by `test/duplicate-keys.test.ts`
73
+ (9 tests covering every layer including the
74
+ "dashboard-defaults" 480-events-at-8/s scenario from the gRPC
75
+ experiment's M1 friction notes).
76
+
77
+ [0.11.6]: https://github.com/pjm17971/pond-ts/compare/v0.11.5...v0.11.6
78
+
14
79
  ## [0.11.5] — 2026-04-29
15
80
 
16
81
  ### Fixed
package/dist/index.d.ts CHANGED
@@ -6,5 +6,6 @@ export { useDerived } from './useDerived.js';
6
6
  export { useLiveQuery } from './useLiveQuery.js';
7
7
  export { useLatest } from './useLatest.js';
8
8
  export { useCurrent, type UseCurrentOptions } from './useCurrent.js';
9
+ export { useEventRate } from './useEventRate.js';
9
10
  export { takeSnapshot } from './takeSnapshot.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -7,5 +7,6 @@ export { useDerived } from './useDerived.js';
7
7
  export { useLiveQuery } from './useLiveQuery.js';
8
8
  export { useLatest } from './useLatest.js';
9
9
  export { useCurrent } from './useCurrent.js';
10
+ export { useEventRate } from './useEventRate.js';
10
11
  export { takeSnapshot } from './takeSnapshot.js';
11
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,WAAW,GAGZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAA0B,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,WAAW,GAGZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAA0B,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { LiveSource, RollingWindow, SeriesSchema } from 'pond-ts';
2
+ import type { UseSnapshotOptions } from './useSnapshot.js';
3
+ /**
4
+ * `LiveSource` that supports `.window(duration)` — `LiveSeries`,
5
+ * `LiveView`, and accumulators.
6
+ */
7
+ type Windowable<S extends SeriesSchema> = LiveSource<S> & {
8
+ window(size: RollingWindow): LiveSource<S> & {
9
+ count(): number;
10
+ eventRate(): number;
11
+ on(type: 'event', fn: (...args: any[]) => void): () => void;
12
+ };
13
+ };
14
+ /**
15
+ * Subscribe to a live source, maintain a `.window(duration)` view
16
+ * over it, and return its events-per-second as a reactive number.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const eventRate = useEventRate(liveSeries, '1m');
21
+ * // <div>EVENT RATE {eventRate.toFixed(1)}/s</div>
22
+ * ```
23
+ *
24
+ * Sugar over `live.window(duration).eventRate()` plus a subscription
25
+ * that re-reads on each push and throttles updates. Closes the
26
+ * boilerplate that the gRPC experiment's M1 friction notes
27
+ * surfaced — `useCurrent(live, { cpu: 'count' }, { tail: '1m' }).cpu / 60`
28
+ * collapses to one hook.
29
+ *
30
+ * Throttling matches `useSnapshot`: `throttle: 100` ms by default.
31
+ * Returns `0` until the first event lands.
32
+ *
33
+ * @throws TypeError if `windowDuration` is a count-based window (a
34
+ * number) — `eventRate` requires a time denominator. Pass a
35
+ * duration string like `'1m'`, `'30s'`, or a number-of-ms via
36
+ * `DurationInput` instead.
37
+ */
38
+ export declare function useEventRate<S extends SeriesSchema>(source: Windowable<S>, windowDuration: RollingWindow, options?: UseSnapshotOptions): number;
39
+ export {};
40
+ //# sourceMappingURL=useEventRate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEventRate.d.ts","sourceRoot":"","sources":["../src/useEventRate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D;;;GAGG;AACH,KAAK,UAAU,CAAC,CAAC,SAAS,YAAY,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG;IACxD,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG;QAC3C,KAAK,IAAI,MAAM,CAAC;QAChB,SAAS,IAAI,MAAM,CAAC;QACpB,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;KAC7D,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,YAAY,EACjD,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EACrB,cAAc,EAAE,aAAa,EAC7B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,MAAM,CAuDR"}
@@ -0,0 +1,75 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ /**
3
+ * Subscribe to a live source, maintain a `.window(duration)` view
4
+ * over it, and return its events-per-second as a reactive number.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const eventRate = useEventRate(liveSeries, '1m');
9
+ * // <div>EVENT RATE {eventRate.toFixed(1)}/s</div>
10
+ * ```
11
+ *
12
+ * Sugar over `live.window(duration).eventRate()` plus a subscription
13
+ * that re-reads on each push and throttles updates. Closes the
14
+ * boilerplate that the gRPC experiment's M1 friction notes
15
+ * surfaced — `useCurrent(live, { cpu: 'count' }, { tail: '1m' }).cpu / 60`
16
+ * collapses to one hook.
17
+ *
18
+ * Throttling matches `useSnapshot`: `throttle: 100` ms by default.
19
+ * Returns `0` until the first event lands.
20
+ *
21
+ * @throws TypeError if `windowDuration` is a count-based window (a
22
+ * number) — `eventRate` requires a time denominator. Pass a
23
+ * duration string like `'1m'`, `'30s'`, or a number-of-ms via
24
+ * `DurationInput` instead.
25
+ */
26
+ export function useEventRate(source, windowDuration, options) {
27
+ const throttleMs = options?.throttle ?? 100;
28
+ // Lazy initial value: take a one-shot read off a temporary view so a
29
+ // hook mounted on an already-populated source renders the correct
30
+ // rate on the first paint, not 0. The temporary view is GC'd when
31
+ // the effect creates the long-lived view; no leak because we don't
32
+ // subscribe.
33
+ const [rate, setRate] = useState(() => {
34
+ const initialView = source.window(windowDuration);
35
+ const r = initialView.eventRate();
36
+ if ('dispose' in initialView &&
37
+ typeof initialView.dispose === 'function') {
38
+ initialView.dispose();
39
+ }
40
+ return r;
41
+ });
42
+ const sourceRef = useRef(source);
43
+ sourceRef.current = source;
44
+ useEffect(() => {
45
+ const view = source.window(windowDuration);
46
+ setRate(view.eventRate());
47
+ let timer = null;
48
+ let pending = false;
49
+ const flush = () => {
50
+ timer = null;
51
+ pending = false;
52
+ setRate(view.eventRate());
53
+ };
54
+ const unsub = view.on('event', () => {
55
+ if (throttleMs === 0) {
56
+ setRate(view.eventRate());
57
+ return;
58
+ }
59
+ if (!pending) {
60
+ pending = true;
61
+ timer = setTimeout(flush, throttleMs);
62
+ }
63
+ });
64
+ return () => {
65
+ unsub();
66
+ if (timer !== null)
67
+ clearTimeout(timer);
68
+ if ('dispose' in view && typeof view.dispose === 'function') {
69
+ view.dispose();
70
+ }
71
+ };
72
+ }, [source, windowDuration, throttleMs]);
73
+ return rate;
74
+ }
75
+ //# sourceMappingURL=useEventRate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEventRate.js","sourceRoot":"","sources":["../src/useEventRate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAgBpD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAqB,EACrB,cAA6B,EAC7B,OAA4B;IAE5B,MAAM,UAAU,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC;IAC5C,qEAAqE;IACrE,kEAAkE;IAClE,kEAAkE;IAClE,mEAAmE;IACnE,aAAa;IACb,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QACpC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAClD,MAAM,CAAC,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC;QAClC,IACE,SAAS,IAAI,WAAW;YACxB,OAAQ,WAAmB,CAAC,OAAO,KAAK,UAAU,EAClD,CAAC;YACA,WAAmB,CAAC,OAAO,EAAE,CAAC;QACjC,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAE1B,IAAI,KAAK,GAAyC,IAAI,CAAC;QACvD,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,GAAG,KAAK,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClC,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,IAAI;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,SAAS,IAAI,IAAI,IAAI,OAAQ,IAAY,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBACpE,IAAY,CAAC,OAAO,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,CAAC,CAAC,CAAC;IAEzC,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
4
4
  "description": "React hooks for pond-ts live time series",
5
5
  "license": "MIT",
6
6
  "repository": {