pond-ts 0.30.0 → 0.31.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.
package/CHANGELOG.md CHANGED
@@ -3,11 +3,14 @@
3
3
  All notable changes to this project are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
- `pond-ts` and `@pond-ts/react` release together under a single `v*` tag, so this
7
- file covers both packages. Pre-1.0: minor bumps may include new features and
8
- type-level changes; patch bumps are strictly additive.
9
-
10
- [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.30.0...HEAD
6
+ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`, and
7
+ `@pond-ts/fit` release together under a single `v*` tag, so this file covers
8
+ them all. Pre-1.0: minor bumps may include new features and type-level changes;
9
+ patch bumps are strictly additive.
10
+
11
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.31.1...HEAD
12
+ [0.31.1]: https://github.com/pjm17971/pond-ts/compare/v0.30.0...v0.31.1
13
+ [0.31.0]: https://github.com/pjm17971/pond-ts/compare/v0.30.0...3c4e8bd
11
14
  [0.30.0]: https://github.com/pjm17971/pond-ts/compare/v0.29.0...v0.30.0
12
15
  [0.29.0]: https://github.com/pjm17971/pond-ts/compare/v0.28.0...v0.29.0
13
16
  [0.28.0]: https://github.com/pjm17971/pond-ts/compare/v0.27.0...v0.28.0
@@ -22,6 +25,73 @@ type-level changes; patch bumps are strictly additive.
22
25
  [0.19.0]: https://github.com/pjm17971/pond-ts/compare/v0.18.0...v0.19.0
23
26
  [0.18.0]: https://github.com/pjm17971/pond-ts/compare/v0.17.1...v0.18.0
24
27
 
28
+ ## [0.31.1] — 2026-06-28
29
+
30
+ ### Fixed
31
+
32
+ - **`@pond-ts/charts` and `@pond-ts/fit` now ship their own README** on npm.
33
+ 0.31.0 inadvertently published the `pond-ts` core README on every package
34
+ (each `prepack` copied the repo-root README); charts and fit now carry their
35
+ own. No code or API changes.
36
+
37
+ ## [0.31.0] — 2026-06-28
38
+
39
+ First published release of **`@pond-ts/charts`** and **`@pond-ts/fit`** (both were
40
+ previously `private`). All four packages — `pond-ts`, `@pond-ts/react`,
41
+ `@pond-ts/charts`, `@pond-ts/fit` — now release together, lock-step, under one
42
+ `v*` tag.
43
+
44
+ ### Added — `pond-ts` (core)
45
+
46
+ - **`ValueSeries` + `TimeSeries.byValue(axis)` — the value axis as a closed
47
+ type.** `byValue` re-keys a series onto a monotonic non-time **value axis**
48
+ (distance, cumulative work, …), returning a `ValueSeries` — the value-keyed
49
+ counterpart of `TimeSeries`. It carries the ordering-based operators
50
+ (`axisValues`, `axisAt`, `column`, `nearestIndex`, `sliceByValue`); the
51
+ calendar/clock operators are deliberately absent — a value axis has no
52
+ wall-clock semantics, and the disjoint `ValueSeriesSchema` makes them
53
+ type-impossible. The axis must be **defined, finite, and non-decreasing at
54
+ every row** (it becomes the index); it is dropped from the value columns (it
55
+ is now the key) and the rest reshare zero-copy. Substrate: a new `'value'`
56
+ `KeyKind` + `ValueKeyColumn`. Projection is O(N + C); `nearestIndex` is
57
+ O(log N); `sliceByValue` is O(log N + C) zero-copy. (value-axis RFC Phase 1.)
58
+ - **`scan(source, step, init, options?)` — typed-accumulator running fold.** The
59
+ general form of `cumulative` (the classic `mapAccumL`): the accumulator `A`
60
+ (any value, seeded from `init`) is **decoupled** from the numeric `output` and
61
+ the output column. `step(acc, value, i)` returns `[nextAcc, output]`. With no
62
+ `options.output` the source column is **replaced** in place (as `cumulative`
63
+ does); with `options.output` a **new** column is appended and the source is
64
+ left intact. Missing-cell carry, stored-`NaN`, and multi-entity semantics are
65
+ inherited from `cumulative` (scope per entity with
66
+ `partitionBy(col).scan(...).collect()`). Column-native, O(N + C), no event
67
+ materialization. Enables `split = scan + byColumn` — materialize cross-bin
68
+ state (e.g. hysteresis elevation gain) into a column, then segment it with
69
+ `byColumn`'s pure, order-free reducers. (estela F-geo-2-splits; value-axis
70
+ RFC wave lead.)
71
+
72
+ ### Added — `@pond-ts/charts` (initial release)
73
+
74
+ - **First public release.** A React charting layer over pond-ts — a canvas data
75
+ plane with SVG interactive overlays. `ChartContainer` / `ChartRow` / `Layers`
76
+ composition; `LineChart`, `AreaChart`, `BarChart`, `Scatter`, `BoxPlot`;
77
+ `TimeAxis` / `YAxis` / `XAxis` (time **and** value x-axes); the cursor system
78
+ (staffed flag, per-row cursor modes); shared gap-rendering modes; and the
79
+ estela theme. Peer-depends on `pond-ts`, `@pond-ts/react`, and React 18/19.
80
+
81
+ ### Added — `@pond-ts/fit` (initial release)
82
+
83
+ - **First public release.** A fitness / activity domain library over pond-ts — the
84
+ `Activity` / `Section` façade, unit-safe quantities (`Distance` / `Speed` /
85
+ `Power` / … with `.format()`), geo / power / zones analytics, `Profile` +
86
+ `usingProfile()` → `ProfiledActivity` / `ProfiledSection`, and the `Track`
87
+ value object. Façade-first: one curated flat barrel, with the functional
88
+ operator surface kept internal. Peer-depends on `pond-ts`.
89
+
90
+ ### Changed
91
+
92
+ - **All `@pond-ts/*` peer / dependency ranges widened to `^0.31.0`** for the
93
+ lock-step release.
94
+
25
95
  ## [0.30.0] — 2026-06-17
26
96
 
27
97
  ### Added
@@ -0,0 +1,19 @@
1
+ import { ColumnarStore, type ColumnSchema } from '../../columnar/index.js';
2
+ /**
3
+ * **Column-native `byValue`** — the raw `TimeSeries → ValueSeries` projection
4
+ * (RFC `value-axis.md` §6). Re-keys the store onto the monotonic `axis` column
5
+ * (a no-op reindex: the rows already sit in axis order, since the axis is
6
+ * non-decreasing in storage order) and **drops `axis` from the value columns**
7
+ * — it is now the key, and `fromTrustedStore` rejects the duplicate name
8
+ * otherwise. The non-axis value columns + their buffers are shared by reference
9
+ * (zero-copy); only the key column is newly allocated.
10
+ *
11
+ * Returns the reshaped store + the value-keyed output schema. The schema cast
12
+ * is the trust boundary; `TimeSeries.byValue` wraps the store in a
13
+ * `ValueSeries` with the precise `ValueKeyedSchema<S, Axis>` type.
14
+ */
15
+ export declare function byValueOp(store: ColumnarStore<ColumnSchema>, schema: ColumnSchema, axis: string): {
16
+ store: ColumnarStore<ColumnSchema>;
17
+ schema: ColumnSchema;
18
+ };
19
+ //# sourceMappingURL=by-value.d.ts.map
@@ -0,0 +1,91 @@
1
+ import { ColumnarStore, Float64Column, ValueKeyColumn, } from '../../columnar/index.js';
2
+ /**
3
+ * Validates that the `axis` column is a usable value axis — **every cell
4
+ * defined + finite + non-decreasing** — and returns the `Float64Array` to key
5
+ * on. Throws otherwise.
6
+ *
7
+ * This is the monotonicity contract for `byValue`: it lives on the *projection*,
8
+ * not on `ValueKeyColumn` or `byColumn` (Codex review #1). An order-free
9
+ * value-bin aggregation has no monotonic precondition, but promoting a column to
10
+ * the *index* of a series does — and a missing/non-finite cell can't be placed
11
+ * in the ordering, so (unlike a value column, where a gap is fine) the axis must
12
+ * be dense.
13
+ *
14
+ * **Zero-copy fast path (Lever 1).** A packed {@link Float64Column} (every batch
15
+ * value column is one) already holds a contiguous backing buffer. Once the scan
16
+ * has proven `[0, n)` is dense + finite, that buffer *is* the key data, so we
17
+ * hand back a `subarray(0, n)` view rather than allocating and copying a fresh
18
+ * array — the source axis column and the new key then share it read-only (the
19
+ * same zero-copy contract as a slice). `_values.length` can exceed the logical
20
+ * length (capacity-grown columns), hence the `subarray`. A chunked column has no
21
+ * single contiguous buffer, so it falls back to materializing one.
22
+ *
23
+ * Trade-off of the reuse: the returned `subarray` view retains the source
24
+ * column's whole `ArrayBuffer` (including any capacity slack on a grown
25
+ * column), where the old copy released it. Negligible for batch (`_values`
26
+ * is sized to the column) and for a single live projection; only worth
27
+ * revisiting if a path holds many such views at once.
28
+ *
29
+ * The validation read-loop is unavoidable here (it enforces dense + finite +
30
+ * sorted). A future `{ assumeSorted }` fast path could skip it for a
31
+ * caller-guaranteed axis (e.g. a `scan`-produced cumulative distance); that's a
32
+ * trusted-construction seam, deferred until a re-projection hot path earns it.
33
+ */
34
+ function assertMonotonicAxis(store, axis) {
35
+ const col = store.columns.get(axis);
36
+ if (col === undefined) {
37
+ throw new RangeError(`byValue: unknown column '${axis}'`);
38
+ }
39
+ const n = store.length;
40
+ let prev = -Infinity;
41
+ for (let i = 0; i < n; i += 1) {
42
+ const v = col.read(i);
43
+ if (typeof v !== 'number' || !Number.isFinite(v)) {
44
+ throw new RangeError(`byValue: axis '${axis}' must be defined and finite at every row to be the index; ` +
45
+ `row ${i} is ${v === undefined ? 'missing' : String(v)}`);
46
+ }
47
+ if (v < prev) {
48
+ throw new RangeError(`byValue: axis '${axis}' must be non-decreasing; row ${i} (${v}) < previous (${prev})`);
49
+ }
50
+ prev = v;
51
+ }
52
+ // Validation passed → `[0, n)` is dense + finite. Reuse the packed backing
53
+ // buffer zero-copy; materialize only for a (rare) chunked axis column.
54
+ if (col instanceof Float64Column) {
55
+ return col._values.subarray(0, n);
56
+ }
57
+ const out = new Float64Array(n);
58
+ for (let i = 0; i < n; i += 1) {
59
+ out[i] = col.read(i);
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * **Column-native `byValue`** — the raw `TimeSeries → ValueSeries` projection
65
+ * (RFC `value-axis.md` §6). Re-keys the store onto the monotonic `axis` column
66
+ * (a no-op reindex: the rows already sit in axis order, since the axis is
67
+ * non-decreasing in storage order) and **drops `axis` from the value columns**
68
+ * — it is now the key, and `fromTrustedStore` rejects the duplicate name
69
+ * otherwise. The non-axis value columns + their buffers are shared by reference
70
+ * (zero-copy); only the key column is newly allocated.
71
+ *
72
+ * Returns the reshaped store + the value-keyed output schema. The schema cast
73
+ * is the trust boundary; `TimeSeries.byValue` wraps the store in a
74
+ * `ValueSeries` with the precise `ValueKeyedSchema<S, Axis>` type.
75
+ */
76
+ export function byValueOp(store, schema, axis) {
77
+ const values = assertMonotonicAxis(store, axis);
78
+ // `fromValidatedSubarray`, not `new ValueKeyColumn` — `assertMonotonicAxis`
79
+ // already proved finiteness, so skip the constructor's redundant finite scan.
80
+ const keyCol = ValueKeyColumn.fromValidatedSubarray(values, store.length);
81
+ // Drop the axis column from the value columns (it becomes the key).
82
+ const newColumns = new Map(store.columns);
83
+ newColumns.delete(axis);
84
+ const newSchema = [
85
+ { name: axis, kind: 'value' },
86
+ ...schema.slice(1).filter((c) => c.name !== axis),
87
+ ];
88
+ const newStore = ColumnarStore.fromTrustedStore(newSchema, keyCol, newColumns);
89
+ return { store: newStore, schema: newSchema };
90
+ }
91
+ //# sourceMappingURL=by-value.js.map
@@ -0,0 +1,47 @@
1
+ import { type ColumnarStore } from '../../columnar/index.js';
2
+ import type { SeriesSchema } from '../../schema/index.js';
3
+ /**
4
+ * The step function for {@link TimeSeries.scan} — the classic `mapAccumL`:
5
+ * given the carried accumulator, the current (defined) source value, and the
6
+ * row index, return the next accumulator and this row's numeric output. The
7
+ * accumulator type `A` is **decoupled** from the numeric output, which is what
8
+ * `cumulative` — where the accumulator *is* the output *is* a `number` — cannot
9
+ * express (e.g. hysteresis elevation gain carries `(ref, gain)` but emits only
10
+ * `gain`).
11
+ */
12
+ export type ScanStep<A> = (acc: A, value: number, index: number) => readonly [next: A, output: number];
13
+ /**
14
+ * **Column-native `scan`** — a typed-accumulator running fold, the
15
+ * generalization of `cumulativeOp`. Threads `acc: A` (any value, seeded from
16
+ * `init`) across the source column's defined cells, emits one numeric `output`
17
+ * per row, and either **replaces** the source column (`output === undefined`,
18
+ * `cumulative`'s convention) or **appends** a new column (`output` named,
19
+ * `withColumn`'s convention; the source stays intact). Reads straight off
20
+ * `Column.read(i)` — no event materialization, one pass, **O(n)**.
21
+ *
22
+ * Semantics are inherited from `cumulativeOp` so `scan` is consistent, not a
23
+ * new dialect:
24
+ * - A **defined numeric cell** calls `step(acc, value, i)`; the accumulator and
25
+ * the last-emitted output both advance.
26
+ * - A **missing / undefined cell** does *not* call `step`: the accumulator is
27
+ * carried unchanged and the row re-emits the last output, so the output holds
28
+ * flat across a gap (exactly as `cumulative`'s accumulator holds). The output
29
+ * is `undefined` only until the first defined value produces one
30
+ * (`float64ColumnFromArray` derives validity from the `undefined`s).
31
+ * - A **stored `NaN`** is a defined number — `step` is called with it, and a
32
+ * computed non-finite output lands as a defined cell. This is the
33
+ * *trusted-compute* path (matching `cumulative`), not the validated
34
+ * `withColumn` intake; the step author owns output finiteness.
35
+ *
36
+ * Returns the reshaped store + the output schema. The result-schema cast is the
37
+ * single trust boundary; `TimeSeries.scan` wraps the store via
38
+ * `#fromTrustedStore`. A non-numeric source is unreachable through the typed
39
+ * surface (`scan<Source extends NumericColumnNameForSchema<S>>`); on the
40
+ * replace path it fails fast (`withColumnReplaced`'s kind guard), matching
41
+ * `cumulative`.
42
+ */
43
+ export declare function scanOp<S extends SeriesSchema, OutSchema extends SeriesSchema, A>(store: ColumnarStore<S>, schema: S, source: string, step: ScanStep<A>, init: A, output: string | undefined): {
44
+ store: ColumnarStore<OutSchema>;
45
+ schema: OutSchema;
46
+ };
47
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { float64ColumnFromArray, withColumnAppended, withColumnReplaced, } from '../../columnar/index.js';
2
+ /**
3
+ * **Column-native `scan`** — a typed-accumulator running fold, the
4
+ * generalization of `cumulativeOp`. Threads `acc: A` (any value, seeded from
5
+ * `init`) across the source column's defined cells, emits one numeric `output`
6
+ * per row, and either **replaces** the source column (`output === undefined`,
7
+ * `cumulative`'s convention) or **appends** a new column (`output` named,
8
+ * `withColumn`'s convention; the source stays intact). Reads straight off
9
+ * `Column.read(i)` — no event materialization, one pass, **O(n)**.
10
+ *
11
+ * Semantics are inherited from `cumulativeOp` so `scan` is consistent, not a
12
+ * new dialect:
13
+ * - A **defined numeric cell** calls `step(acc, value, i)`; the accumulator and
14
+ * the last-emitted output both advance.
15
+ * - A **missing / undefined cell** does *not* call `step`: the accumulator is
16
+ * carried unchanged and the row re-emits the last output, so the output holds
17
+ * flat across a gap (exactly as `cumulative`'s accumulator holds). The output
18
+ * is `undefined` only until the first defined value produces one
19
+ * (`float64ColumnFromArray` derives validity from the `undefined`s).
20
+ * - A **stored `NaN`** is a defined number — `step` is called with it, and a
21
+ * computed non-finite output lands as a defined cell. This is the
22
+ * *trusted-compute* path (matching `cumulative`), not the validated
23
+ * `withColumn` intake; the step author owns output finiteness.
24
+ *
25
+ * Returns the reshaped store + the output schema. The result-schema cast is the
26
+ * single trust boundary; `TimeSeries.scan` wraps the store via
27
+ * `#fromTrustedStore`. A non-numeric source is unreachable through the typed
28
+ * surface (`scan<Source extends NumericColumnNameForSchema<S>>`); on the
29
+ * replace path it fails fast (`withColumnReplaced`'s kind guard), matching
30
+ * `cumulative`.
31
+ */
32
+ export function scanOp(store, schema, source, step, init, output) {
33
+ const col = store.columns.get(source);
34
+ if (col === undefined) {
35
+ throw new RangeError(`scan: unknown column '${source}'`);
36
+ }
37
+ const n = store.length;
38
+ const out = new Array(n);
39
+ let acc = init;
40
+ let last;
41
+ for (let i = 0; i < n; i += 1) {
42
+ const raw = col.read(i);
43
+ if (typeof raw === 'number') {
44
+ const r = step(acc, raw, i);
45
+ acc = r[0];
46
+ last = r[1];
47
+ }
48
+ out[i] = last;
49
+ }
50
+ const column = float64ColumnFromArray(out);
51
+ const base = store;
52
+ // Output omitted ⇒ replace the source column in place (cumulative's
53
+ // convention; the source is widened to optional `number`).
54
+ if (output === undefined) {
55
+ const result = withColumnReplaced(base, source, column);
56
+ const outSchema = Object.freeze(schema.map((c, i) => i === 0 || c.name !== source
57
+ ? c
58
+ : { ...c, kind: 'number', required: false }));
59
+ return {
60
+ store: result,
61
+ schema: outSchema,
62
+ };
63
+ }
64
+ // Output named ⇒ append a new column, leaving the source intact. The name
65
+ // must not collide with the key or an existing value column (the schema
66
+ // includes the key at index 0). `withColumnAppended` re-checks, but the
67
+ // explicit guard gives a scan-specific message pointing at the replace path.
68
+ if (schema.some((c) => c.name === output)) {
69
+ throw new RangeError(`scan: output column '${output}' already exists; omit options.output to replace the source column`);
70
+ }
71
+ const result = withColumnAppended(base, output, column);
72
+ const outSchema = Object.freeze([
73
+ ...schema,
74
+ { name: output, kind: 'number' },
75
+ ]);
76
+ return {
77
+ store: result,
78
+ schema: outSchema,
79
+ };
80
+ }
81
+ //# sourceMappingURL=scan.js.map
@@ -4,7 +4,8 @@ import { Sequence } from '../sequence/sequence.js';
4
4
  import type { DurationInput } from '../core/duration.js';
5
5
  import type { TemporalLike } from '../core/temporal.js';
6
6
  import type { BatchSampleStrategy } from '../sequence/sample.js';
7
- import type { AggregateSchema, AlignSchema, BaselineSchema, DedupeKeep, DiffSchema, EventDataForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
7
+ import type { AggregateSchema, AlignSchema, AppendColumn, BaselineSchema, DedupeKeep, DiffSchema, EventDataForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
8
+ import type { ScanStep } from './operators/scan.js';
8
9
  type SequenceLike = Sequence | BoundedSequence;
9
10
  type AlignMethod = 'hold' | 'linear';
10
11
  type AlignSample = 'begin' | 'center' | 'end';
@@ -303,6 +304,11 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
303
304
  cumulative<const Targets extends NumericColumnNameForSchema<S>>(spec: {
304
305
  [K in Targets]: 'sum' | 'max' | 'min' | 'count' | ((acc: number, value: number) => number);
305
306
  }): PartitionedTimeSeries<DiffSchema<S, Targets>, K>;
307
+ /** Per-partition `scan`. See {@link TimeSeries.scan}. */
308
+ scan<const Source extends NumericColumnNameForSchema<S>, A>(source: Source, step: ScanStep<A>, init: A): PartitionedTimeSeries<DiffSchema<S, Source>, K>;
309
+ scan<const Source extends NumericColumnNameForSchema<S>, const Name extends string, A>(source: Source, step: ScanStep<A>, init: A, options: {
310
+ output: Name;
311
+ }): PartitionedTimeSeries<AppendColumn<S, Name, 'number'>, K>;
306
312
  /** Per-partition `shift`. See {@link TimeSeries.shift}. */
307
313
  shift<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], n: number): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
308
314
  /** Per-partition `aggregate`. See {@link TimeSeries.aggregate}. */
@@ -419,6 +419,9 @@ export class PartitionedTimeSeries {
419
419
  cumulative(spec) {
420
420
  return this.rewrap(PartitionedTimeSeries.applyToSource(this.source, this.by, (g) => g.cumulative(spec)));
421
421
  }
422
+ scan(source, step, init, options) {
423
+ return this.rewrap(PartitionedTimeSeries.applyToSource(this.source, this.by, (g) => g.scan(source, step, init, options)));
424
+ }
422
425
  /** Per-partition `shift`. See {@link TimeSeries.shift}. */
423
426
  shift(columns, n) {
424
427
  return this.rewrap(PartitionedTimeSeries.applyToSource(this.source, this.by, (g) => g.shift(columns, n)));
@@ -1,5 +1,7 @@
1
1
  import type { AlignSchema, MaterializeSchema, ArrayAggregateAppendSchema, ArrayAggregateReplaceSchema, ArrayColumnNameForSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, BaselineSchema, AggregateReducer, AggregateSchema, AppendColumn, CollapseSchema, EventDataForSchema, EventForSchema, FirstColKind, IntervalKeyedSchema, JsonRowFormat, JoinManySchema, JoinSchema, JoinType, NumericColumnNameForSchema, NormalizedObjectRow, NormalizedRowForSchema, PivotByGroupSchema, PointRowForSchema, PrefixedJoinManySchema, PrefixedJoinSchema, ReduceResult, RenameMap, ValidatedAggregateMap } from '../schema/index.js';
2
- import type { RenameSchema, RollingAlignment, RollingSchema, ColumnValue, DedupeKeep, DiffSchema, FillMapping, FillStrategy, ScalarKind, ScalarValue, SmoothMethod, SmoothAppendSchema, SmoothSchema, SelectSchema, SeriesSchema, TimeKeyedSchema, TimeSeriesJsonInput, TimeSeriesInput, TimeRangeKeyedSchema, ValueColumnKindForName, ValueColumnNameForSchema, ValueColumnsForSchema } from '../schema/index.js';
2
+ import type { RenameSchema, RollingAlignment, RollingSchema, ColumnValue, DedupeKeep, DiffSchema, FillMapping, FillStrategy, ScalarKind, ScalarValue, SmoothMethod, SmoothAppendSchema, SmoothSchema, SelectSchema, SeriesSchema, TimeKeyedSchema, TimeSeriesJsonInput, TimeSeriesInput, TimeRangeKeyedSchema, ValueColumnKindForName, ValueColumnNameForSchema, ValueColumnsForSchema, ValueKeyedSchema } from '../schema/index.js';
3
+ import { type ScanStep } from './operators/scan.js';
4
+ import { ValueSeries } from './value-series.js';
3
5
  import { type BinSpec } from './by-column.js';
4
6
  import { type WindowSpec } from './rolling-by-column.js';
5
7
  import { BoundedSequence } from '../sequence/bounded-sequence.js';
@@ -521,6 +523,29 @@ export declare class TimeSeries<S extends SeriesSchema> {
521
523
  */
522
524
  reduce(column: ValueColumnsForSchema<S>[number]['name'], reducer: AggregateReducer): ColumnValue | undefined;
523
525
  reduce<const Mapping extends ValidatedAggregateMap<S, Mapping>>(mapping: Mapping): ReduceResult<S, Mapping>;
526
+ /**
527
+ * Example: `track.byValue('cumDist')`.
528
+ *
529
+ * **The raw `TimeSeries → ValueSeries` projection** (RFC `value-axis.md` §6):
530
+ * re-key the series onto a monotonic numeric **value axis** (distance,
531
+ * cumulative work, …), returning a {@link ValueSeries} that carries the
532
+ * ordering-based operators (axis read, nearest-by-value, slice-by-value) over
533
+ * that axis instead of time.
534
+ *
535
+ * `axis` must be **defined, finite, and non-decreasing at every row** — it
536
+ * becomes the index, so (unlike a value column) it cannot have gaps; an
537
+ * `assertMonotonicAxis` check throws otherwise. This monotonicity contract
538
+ * lives on the *projection*, not on {@link TimeSeries.byColumn} (whose
539
+ * order-free binning has no such precondition). The re-key is a no-op reindex
540
+ * (the rows already sit in axis order) and the axis column is **dropped from
541
+ * the value columns** (it is now the key); the other columns are shared
542
+ * zero-copy.
543
+ *
544
+ * `byValue` is the projection; `byColumn` is value-axis *aggregation* — pair
545
+ * them via {@link TimeSeries.scan} for stateful splits
546
+ * (`split = scan + byColumn`).
547
+ */
548
+ byValue<const Axis extends NumericColumnNameForSchema<S>>(axis: Axis): Axis extends Axis ? ValueSeries<ValueKeyedSchema<S, Axis>> : never;
524
549
  /**
525
550
  * Example:
526
551
  * `series.byColumn('cumDist', { width: 1000 }, { gain: { from: 'ele', using: 'sum' } })`.
@@ -792,6 +817,56 @@ export declare class TimeSeries<S extends SeriesSchema> {
792
817
  cumulative<const Targets extends NumericColumnNameForSchema<S>>(spec: {
793
818
  [K in Targets]: 'sum' | 'max' | 'min' | 'count' | ((acc: number, value: number) => number);
794
819
  }): TimeSeries<DiffSchema<S, Targets>>;
820
+ /**
821
+ * Example:
822
+ * ```ts
823
+ * // running sum — the cumulative special case, replacing in place:
824
+ * series.scan('work', (acc, v) => [acc + v, acc + v], 0);
825
+ *
826
+ * // typed accumulator into a NEW column (hysteresis elevation gain):
827
+ * track.scan<'cumGain', { ref: number | null; gain: number }>(
828
+ * 'ele',
829
+ * (acc, ele) => {
830
+ * if (acc.ref === null) return [{ ref: ele, gain: 0 }, 0];
831
+ * const d = ele - acc.ref;
832
+ * if (d >= 3) return [{ ref: ele, gain: acc.gain + d }, acc.gain + d];
833
+ * if (d <= -3) return [{ ref: ele, gain: acc.gain }, acc.gain];
834
+ * return [acc, acc.gain]; // within deadband — carry
835
+ * },
836
+ * { ref: null, gain: 0 },
837
+ * { output: 'cumGain' },
838
+ * );
839
+ * ```
840
+ *
841
+ * **Typed-accumulator running fold** (the classic `mapAccumL`) over a numeric
842
+ * column. `step(acc, value, i)` returns `[nextAcc, output]`; the accumulator
843
+ * `A` (inferred from `init`) is **decoupled** from the numeric `output` — the
844
+ * generalization {@link TimeSeries.cumulative} can't express, since there the
845
+ * accumulator *is* the output *is* a `number`. `cumulative` is the scalar
846
+ * special case: `series.cumulative({ x: 'sum' })` is
847
+ * `series.scan('x', (a, v) => [a + v, a + v], 0)`.
848
+ *
849
+ * With no `options.output` the source column is **replaced** in place (widened
850
+ * to optional `number`, as `cumulative` does). With `options.output` a **new**
851
+ * column of that name is appended and the source is left intact (as
852
+ * {@link TimeSeries.withColumn} does); the name must not already exist.
853
+ *
854
+ * **Missing cells carry:** a missing / undefined source cell does not call
855
+ * `step` — the accumulator is held and the row re-emits the last output (so it
856
+ * holds flat across a gap), `undefined` only until the first defined value. A
857
+ * stored `NaN` is a defined number and is passed to `step`; the step author
858
+ * owns output finiteness (this is the trusted-compute path, not the validated
859
+ * `withColumn` intake).
860
+ *
861
+ * **Multi-entity series:** the accumulator threads across all rows in storage
862
+ * order, so it interleaves across entities — `host-A`'s next row folds on top
863
+ * of `host-B`'s. Use `series.partitionBy(col).scan(...).collect()` to scope
864
+ * per entity. See {@link TimeSeries.partitionBy}.
865
+ */
866
+ scan<const Source extends NumericColumnNameForSchema<S>, A>(source: Source, step: ScanStep<A>, init: A): TimeSeries<DiffSchema<S, Source>>;
867
+ scan<const Source extends NumericColumnNameForSchema<S>, const Name extends string, A>(source: Source, step: ScanStep<A>, init: A, options: {
868
+ output: Name;
869
+ }): TimeSeries<AppendColumn<S, Name, 'number'>>;
795
870
  /**
796
871
  * Example: `series.shift("value", 1)`.
797
872
  * Lags column values by N events (positive N) or leads them (negative N).
@@ -1089,6 +1164,20 @@ export declare class TimeSeries<S extends SeriesSchema> {
1089
1164
  atOrBefore(key: KeyLike): EventForSchema<S> | undefined;
1090
1165
  /** Example: `series.atOrAfter(new Time(Date.now()))`. Returns the event with the exact key or the nearest later event, if any. */
1091
1166
  atOrAfter(key: KeyLike): EventForSchema<S> | undefined;
1167
+ /**
1168
+ * Example: `series.nearest(new Time(Date.now()))`. Returns the event whose key
1169
+ * is **closest** to `key` by `begin()` distance, or `undefined` only when the
1170
+ * series is empty.
1171
+ *
1172
+ * Where `atOrBefore` / `atOrAfter` bound to one side, this rounds to the nearer
1173
+ * neighbour (ties go to the earlier event). A key outside the series resolves
1174
+ * to the first or last event — the nearest that exists — so callers that want
1175
+ * "no match past the data" should range-check against {@link timeRange}.
1176
+ *
1177
+ * O(log N) via `bisect`; the columnar key buffer is probed by `begin()`, with
1178
+ * no Event allocation beyond the single result.
1179
+ */
1180
+ nearest(key: KeyLike): EventForSchema<S> | undefined;
1092
1181
  /** Example: `series.timeRange()`. Returns the overall temporal extent of the series, if the series is not empty. */
1093
1182
  timeRange(): TimeRange | undefined;
1094
1183
  /** Example: `series.overlaps(range)`. Returns `true` when the overall series extent overlaps the supplied temporal value. */
@@ -1,5 +1,8 @@
1
1
  import { isAggregateOutputSpec, normalizeAggregateColumns, tryAggregateColumnarTimeKeyed, } from './aggregate-columns.js';
2
2
  import { cumulativeOp, } from './operators/cumulative.js';
3
+ import { scanOp } from './operators/scan.js';
4
+ import { byValueOp } from './operators/by-value.js';
5
+ import { ValueSeries } from './value-series.js';
3
6
  import { diffRateOp } from './operators/diff-rate.js';
4
7
  import { fillOp } from './operators/fill.js';
5
8
  import { mapOp } from './operators/map.js';
@@ -1377,6 +1380,39 @@ export class TimeSeries {
1377
1380
  }
1378
1381
  return result;
1379
1382
  }
1383
+ /**
1384
+ * Example: `track.byValue('cumDist')`.
1385
+ *
1386
+ * **The raw `TimeSeries → ValueSeries` projection** (RFC `value-axis.md` §6):
1387
+ * re-key the series onto a monotonic numeric **value axis** (distance,
1388
+ * cumulative work, …), returning a {@link ValueSeries} that carries the
1389
+ * ordering-based operators (axis read, nearest-by-value, slice-by-value) over
1390
+ * that axis instead of time.
1391
+ *
1392
+ * `axis` must be **defined, finite, and non-decreasing at every row** — it
1393
+ * becomes the index, so (unlike a value column) it cannot have gaps; an
1394
+ * `assertMonotonicAxis` check throws otherwise. This monotonicity contract
1395
+ * lives on the *projection*, not on {@link TimeSeries.byColumn} (whose
1396
+ * order-free binning has no such precondition). The re-key is a no-op reindex
1397
+ * (the rows already sit in axis order) and the axis column is **dropped from
1398
+ * the value columns** (it is now the key); the other columns are shared
1399
+ * zero-copy.
1400
+ *
1401
+ * `byValue` is the projection; `byColumn` is value-axis *aggregation* — pair
1402
+ * them via {@link TimeSeries.scan} for stateful splits
1403
+ * (`split = scan + byColumn`).
1404
+ */
1405
+ byValue(axis) {
1406
+ // The return type is **distributive** over `Axis` (`Axis extends Axis ?`):
1407
+ // for a literal axis it is just `ValueSeries<ValueKeyedSchema<S, Axis>>`,
1408
+ // but for a union axis (e.g. a generic wrapper's `'cumDist' | 'hr'`) it
1409
+ // becomes the discriminated union `ValueSeries<…cumDist> | ValueSeries<…hr>`
1410
+ // — each branch drops only its own axis, so narrowing on `axisName` recovers
1411
+ // the right `column()` names. Without distribution, `ValueKeyedSchema` would
1412
+ // drop *every* union member from the value columns.
1413
+ const { store, schema } = byValueOp(this.#store.store, this.schema, axis);
1414
+ return ValueSeries.fromTrustedStore(this.name, schema, store);
1415
+ }
1380
1416
  /**
1381
1417
  * Example:
1382
1418
  * `series.byColumn('cumDist', { width: 1000 }, { gain: { from: 'ele', using: 'sum' } })`.
@@ -1680,6 +1716,13 @@ export class TimeSeries {
1680
1716
  const { store, schema } = cumulativeOp(this.#store.store, this.schema, spec);
1681
1717
  return TimeSeries.#fromTrustedStore(this.name, schema, store);
1682
1718
  }
1719
+ scan(source, step, init, options) {
1720
+ // Column-native: the typed-accumulator fold runs straight off the store's
1721
+ // source column in the extracted `scanOp` — no `this.events`
1722
+ // materialization. The method is a thin delegate.
1723
+ const { store, schema } = scanOp(this.#store.store, this.schema, source, step, init, options?.output);
1724
+ return TimeSeries.#fromTrustedStore(this.name, schema, store);
1725
+ }
1683
1726
  /**
1684
1727
  * Example: `series.shift("value", 1)`.
1685
1728
  * Lags column values by N events (positive N) or leads them (negative N).
@@ -2603,6 +2646,34 @@ export class TimeSeries {
2603
2646
  atOrAfter(key) {
2604
2647
  return this.at(this.bisect(key));
2605
2648
  }
2649
+ /**
2650
+ * Example: `series.nearest(new Time(Date.now()))`. Returns the event whose key
2651
+ * is **closest** to `key` by `begin()` distance, or `undefined` only when the
2652
+ * series is empty.
2653
+ *
2654
+ * Where `atOrBefore` / `atOrAfter` bound to one side, this rounds to the nearer
2655
+ * neighbour (ties go to the earlier event). A key outside the series resolves
2656
+ * to the first or last event — the nearest that exists — so callers that want
2657
+ * "no match past the data" should range-check against {@link timeRange}.
2658
+ *
2659
+ * O(log N) via `bisect`; the columnar key buffer is probed by `begin()`, with
2660
+ * no Event allocation beyond the single result.
2661
+ */
2662
+ nearest(key) {
2663
+ const n = this.#store.length;
2664
+ if (n === 0)
2665
+ return undefined;
2666
+ const normalizedKey = toKey(key);
2667
+ const index = this.bisect(normalizedKey); // first index with keyAt(i) >= key
2668
+ if (index <= 0)
2669
+ return this.at(0);
2670
+ if (index >= n)
2671
+ return this.at(n - 1);
2672
+ const target = normalizedKey.begin();
2673
+ const before = this.#store.keyAt(index - 1).begin();
2674
+ const after = this.#store.keyAt(index).begin();
2675
+ return this.at(target - before <= after - target ? index - 1 : index);
2676
+ }
2606
2677
  /** Example: `series.timeRange()`. Returns the overall temporal extent of the series, if the series is not empty. */
2607
2678
  timeRange() {
2608
2679
  // Columnar key-axis read. The old implementation reduced over
@@ -0,0 +1,53 @@
1
+ import { type Column as ColumnarColumn } from '../columnar/index.js';
2
+ import type { ValueSeriesColumnName, ValueSeriesSchema } from '../schema/index.js';
3
+ /**
4
+ * A **value-keyed series** — the closed value-axis counterpart of
5
+ * `TimeSeries`. Its key is a monotonic non-time axis (distance, cumulative
6
+ * work, …) rather than time, produced by `TimeSeries.byValue(axis)`.
7
+ *
8
+ * `ValueSeries` carries the **ordering-based** operators (read the axis, read
9
+ * value columns, nearest-by-value, slice-by-value) — the part of the series
10
+ * algebra that was never really about time (RFC `value-axis.md` §5). The
11
+ * calendar/clock operators (`Sequence.every`, tz formatting) are deliberately
12
+ * absent: a value axis has no wall-clock semantics, and the disjoint
13
+ * `ValueSeriesSchema` makes them type-impossible here.
14
+ *
15
+ * Minimal by design (RFC §7: adopt the type early, grow the algebra as a second
16
+ * value-axis consumer earns it). Wraps the columnar store directly — a value
17
+ * row is an `(axis, …values)` tuple, not a `Time`-keyed `Event`, so it does not
18
+ * go through the time-only `SeriesStore` / EventKey layer.
19
+ */
20
+ export declare class ValueSeries<VS extends ValueSeriesSchema> {
21
+ #private;
22
+ readonly name: string;
23
+ readonly schema: VS;
24
+ private constructor();
25
+ /** Number of rows. */
26
+ get length(): number;
27
+ /** The axis (key) column's name — e.g. `'cumDist'`. */
28
+ get axisName(): VS[0]['name'];
29
+ /**
30
+ * The axis values (the x of every row), in axis order. **Zero-copy** — the
31
+ * returned `Float64Array` is the live key buffer; treat it as read-only.
32
+ */
33
+ axisValues(): Float64Array;
34
+ /** The axis value at row `i`. Throws if out of range. */
35
+ axisAt(i: number): number;
36
+ /** A value column by name, for direct columnar reads (`.read(i)`, `.values()`). */
37
+ column(name: ValueSeriesColumnName<VS>): ColumnarColumn | undefined;
38
+ /**
39
+ * Index of the row whose axis value is **closest** to `value` — the
40
+ * value-axis cursor primitive. The axis is non-decreasing, so this is a
41
+ * binary search. Returns `-1` for an empty series; clamps to the first / last
42
+ * row when `value` is outside the axis extent.
43
+ */
44
+ nearestIndex(value: number): number;
45
+ /**
46
+ * The contiguous sub-series whose axis value lies in `[lo, hi)` — the
47
+ * value-axis cull (pan / zoom on a value x). Binary-searches the bounds and
48
+ * zero-copy slices the store. `lo >= hi` (or a range outside the extent)
49
+ * yields an empty series.
50
+ */
51
+ sliceByValue(lo: number, hi: number): ValueSeries<VS>;
52
+ }
53
+ //# sourceMappingURL=value-series.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { withRowRange, } from '../columnar/index.js';
2
+ /**
3
+ * A **value-keyed series** — the closed value-axis counterpart of
4
+ * `TimeSeries`. Its key is a monotonic non-time axis (distance, cumulative
5
+ * work, …) rather than time, produced by `TimeSeries.byValue(axis)`.
6
+ *
7
+ * `ValueSeries` carries the **ordering-based** operators (read the axis, read
8
+ * value columns, nearest-by-value, slice-by-value) — the part of the series
9
+ * algebra that was never really about time (RFC `value-axis.md` §5). The
10
+ * calendar/clock operators (`Sequence.every`, tz formatting) are deliberately
11
+ * absent: a value axis has no wall-clock semantics, and the disjoint
12
+ * `ValueSeriesSchema` makes them type-impossible here.
13
+ *
14
+ * Minimal by design (RFC §7: adopt the type early, grow the algebra as a second
15
+ * value-axis consumer earns it). Wraps the columnar store directly — a value
16
+ * row is an `(axis, …values)` tuple, not a `Time`-keyed `Event`, so it does not
17
+ * go through the time-only `SeriesStore` / EventKey layer.
18
+ */
19
+ export class ValueSeries {
20
+ name;
21
+ schema;
22
+ #store;
23
+ /**
24
+ * @internal Trusted construction — `store` must be value-keyed and structurally
25
+ * match `schema` (the invariant `TimeSeries.byValue` / `byValueOp` establish).
26
+ * Not for general use; construct a `ValueSeries` via `TimeSeries.byValue`.
27
+ */
28
+ static fromTrustedStore(name, schema, store) {
29
+ return new ValueSeries(name, schema, store);
30
+ }
31
+ constructor(name, schema, store) {
32
+ this.name = name;
33
+ this.schema = Object.freeze(schema.slice());
34
+ this.#store = store;
35
+ }
36
+ /** Number of rows. */
37
+ get length() {
38
+ return this.#store.length;
39
+ }
40
+ /** The axis (key) column's name — e.g. `'cumDist'`. */
41
+ get axisName() {
42
+ return this.schema[0].name;
43
+ }
44
+ /**
45
+ * The axis values (the x of every row), in axis order. **Zero-copy** — the
46
+ * returned `Float64Array` is the live key buffer; treat it as read-only.
47
+ */
48
+ axisValues() {
49
+ return this.#store.keys.begin;
50
+ }
51
+ /** The axis value at row `i`. Throws if out of range. */
52
+ axisAt(i) {
53
+ return this.#store.keys.beginAt(i);
54
+ }
55
+ /** A value column by name, for direct columnar reads (`.read(i)`, `.values()`). */
56
+ column(name) {
57
+ return this.#store.columns.get(name);
58
+ }
59
+ /**
60
+ * Index of the row whose axis value is **closest** to `value` — the
61
+ * value-axis cursor primitive. The axis is non-decreasing, so this is a
62
+ * binary search. Returns `-1` for an empty series; clamps to the first / last
63
+ * row when `value` is outside the axis extent.
64
+ */
65
+ nearestIndex(value) {
66
+ const n = this.length;
67
+ if (n === 0)
68
+ return -1;
69
+ const ax = this.axisValues();
70
+ const lo = lowerBound(ax, n, value);
71
+ if (lo === 0)
72
+ return 0;
73
+ if (lo === n)
74
+ return n - 1;
75
+ return value - ax[lo - 1] <= ax[lo] - value ? lo - 1 : lo;
76
+ }
77
+ /**
78
+ * The contiguous sub-series whose axis value lies in `[lo, hi)` — the
79
+ * value-axis cull (pan / zoom on a value x). Binary-searches the bounds and
80
+ * zero-copy slices the store. `lo >= hi` (or a range outside the extent)
81
+ * yields an empty series.
82
+ */
83
+ sliceByValue(lo, hi) {
84
+ const ax = this.axisValues();
85
+ const n = this.length;
86
+ const loIdx = lowerBound(ax, n, lo);
87
+ const hiIdx = lowerBound(ax, n, hi);
88
+ const sliced = withRowRange(this.#store, loIdx, hiIdx);
89
+ return ValueSeries.fromTrustedStore(this.name, this.schema, sliced);
90
+ }
91
+ }
92
+ /** First index `i` in `ax[0..n)` with `ax[i] >= target` (lower bound). */
93
+ function lowerBound(ax, n, target) {
94
+ let lo = 0;
95
+ let hi = n;
96
+ while (lo < hi) {
97
+ const mid = (lo + hi) >>> 1;
98
+ if (ax[mid] < target)
99
+ lo = mid + 1;
100
+ else
101
+ hi = mid;
102
+ }
103
+ return lo;
104
+ }
105
+ //# sourceMappingURL=value-series.js.map
@@ -33,7 +33,7 @@ export { type ColumnarRingBufferOptions, ColumnarRingBuffer, } from './ring-buff
33
33
  export { type OnUndefinedPartition, type ScatterByPartitionOptions, scatterByPartition, } from './scatter.js';
34
34
  export { DICT_ENCODE_MIN_LENGTH, DICT_ENCODE_RATIO, StringColumn, buildDictionaryIndex, estimateDictionaryBytes, remapColumnToDictionary, remapIndicesToDictionary, stringColumnDictEncoded, stringColumnFallback, stringColumnFromArray, } from './string-column.js';
35
35
  export { ArrayColumn, EMPTY_ARRAY_SENTINEL, arrayColumnFromArray, } from './array-column.js';
36
- export { type IntervalLabelKind, type KeyColumn, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, timeKeyColumnFromArray, timeRangeKeyColumnFromPairs, } from './key-column.js';
36
+ export { type IntervalLabelKind, type KeyColumn, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, ValueKeyColumn, timeKeyColumnFromArray, timeRangeKeyColumnFromPairs, valueKeyColumnFromArray, } from './key-column.js';
37
37
  export { type FromTrustedStoreOptions, ColumnarStore } from './store.js';
38
38
  export { type ColumnBuilder, ArrayColumnBuilder, BooleanColumnBuilder, Float64ColumnBuilder, StringColumnBuilder, columnBuilderForKind, } from './builder.js';
39
39
  export { type AnyColumnKind, type ArrayValue, type ColumnDef, type ColumnSchema, type KeyKind, type ScalarValue, } from './types.js';
@@ -33,7 +33,7 @@ export { ColumnarRingBuffer, } from './ring-buffer.js';
33
33
  export { scatterByPartition, } from './scatter.js';
34
34
  export { DICT_ENCODE_MIN_LENGTH, DICT_ENCODE_RATIO, StringColumn, buildDictionaryIndex, estimateDictionaryBytes, remapColumnToDictionary, remapIndicesToDictionary, stringColumnDictEncoded, stringColumnFallback, stringColumnFromArray, } from './string-column.js';
35
35
  export { ArrayColumn, EMPTY_ARRAY_SENTINEL, arrayColumnFromArray, } from './array-column.js';
36
- export { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, timeKeyColumnFromArray, timeRangeKeyColumnFromPairs, } from './key-column.js';
36
+ export { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, ValueKeyColumn, timeKeyColumnFromArray, timeRangeKeyColumnFromPairs, valueKeyColumnFromArray, } from './key-column.js';
37
37
  export { ColumnarStore } from './store.js';
38
38
  export { ArrayColumnBuilder, BooleanColumnBuilder, Float64ColumnBuilder, StringColumnBuilder, columnBuilderForKind, } from './builder.js';
39
39
  export { materialize, withColumnAppended, withColumnReplaced, withColumnsRenamed, withColumnsSelected, withKeyColumn, withRowRange, withRowSelection, } from './view.js';
@@ -33,13 +33,13 @@
33
33
  import { Float64Column } from './column.js';
34
34
  import { StringColumn } from './string-column.js';
35
35
  /** The framework's key-column discriminated union. */
36
- export type KeyColumn = TimeKeyColumn | TimeRangeKeyColumn | IntervalKeyColumn;
36
+ export type KeyColumn = TimeKeyColumn | TimeRangeKeyColumn | IntervalKeyColumn | ValueKeyColumn;
37
37
  /**
38
38
  * Shared interface implemented by every key-column class. Pure
39
39
  * indexed buffer access; the framework knows nothing about
40
40
  * `EventKey` / `Time` / `TimeRange` / `Interval`.
41
41
  */
42
- interface KeyColumnBase<K extends 'time' | 'timeRange' | 'interval'> {
42
+ interface KeyColumnBase<K extends 'time' | 'timeRange' | 'interval' | 'value'> {
43
43
  readonly kind: K;
44
44
  /** Row count. */
45
45
  readonly length: number;
@@ -97,6 +97,34 @@ export declare class TimeKeyColumn implements KeyColumnBase<'time'> {
97
97
  */
98
98
  sliceByIndices(indices: Int32Array): TimeKeyColumn;
99
99
  }
100
+ /**
101
+ * Point key on a **value axis** (distance, cumulative work, …) rather than
102
+ * time — the substrate of a `ValueSeries`. Structurally identical to
103
+ * {@link TimeKeyColumn} (single `Float64Array`, `end === begin`); only the
104
+ * `kind` tag differs, which is what gates the calendar/clock operators
105
+ * (`Sequence.every`, tz tick formatting) off a value-keyed series.
106
+ *
107
+ * The buffer carries finite axis values, not epoch milliseconds. **Ordering
108
+ * (non-decreasing) is NOT enforced here** — the monotonicity contract lives on
109
+ * the `byValue` projection (`assertMonotonicAxis`), so the column stays a dumb
110
+ * indexed buffer (matching how the time columns don't self-validate sort
111
+ * order). Finiteness *is* enforced: a `NaN` / `Infinity` axis value would break
112
+ * bisection and range logic.
113
+ */
114
+ export declare class ValueKeyColumn implements KeyColumnBase<'value'> {
115
+ readonly kind: "value";
116
+ readonly length: number;
117
+ readonly begin: Float64Array;
118
+ /** For a point value key, `end === begin` (same buffer) — a zero-width axis position. */
119
+ readonly end: Float64Array;
120
+ constructor(begin: Float64Array, length: number);
121
+ beginAt(i: number): number;
122
+ endAt(i: number): number;
123
+ /** Zero-copy index-range view. Mirrors {@link TimeKeyColumn.sliceByRange}. */
124
+ sliceByRange(start: number, end: number): ValueKeyColumn;
125
+ /** Gathers rows by index. See {@link TimeKeyColumn.sliceByIndices}. */
126
+ sliceByIndices(indices: Int32Array): ValueKeyColumn;
127
+ }
100
128
  export declare class TimeRangeKeyColumn implements KeyColumnBase<'timeRange'> {
101
129
  readonly kind: "timeRange";
102
130
  readonly length: number;
@@ -181,6 +209,12 @@ export declare class IntervalKeyColumn implements KeyColumnBase<'interval'> {
181
209
  * value columns.
182
210
  */
183
211
  export declare function timeKeyColumnFromArray(timestamps: ReadonlyArray<number>): TimeKeyColumn;
212
+ /**
213
+ * Builds a {@link ValueKeyColumn} from an array of axis values. The values
214
+ * must be finite; ordering (non-decreasing) is the `byValue` projection's
215
+ * contract, not this factory's.
216
+ */
217
+ export declare function valueKeyColumnFromArray(values: ReadonlyArray<number>): ValueKeyColumn;
184
218
  /**
185
219
  * Builds a `TimeRangeKeyColumn` from `[begin, end]` pairs. Each pair
186
220
  * must satisfy `begin <= end`.
@@ -150,6 +150,81 @@ export class TimeKeyColumn {
150
150
  }
151
151
  }
152
152
  /* -------------------------------------------------------------------------- */
153
+ /* ValueKeyColumn — single-buffer point key on a non-time value axis. */
154
+ /* -------------------------------------------------------------------------- */
155
+ /**
156
+ * Point key on a **value axis** (distance, cumulative work, …) rather than
157
+ * time — the substrate of a `ValueSeries`. Structurally identical to
158
+ * {@link TimeKeyColumn} (single `Float64Array`, `end === begin`); only the
159
+ * `kind` tag differs, which is what gates the calendar/clock operators
160
+ * (`Sequence.every`, tz tick formatting) off a value-keyed series.
161
+ *
162
+ * The buffer carries finite axis values, not epoch milliseconds. **Ordering
163
+ * (non-decreasing) is NOT enforced here** — the monotonicity contract lives on
164
+ * the `byValue` projection (`assertMonotonicAxis`), so the column stays a dumb
165
+ * indexed buffer (matching how the time columns don't self-validate sort
166
+ * order). Finiteness *is* enforced: a `NaN` / `Infinity` axis value would break
167
+ * bisection and range logic.
168
+ */
169
+ export class ValueKeyColumn {
170
+ kind = 'value';
171
+ length;
172
+ begin;
173
+ /** For a point value key, `end === begin` (same buffer) — a zero-width axis position. */
174
+ end;
175
+ constructor(begin, length) {
176
+ validateColumnLength(length, 'ValueKeyColumn');
177
+ if (length > begin.length) {
178
+ throw new RangeError(`ValueKeyColumn buffer underflow: length ${length} exceeds begin.length ${begin.length}`);
179
+ }
180
+ assertFiniteTimestamps(begin, length, 'ValueKeyColumn', 'begin');
181
+ this.length = length;
182
+ this.begin = begin;
183
+ this.end = begin;
184
+ }
185
+ beginAt(i) {
186
+ if (i < 0 || i >= this.length) {
187
+ throw new RangeError(`ValueKeyColumn.beginAt out of range: ${i} not in [0, ${this.length})`);
188
+ }
189
+ return this.begin[i];
190
+ }
191
+ endAt(i) {
192
+ return this.beginAt(i);
193
+ }
194
+ /** Zero-copy index-range view. Mirrors {@link TimeKeyColumn.sliceByRange}. */
195
+ sliceByRange(start, end) {
196
+ const lo = Math.max(0, start);
197
+ const hi = Math.min(this.length, end);
198
+ if (hi <= lo) {
199
+ return ValueKeyColumn.fromValidatedSubarray(new Float64Array(0), 0);
200
+ }
201
+ return ValueKeyColumn.fromValidatedSubarray(this.begin.subarray(lo, hi), hi - lo);
202
+ }
203
+ /**
204
+ * @internal Trusted-buffer factory for slice paths; skips the per-row
205
+ * finiteness scan. Mirrors {@link TimeKeyColumn.fromValidatedSubarray}.
206
+ */
207
+ static fromValidatedSubarray(begin, length) {
208
+ validateColumnLength(length, 'ValueKeyColumn.fromValidatedSubarray');
209
+ if (length > begin.length) {
210
+ throw new RangeError(`ValueKeyColumn.fromValidatedSubarray buffer underflow: length ${length} exceeds begin.length ${begin.length}`);
211
+ }
212
+ const c = Object.create(ValueKeyColumn.prototype);
213
+ Object.assign(c, { kind: 'value', length, begin, end: begin });
214
+ return c;
215
+ }
216
+ /** Gathers rows by index. See {@link TimeKeyColumn.sliceByIndices}. */
217
+ sliceByIndices(indices) {
218
+ const outLength = indices.length;
219
+ const out = new Float64Array(outLength);
220
+ for (let i = 0; i < outLength; i += 1) {
221
+ const idx = indices[i];
222
+ out[i] = idx >= 0 && idx < this.length ? this.begin[idx] : 0;
223
+ }
224
+ return new ValueKeyColumn(out, outLength);
225
+ }
226
+ }
227
+ /* -------------------------------------------------------------------------- */
153
228
  /* TimeRangeKeyColumn — begin + end buffers. */
154
229
  /* -------------------------------------------------------------------------- */
155
230
  export class TimeRangeKeyColumn {
@@ -455,6 +530,20 @@ export function timeKeyColumnFromArray(timestamps) {
455
530
  }
456
531
  return new TimeKeyColumn(begin, length);
457
532
  }
533
+ /**
534
+ * Builds a {@link ValueKeyColumn} from an array of axis values. The values
535
+ * must be finite; ordering (non-decreasing) is the `byValue` projection's
536
+ * contract, not this factory's.
537
+ */
538
+ export function valueKeyColumnFromArray(values) {
539
+ const length = values.length;
540
+ validateColumnLength(length, 'ValueKeyColumn');
541
+ const begin = new Float64Array(length);
542
+ for (let i = 0; i < length; i += 1) {
543
+ begin[i] = values[i];
544
+ }
545
+ return new ValueKeyColumn(begin, length);
546
+ }
458
547
  /**
459
548
  * Builds a `TimeRangeKeyColumn` from `[begin, end]` pairs. Each pair
460
549
  * must satisfy `begin <= end`.
@@ -12,8 +12,14 @@
12
12
  */
13
13
  /** Value-column kinds — the four scalar storage shapes. */
14
14
  export type ColumnKind = 'number' | 'boolean' | 'string' | 'array';
15
- /** Key-column kinds — the three temporal axis shapes. */
16
- export type KeyKind = 'time' | 'timeRange' | 'interval';
15
+ /**
16
+ * Key-column kinds. `'time'` / `'timeRange'` / `'interval'` are the temporal
17
+ * axis shapes; `'value'` is a non-time **value axis** (distance, cumulative
18
+ * work, …) — the substrate of a `ValueSeries`. The value tag is what gates the
19
+ * calendar/clock operators (`Sequence.every`, tz formatting) off a value-keyed
20
+ * series; the underlying buffer is the same finite `Float64Array`.
21
+ */
22
+ export type KeyKind = 'time' | 'timeRange' | 'interval' | 'value';
17
23
  /**
18
24
  * Any column kind — value or key. `ColumnDef`'s `kind` field is
19
25
  * widened to this so a single tuple type can describe both the
@@ -24,7 +24,7 @@
24
24
  * Framework-internal; not exported from `packages/core/src/index.ts`.
25
25
  */
26
26
  import { materializeChunkedArray, materializeChunkedBoolean, materializeChunkedFloat64, materializeChunkedString, } from './chunked-column.js';
27
- import { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, } from './key-column.js';
27
+ import { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, ValueKeyColumn, } from './key-column.js';
28
28
  import { ColumnarStore } from './store.js';
29
29
  /**
30
30
  * Returns a new `ColumnarStore` whose rows are
@@ -361,6 +361,9 @@ function sliceKeyColumnByIndices(keys, indices) {
361
361
  if (keys instanceof IntervalKeyColumn) {
362
362
  return keys.sliceByIndices(indices);
363
363
  }
364
+ if (keys instanceof ValueKeyColumn) {
365
+ return keys.sliceByIndices(indices);
366
+ }
364
367
  // Defensive fallback — exhaustiveness check.
365
368
  throw new TypeError(`withRowSelection: unrecognized KeyColumn kind '${keys.kind}'`);
366
369
  }
@@ -382,6 +385,9 @@ function sliceKeyColumnByRange(keys, start, end) {
382
385
  if (keys instanceof IntervalKeyColumn) {
383
386
  return keys.sliceByRange(start, end);
384
387
  }
388
+ if (keys instanceof ValueKeyColumn) {
389
+ return keys.sliceByRange(start, end);
390
+ }
385
391
  // Defensive fallback — exhaustiveness check.
386
392
  throw new TypeError(`withRowRange: unrecognized KeyColumn kind '${keys.kind}'`);
387
393
  }
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export { Time } from './core/time.js';
19
19
  export { TimeRange, toTimeRange } from './core/time-range.js';
20
20
  export { Sequence } from './sequence/sequence.js';
21
21
  export { TimeSeries, type KeyLike } from './batch/time-series.js';
22
+ export { ValueSeries } from './batch/value-series.js';
22
23
  export { top } from './reducers/index.js';
23
24
  export { ValidationError } from './core/errors.js';
24
25
  import './column.js';
@@ -27,9 +28,9 @@ export { type Column, type ColumnKind, type ColumnStorage, type ScanOptions, Boo
27
28
  export { StringColumn } from './columnar/string-column.js';
28
29
  export { ArrayColumn } from './columnar/array-column.js';
29
30
  export { ChunkedArrayColumn, ChunkedBooleanColumn, ChunkedFloat64Column, ChunkedStringColumn, } from './columnar/chunked-column.js';
30
- export { type IntervalLabelKind, type KeyColumn, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, } from './columnar/key-column.js';
31
+ export { type IntervalLabelKind, type KeyColumn, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, ValueKeyColumn, } from './columnar/key-column.js';
31
32
  export { type ValidityBitmap } from './columnar/validity.js';
32
- export type { AlignSchema, ArrayColumnNameForSchema, BaselineSchema, AggregateFunction, AggregateReducer, AggregateOutputMap, AggregateOutputSpec, AggregateMap, AggregateSchema, ValidatedAggregateMap, ColumnDef, CollapseData, ArrayAggregateAppendSchema, ArrayAggregateKind, ArrayAggregateReplaceSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, CollapseSchema, DedupeKeep, EventDataForSchema, EventForSchema, EventKeyForKind, FillMapping, FillStrategy, MaterializeSchema, EventKeyForSchema, FirstColKind, FirstColumn, IntervalKeyedSchema, JsonIntervalInput, JsonObjectRowForSchema, JsonRowFormat, JsonRowForSchema, JsonTimeRangeInput, JsonTimestampInput, JsonValueForKind, RollingAlignment, RollingSchema, JoinConflictMode, JoinManySchema, PrefixedJoinManySchema, PrefixedJoinSchema, LiveSource, JoinType, JoinSchema, NormalizedRowForSchema, NormalizedObjectRowForSchema, NormalizedObjectRow, NormalizedValueForKind, ReduceResult, RenameData, RenameMap, RenameSchema, RekeySchema, RowForSchema, ArrayValue, ColumnValue, ScalarKind, ScalarValue, CustomAggregateReducer, DiffSchema, NumericColumnNameForSchema, SmoothMethod, SmoothAppendSchema, SmoothSchema, SelectData, SelectSchema, SeriesSchema, TimeKeyedSchema, TimeSeriesInput, TimeSeriesJsonInput, TimeRangeKeyedSchema, ValueColumnsForSchema, ValueColumn, ValueForKind, } from './schema/index.js';
33
+ export type { AlignSchema, ArrayColumnNameForSchema, BaselineSchema, AggregateFunction, AggregateReducer, AggregateOutputMap, AggregateOutputSpec, AggregateMap, AggregateSchema, ValidatedAggregateMap, ColumnDef, CollapseData, ArrayAggregateAppendSchema, ArrayAggregateKind, ArrayAggregateReplaceSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, CollapseSchema, DedupeKeep, EventDataForSchema, EventForSchema, EventKeyForKind, FillMapping, FillStrategy, MaterializeSchema, EventKeyForSchema, FirstColKind, FirstColumn, IntervalKeyedSchema, JsonIntervalInput, JsonObjectRowForSchema, JsonRowFormat, JsonRowForSchema, JsonTimeRangeInput, JsonTimestampInput, JsonValueForKind, RollingAlignment, RollingSchema, JoinConflictMode, JoinManySchema, PrefixedJoinManySchema, PrefixedJoinSchema, LiveSource, JoinType, JoinSchema, NormalizedRowForSchema, NormalizedObjectRowForSchema, NormalizedObjectRow, NormalizedValueForKind, ReduceResult, RenameData, RenameMap, RenameSchema, RekeySchema, RowForSchema, ArrayValue, ColumnValue, ScalarKind, ScalarValue, CustomAggregateReducer, DiffSchema, NumericColumnNameForSchema, SmoothMethod, SmoothAppendSchema, SmoothSchema, SelectData, SelectSchema, SeriesSchema, TimeKeyedSchema, TimeSeriesInput, TimeSeriesJsonInput, TimeRangeKeyedSchema, ValueColumnsForSchema, ValueColumn, ValueForKind, ValueFirstColumn, ValueKeyedSchema, ValueSeriesColumnName, ValueSeriesSchema, ValueSeriesValueColumns, } from './schema/index.js';
33
34
  export type { CalendarOptions, CalendarUnit, TimeZoneOptions, } from './core/calendar.js';
34
35
  export type { EventKey, IntervalInput, IntervalValue, TemporalLike, TimeRangeInput, TimestampInput, } from './core/temporal.js';
35
36
  export type { DurationInput } from './core/duration.js';
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ export { Time } from './core/time.js';
15
15
  export { TimeRange, toTimeRange } from './core/time-range.js';
16
16
  export { Sequence } from './sequence/sequence.js';
17
17
  export { TimeSeries } from './batch/time-series.js';
18
+ export { ValueSeries } from './batch/value-series.js';
18
19
  export { top } from './reducers/index.js';
19
20
  export { ValidationError } from './core/errors.js';
20
21
  // ─── Column-centric public API (Phase 4.7 steps 8a + 8b) ────────
@@ -53,5 +54,5 @@ export { BooleanColumn, Float64Column, } from './columnar/column.js';
53
54
  export { StringColumn } from './columnar/string-column.js';
54
55
  export { ArrayColumn } from './columnar/array-column.js';
55
56
  export { ChunkedArrayColumn, ChunkedBooleanColumn, ChunkedFloat64Column, ChunkedStringColumn, } from './columnar/chunked-column.js';
56
- export { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, } from './columnar/key-column.js';
57
+ export { IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, ValueKeyColumn, } from './columnar/key-column.js';
57
58
  //# sourceMappingURL=index.js.map
@@ -296,17 +296,25 @@ function materializeKey(keys, i) {
296
296
  if (keys.kind === 'timeRange') {
297
297
  return new TimeRange({ start: keys.beginAt(i), end: keys.endAt(i) });
298
298
  }
299
- // interval
300
- const ikeys = keys;
301
- const label = ikeys.labelAt(i);
302
- if (label === undefined) {
303
- throw new Error(`SeriesStore.keyAt: row ${i} has no interval label (this should have been caught at IntervalKeyColumn construction)`);
304
- }
305
- return new Interval({
306
- value: label,
307
- start: keys.beginAt(i),
308
- end: keys.endAt(i),
309
- });
299
+ if (keys.kind === 'interval') {
300
+ const ikeys = keys;
301
+ const label = ikeys.labelAt(i);
302
+ if (label === undefined) {
303
+ throw new Error(`SeriesStore.keyAt: row ${i} has no interval label (this should have been caught at IntervalKeyColumn construction)`);
304
+ }
305
+ return new Interval({
306
+ value: label,
307
+ start: keys.beginAt(i),
308
+ end: keys.endAt(i),
309
+ });
310
+ }
311
+ // A `'value'` key (ValueSeries) has no EventKey representation — a ValueSeries
312
+ // wraps the ColumnarStore directly and never routes through SeriesStore. Fail
313
+ // loudly rather than silently mistreat a value key as an interval (which would
314
+ // call the non-existent `labelAt`). Unreachable today; a guard against a
315
+ // future path that wraps a value-keyed store in a SeriesStore.
316
+ throw new TypeError(`SeriesStore.keyAt: key kind '${keys.kind}' has no EventKey representation ` +
317
+ `(value-keyed stores are accessed via ValueSeries, not SeriesStore)`);
310
318
  }
311
319
  function buildRowData(store, i) {
312
320
  const data = {};
@@ -1,4 +1,4 @@
1
- export type { AppendColumn, ArrayColumnNameForSchema, ArrayValue, ColumnDef, ColumnValue, FirstColKind, FirstColumn, IntervalKeyedSchema, KindForValue, NormalizedValueForKind, NumericColumnNameForSchema, OptionalNumberColumn, OptionalizeColumn, OptionalizeColumns, RekeySchema, ReplaceColumnKind, RowForSchema, ScalarKind, ScalarValue, SeriesSchema, TimeKeyedSchema, TimeRangeKeyedSchema, ValueColumn, ValueColumnKindForName, ValueColumnNameForSchema, ValueColumnsForSchema, ValueForKind, } from './series.js';
1
+ export type { AppendColumn, ArrayColumnNameForSchema, ArrayValue, ColumnDef, ColumnValue, ExcludeColumnByName, FirstColKind, FirstColumn, IntervalKeyedSchema, KindForValue, NormalizedValueForKind, NumericColumnNameForSchema, OptionalNumberColumn, OptionalizeColumn, OptionalizeColumns, RekeySchema, ReplaceColumnKind, RowForSchema, ScalarKind, ScalarValue, SeriesSchema, TimeKeyedSchema, TimeRangeKeyedSchema, ValueColumn, ValueColumnKindForName, ValueColumnNameForSchema, ValueColumnsForSchema, ValueFirstColumn, ValueForKind, ValueKeyedSchema, ValueSeriesColumnName, ValueSeriesSchema, ValueSeriesValueColumns, } from './series.js';
2
2
  export type { EventDataForSchema, EventForSchema, EventKeyForKind, EventKeyForSchema, LiveSource, NormalizedObjectRow, NormalizedObjectRowForSchema, NormalizedRowForSchema, PointRowForSchema, TimeSeriesInput, } from './events.js';
3
3
  export type { JsonIntervalInput, JsonObjectRowForSchema, JsonRowForSchema, JsonRowFormat, JsonTimeRangeInput, JsonTimestampInput, JsonValueForKind, TimeSeriesJsonInput, TimeSeriesJsonOutputArray, TimeSeriesJsonOutputObject, } from './json.js';
4
4
  export type { AggregateColumns, AggregateFunction, AggregateMap, AggregateOutputMap, AggregateOutputMapResultSchema, AggregateOutputSpec, AggregateReducer, AggregateSchema, AlignSchema, ValidatedAggregateMap, ArrayAggregateAppendSchema, ArrayAggregateKind, ArrayAggregateReplaceSchema, ArrayExplodeAppendSchema, ArrayExplodeReplaceSchema, CustomAggregateReducer, MaterializeSchema, RollingOutputMapSchema, } from './aggregate.js';
@@ -92,4 +92,23 @@ export type RekeySchema<S extends SeriesSchema, First extends FirstColumn> = rea
92
92
  export type TimeKeyedSchema<S extends SeriesSchema> = RekeySchema<S, ColumnDef<'time', 'time'>>;
93
93
  export type TimeRangeKeyedSchema<S extends SeriesSchema> = RekeySchema<S, ColumnDef<'timeRange', 'timeRange'>>;
94
94
  export type IntervalKeyedSchema<S extends SeriesSchema> = RekeySchema<S, ColumnDef<'interval', 'interval'>>;
95
+ /** The first column of a `ValueSeries` — a `'value'` key with an arbitrary axis name. */
96
+ export type ValueFirstColumn = ColumnDef<string, 'value'>;
97
+ /** A value-axis-keyed schema: a `'value'` key (arbitrary name) + value columns. */
98
+ export type ValueSeriesSchema = readonly [ValueFirstColumn, ...ValueColumn[]];
99
+ /** Drops the column named `Target` from a value-column tuple. Mirrors {@link ReplaceColumnKind}. */
100
+ export type ExcludeColumnByName<Columns extends readonly ValueColumn[], Target extends string> = Columns extends readonly [infer Head, ...infer Tail] ? Head extends ValueColumn ? Tail extends readonly ValueColumn[] ? Head['name'] extends Target ? ExcludeColumnByName<Tail, Target> : [Head, ...ExcludeColumnByName<Tail, Target>] : [] : [] : [];
101
+ /**
102
+ * The schema produced by `TimeSeries.byValue(Axis)`: the named axis column
103
+ * becomes the `'value'` key, and is removed from the value columns (the key
104
+ * takes its name, so leaving it as a value column would duplicate the name).
105
+ */
106
+ export type ValueKeyedSchema<S extends SeriesSchema, Axis extends string> = readonly [
107
+ ColumnDef<Axis, 'value'>,
108
+ ...ExcludeColumnByName<ValueColumnsForSchema<S>, Axis>
109
+ ];
110
+ /** The value columns of a `ValueSeriesSchema` (everything after the axis key). */
111
+ export type ValueSeriesValueColumns<VS extends ValueSeriesSchema> = VS extends readonly [ValueFirstColumn, ...infer Rest] ? Rest extends readonly ValueColumn[] ? Rest : never : never;
112
+ /** Union of value-column names on a `ValueSeriesSchema` (for `ValueSeries.column`). */
113
+ export type ValueSeriesColumnName<VS extends ValueSeriesSchema> = ValueSeriesValueColumns<VS>[number]['name'];
95
114
  //# sourceMappingURL=series.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pond-ts",
3
- "version": "0.30.0",
3
+ "version": "0.31.1",
4
4
  "description": "TypeScript-first time series primitives",
5
5
  "license": "MIT",
6
6
  "repository": {