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 +75 -5
- package/dist/batch/operators/by-value.d.ts +19 -0
- package/dist/batch/operators/by-value.js +91 -0
- package/dist/batch/operators/scan.d.ts +47 -0
- package/dist/batch/operators/scan.js +81 -0
- package/dist/batch/partitioned-time-series.d.ts +7 -1
- package/dist/batch/partitioned-time-series.js +3 -0
- package/dist/batch/time-series.d.ts +90 -1
- package/dist/batch/time-series.js +71 -0
- package/dist/batch/value-series.d.ts +53 -0
- package/dist/batch/value-series.js +105 -0
- package/dist/columnar/index.d.ts +1 -1
- package/dist/columnar/index.js +1 -1
- package/dist/columnar/key-column.d.ts +36 -2
- package/dist/columnar/key-column.js +89 -0
- package/dist/columnar/types.d.ts +8 -2
- package/dist/columnar/view.js +7 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/live/series-store.js +19 -11
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/series.d.ts +19 -0
- package/package.json +1 -1
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
package/dist/columnar/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/columnar/index.js
CHANGED
|
@@ -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`.
|
package/dist/columnar/types.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
16
|
-
|
|
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
|
package/dist/columnar/view.js
CHANGED
|
@@ -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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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 = {};
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/schema/series.d.ts
CHANGED
|
@@ -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
|