pond-ts 0.32.0 → 0.34.0
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 +72 -1
- package/dist/batch/time-series.d.ts +40 -0
- package/dist/batch/time-series.js +104 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`, and
|
|
|
8
8
|
them all. Pre-1.0: minor bumps may include new features and type-level changes;
|
|
9
9
|
patch bumps are strictly additive.
|
|
10
10
|
|
|
11
|
-
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.34.0...HEAD
|
|
12
|
+
[0.34.0]: https://github.com/pjm17971/pond-ts/compare/v0.33.0...v0.34.0
|
|
13
|
+
[0.33.0]: https://github.com/pjm17971/pond-ts/compare/v0.32.0...v0.33.0
|
|
12
14
|
[0.32.0]: https://github.com/pjm17971/pond-ts/compare/v0.31.2...v0.32.0
|
|
13
15
|
[0.31.2]: https://github.com/pjm17971/pond-ts/compare/v0.31.1...v0.31.2
|
|
14
16
|
[0.31.1]: https://github.com/pjm17971/pond-ts/compare/v0.30.0...v0.31.1
|
|
@@ -27,6 +29,75 @@ patch bumps are strictly additive.
|
|
|
27
29
|
[0.19.0]: https://github.com/pjm17971/pond-ts/compare/v0.18.0...v0.19.0
|
|
28
30
|
[0.18.0]: https://github.com/pjm17971/pond-ts/compare/v0.17.1...v0.18.0
|
|
29
31
|
|
|
32
|
+
## [0.34.0] — 2026-07-01
|
|
33
|
+
|
|
34
|
+
A `pond-ts` core release: the columnar/typed-array ingress driven by the
|
|
35
|
+
Tidal wire-format spike. `@pond-ts/react`, `@pond-ts/charts`, and
|
|
36
|
+
`@pond-ts/fit` carry no code changes — republished in lock-step (their
|
|
37
|
+
`pond-ts` / `@pond-ts/react` peer ranges widen to `^0.34.0`).
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`TimeSeries.fromColumns`** — the columnar (struct-of-arrays) ingress,
|
|
42
|
+
the counterpart to `fromJSON`'s row-tuple shape. Accepts either a plain
|
|
43
|
+
`number[]` or a `Float64Array` per column — one polymorphic door, so a
|
|
44
|
+
wire format only changes the *decoder*, not the ingest. `Float64Array`
|
|
45
|
+
columns are adopted directly (zero-copy); `number[]` columns are copied.
|
|
46
|
+
A `null`/`undefined` cell or a non-finite value (`NaN`/`Infinity`) is a
|
|
47
|
+
gap, identically across both input shapes. Enforces the same
|
|
48
|
+
non-decreasing key-order invariant as `fromJSON`. v1 scope: a `time`-kind
|
|
49
|
+
key and `number` value columns. (#310)
|
|
50
|
+
|
|
51
|
+
Measured against a wire-format spike (`tidal-app/pond-columnar-ingest`):
|
|
52
|
+
`fromColumns` collapses the ingest step that every non-rows path
|
|
53
|
+
previously paid (transpose → `fromJSON`) — 100k-point protobuf ingest
|
|
54
|
+
27.7ms → 2.8ms; JSON-columnar 27.0ms → 5.2ms. In a browser, decoding
|
|
55
|
+
off-main in a Web Worker and transferring the resulting `Float64Array`s
|
|
56
|
+
back keeps `fromColumns`'s adopt path on the main thread to ~9ms,
|
|
57
|
+
dropping a 500k-point ingest's worst animation-frame stall from ~50ms to
|
|
58
|
+
~9ms.
|
|
59
|
+
|
|
60
|
+
- **Docs** — a "Columnar ingest" section on the
|
|
61
|
+
[Creating series](https://pjm17971.github.io/pond-ts/docs/start-here/creating#columnar-ingest)
|
|
62
|
+
page covering both input shapes, the adopt-vs-copy/aliasing distinction,
|
|
63
|
+
missing-value and ordering semantics, and why this matters for
|
|
64
|
+
interactive charts; a pointer from Getting Started.
|
|
65
|
+
|
|
66
|
+
## [0.33.0] — 2026-06-30
|
|
67
|
+
|
|
68
|
+
A `@pond-ts/charts` release: a label opt-out for annotations plus interaction
|
|
69
|
+
fixes from an adversarial review of the #306 annotation system. `pond-ts`,
|
|
70
|
+
`@pond-ts/react`, and `@pond-ts/fit` carry no code changes — republished in
|
|
71
|
+
lock-step (their `pond-ts` / `@pond-ts/react` peer ranges widen to `^0.33.0`).
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **Charts — annotation label opt-out.** `<Region>`, `<Marker>`, and
|
|
76
|
+
`<Baseline>` accept `label={false}` (or `label=""`) to render **no label
|
|
77
|
+
chip** — for an inert background mark (e.g. a `selectable={false}` highlight
|
|
78
|
+
band) where the auto-label would only show a raw axis value. Omitting `label`
|
|
79
|
+
still auto-labels; a string still renders it. `label` widens to
|
|
80
|
+
`string | false`. New `Highlight` story + Linux e2e baselines for the
|
|
81
|
+
`Annotations` stories. (#308)
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- **Charts — `<Region>` / `<Marker>` / `<Baseline>` with `label=""` now render
|
|
86
|
+
no chip** (previously an empty, zero-width chip). The label-less path; pass a
|
|
87
|
+
non-empty string for a visible label. (#308)
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Charts — annotation edge-resize no longer inverts.** Dragging a `<Region>`
|
|
92
|
+
edge past the opposite one previously reported `{ from > to }`; it now pivots
|
|
93
|
+
around the fixed opposite edge, so the reported span stays ordered and a drag
|
|
94
|
+
either way re-opens the region instead of dead-ending at zero width. (#308)
|
|
95
|
+
- **Charts — annotation drag releases on `pointercancel`.** A system gesture
|
|
96
|
+
takeover (which fires `pointercancel`, not `pointerup`) no longer leaves a mark
|
|
97
|
+
stuck mid-drag. (#308)
|
|
98
|
+
- **Charts — single-annotation edit exits on an empty-plot click** even when the
|
|
99
|
+
mark is `editing` but not `selected`. (#308)
|
|
100
|
+
|
|
30
101
|
## [0.32.0] — 2026-06-29
|
|
31
102
|
|
|
32
103
|
A `@pond-ts/charts` release: value-axis support across the fill/bar layers, plus
|
|
@@ -87,6 +87,46 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
87
87
|
*/
|
|
88
88
|
sort?: boolean;
|
|
89
89
|
}): TimeSeries<S>;
|
|
90
|
+
/**
|
|
91
|
+
* Example: `TimeSeries.fromColumns({ name, schema, columns })`.
|
|
92
|
+
*
|
|
93
|
+
* The **columnar** ingress — the struct-of-arrays counterpart to
|
|
94
|
+
* {@link TimeSeries.fromJSON} (which takes row tuples). Each entry in
|
|
95
|
+
* `columns` is one column's values, keyed by schema column name and aligned by
|
|
96
|
+
* index; every column must have the same length. Values may be a plain
|
|
97
|
+
* `number[]` (e.g. `JSON.parse` of a columnar wire payload) **or** a
|
|
98
|
+
* `Float64Array` (e.g. protobuf packed doubles / fixed-point after descale) —
|
|
99
|
+
* one polymorphic door, so the wire format only changes the *decoder*, not the
|
|
100
|
+
* ingest. A value cell is a gap (missing) iff it's `null`/`undefined` or
|
|
101
|
+
* non-finite (`NaN`/`Infinity`) — identical rule regardless of which array
|
|
102
|
+
* type supplied it. Note this is looser than `fromJSON`, which *rejects* a
|
|
103
|
+
* non-finite provided number outright rather than treating it as a gap.
|
|
104
|
+
*
|
|
105
|
+
* Skips the row-tuple materialization + per-row intake a columnar caller
|
|
106
|
+
* otherwise pays (transpose → `fromJSON`): each column packs directly, the key
|
|
107
|
+
* column is built from the first schema column, and the store is assembled via
|
|
108
|
+
* trusted construction (`ColumnarStore.fromTrustedStore` still validates kind +
|
|
109
|
+
* aligned length).
|
|
110
|
+
*
|
|
111
|
+
* **`Float64Array` inputs are adopted, not copied** (zero-copy — the point of
|
|
112
|
+
* the fast path): the resulting series' columns alias the caller's buffers.
|
|
113
|
+
* Mutating an adopted buffer after construction mutates the series. Pass a
|
|
114
|
+
* fresh buffer (e.g. straight off a decode) if this matters; `number[]`
|
|
115
|
+
* columns are always copied.
|
|
116
|
+
*
|
|
117
|
+
* **v1 scope:** a `time`-kind key + `number` value columns (the market-data /
|
|
118
|
+
* chart wire case). Other key kinds (`interval` / `timeRange`) and non-numeric
|
|
119
|
+
* value columns throw for now — extend as consumers need.
|
|
120
|
+
*
|
|
121
|
+
* @throws ValidationError on a missing column, a length mismatch, an
|
|
122
|
+
* unsupported kind, a non-finite timestamp key, or an out-of-order
|
|
123
|
+
* (decreasing) timestamp — keys must be non-decreasing, same as `fromJSON`.
|
|
124
|
+
*/
|
|
125
|
+
static fromColumns<S extends SeriesSchema>(input: {
|
|
126
|
+
name: string;
|
|
127
|
+
schema: S;
|
|
128
|
+
columns: Record<string, ReadonlyArray<number | null | undefined> | Float64Array>;
|
|
129
|
+
}): TimeSeries<S>;
|
|
90
130
|
/**
|
|
91
131
|
* Example: `TimeSeries.fromEvents(events, { schema, name })`.
|
|
92
132
|
* Builds a typed series from an array of `Event` instances. The events
|
|
@@ -18,7 +18,8 @@ import { TimeRange } from '../core/time-range.js';
|
|
|
18
18
|
import { compareEventKeys } from '../core/temporal.js';
|
|
19
19
|
import { PartitionedTimeSeries } from './partitioned-time-series.js';
|
|
20
20
|
import { Sequence } from '../sequence/sequence.js';
|
|
21
|
-
import { ColumnarStore, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, float64ColumnFromArray, stringColumnFromArray, withColumnAppended, withColumnsRenamed, withColumnsSelected, withKeyColumn, withRowRange, withRowSelection, } from '../columnar/index.js';
|
|
21
|
+
import { ColumnarStore, Float64Column, IntervalKeyColumn, TimeKeyColumn, TimeRangeKeyColumn, float64ColumnFromArray, stringColumnFromArray, validityFromPredicate, withColumnAppended, withColumnsRenamed, withColumnsSelected, withKeyColumn, withRowRange, withRowSelection, } from '../columnar/index.js';
|
|
22
|
+
import { ValidationError } from '../core/errors.js';
|
|
22
23
|
import { SeriesStore } from '../live/series-store.js';
|
|
23
24
|
import { parseDuration } from '../core/duration.js';
|
|
24
25
|
import { resolveReducer, bucketStateFor, rollingStateFor, } from '../reducers/index.js';
|
|
@@ -485,6 +486,108 @@ export class TimeSeries {
|
|
|
485
486
|
sort: input.sort ?? false,
|
|
486
487
|
});
|
|
487
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Example: `TimeSeries.fromColumns({ name, schema, columns })`.
|
|
491
|
+
*
|
|
492
|
+
* The **columnar** ingress — the struct-of-arrays counterpart to
|
|
493
|
+
* {@link TimeSeries.fromJSON} (which takes row tuples). Each entry in
|
|
494
|
+
* `columns` is one column's values, keyed by schema column name and aligned by
|
|
495
|
+
* index; every column must have the same length. Values may be a plain
|
|
496
|
+
* `number[]` (e.g. `JSON.parse` of a columnar wire payload) **or** a
|
|
497
|
+
* `Float64Array` (e.g. protobuf packed doubles / fixed-point after descale) —
|
|
498
|
+
* one polymorphic door, so the wire format only changes the *decoder*, not the
|
|
499
|
+
* ingest. A value cell is a gap (missing) iff it's `null`/`undefined` or
|
|
500
|
+
* non-finite (`NaN`/`Infinity`) — identical rule regardless of which array
|
|
501
|
+
* type supplied it. Note this is looser than `fromJSON`, which *rejects* a
|
|
502
|
+
* non-finite provided number outright rather than treating it as a gap.
|
|
503
|
+
*
|
|
504
|
+
* Skips the row-tuple materialization + per-row intake a columnar caller
|
|
505
|
+
* otherwise pays (transpose → `fromJSON`): each column packs directly, the key
|
|
506
|
+
* column is built from the first schema column, and the store is assembled via
|
|
507
|
+
* trusted construction (`ColumnarStore.fromTrustedStore` still validates kind +
|
|
508
|
+
* aligned length).
|
|
509
|
+
*
|
|
510
|
+
* **`Float64Array` inputs are adopted, not copied** (zero-copy — the point of
|
|
511
|
+
* the fast path): the resulting series' columns alias the caller's buffers.
|
|
512
|
+
* Mutating an adopted buffer after construction mutates the series. Pass a
|
|
513
|
+
* fresh buffer (e.g. straight off a decode) if this matters; `number[]`
|
|
514
|
+
* columns are always copied.
|
|
515
|
+
*
|
|
516
|
+
* **v1 scope:** a `time`-kind key + `number` value columns (the market-data /
|
|
517
|
+
* chart wire case). Other key kinds (`interval` / `timeRange`) and non-numeric
|
|
518
|
+
* value columns throw for now — extend as consumers need.
|
|
519
|
+
*
|
|
520
|
+
* @throws ValidationError on a missing column, a length mismatch, an
|
|
521
|
+
* unsupported kind, a non-finite timestamp key, or an out-of-order
|
|
522
|
+
* (decreasing) timestamp — keys must be non-decreasing, same as `fromJSON`.
|
|
523
|
+
*/
|
|
524
|
+
static fromColumns(input) {
|
|
525
|
+
const { name, schema, columns } = input;
|
|
526
|
+
// Key column (schema[0]). v1: point-in-time (`time`) keys only.
|
|
527
|
+
const keyDef = schema[0];
|
|
528
|
+
if (keyDef === undefined) {
|
|
529
|
+
throw new ValidationError('fromColumns: schema must have at least a key column');
|
|
530
|
+
}
|
|
531
|
+
if (keyDef.kind !== 'time') {
|
|
532
|
+
throw new ValidationError(`fromColumns: v1 supports a 'time' key; schema[0] '${keyDef.name}' is '${keyDef.kind}'`);
|
|
533
|
+
}
|
|
534
|
+
const keyRaw = columns[keyDef.name];
|
|
535
|
+
if (keyRaw === undefined) {
|
|
536
|
+
throw new ValidationError(`fromColumns: missing key column '${keyDef.name}'`);
|
|
537
|
+
}
|
|
538
|
+
// epoch-ms buffer; TimeKeyColumn asserts all finite.
|
|
539
|
+
const begin = keyRaw instanceof Float64Array
|
|
540
|
+
? keyRaw
|
|
541
|
+
: Float64Array.from(keyRaw, (v) => (v == null ? NaN : Number(v)));
|
|
542
|
+
const count = begin.length;
|
|
543
|
+
// Throws on any non-finite timestamp.
|
|
544
|
+
const keys = new TimeKeyColumn(begin, count);
|
|
545
|
+
// Enforce the non-decreasing-key invariant that `fromJSON`'s
|
|
546
|
+
// `validateAndNormalize` guarantees. Trusted construction skips row
|
|
547
|
+
// materialization + kind re-validation, but NOT this correctness contract:
|
|
548
|
+
// bisect-based operators (crop, `atTime`, range queries) rely on it, so an
|
|
549
|
+
// unsorted columnar input must fail loudly here rather than build a silently
|
|
550
|
+
// broken series. One O(N) scan over already-finite values — negligible next
|
|
551
|
+
// to decode.
|
|
552
|
+
for (let j = 1; j < count; j += 1) {
|
|
553
|
+
if (begin[j] < begin[j - 1]) {
|
|
554
|
+
throw new ValidationError(`fromColumns: key column '${keyDef.name}' is out of order at index ${j} ` +
|
|
555
|
+
`(${begin[j]} < ${begin[j - 1]}) — timestamps must be non-decreasing; pre-sort the columns`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Value columns — packed directly (missing-aware) from the arrays.
|
|
559
|
+
const columnMap = new Map();
|
|
560
|
+
for (let i = 1; i < schema.length; i += 1) {
|
|
561
|
+
const def = schema[i];
|
|
562
|
+
if (def.kind !== 'number') {
|
|
563
|
+
throw new ValidationError(`fromColumns: v1 supports 'number' value columns; column '${def.name}' is '${def.kind}'`);
|
|
564
|
+
}
|
|
565
|
+
const raw = columns[def.name];
|
|
566
|
+
if (raw === undefined) {
|
|
567
|
+
throw new ValidationError(`fromColumns: missing column '${def.name}'`);
|
|
568
|
+
}
|
|
569
|
+
if (raw.length !== count) {
|
|
570
|
+
throw new ValidationError(`fromColumns: column '${def.name}' length ${raw.length} does not match key length ${count}`);
|
|
571
|
+
}
|
|
572
|
+
// Normalize to a Float64Array either way — adopt if already typed (the
|
|
573
|
+
// fast path a protobuf / fixed-point decoder hits, zero-copy), else
|
|
574
|
+
// convert (`null`/`undefined` -> `NaN`) — then apply ONE validity rule
|
|
575
|
+
// to both: a cell is a gap iff it's non-finite. This must be identical
|
|
576
|
+
// regardless of input type: an earlier version used `float64ColumnFromArray`
|
|
577
|
+
// for the `number[]` branch, which treats a `NaN` *value* (as opposed to
|
|
578
|
+
// `null`) as defined-but-non-finite rather than missing, diverging from
|
|
579
|
+
// the `Float64Array` branch's `Number.isFinite` gap signal — the same
|
|
580
|
+
// wire value would silently mean different things depending on which
|
|
581
|
+
// array type decoded it.
|
|
582
|
+
const values = raw instanceof Float64Array
|
|
583
|
+
? raw
|
|
584
|
+
: Float64Array.from(raw, (v) => (v == null ? NaN : v));
|
|
585
|
+
const validity = validityFromPredicate(count, (j) => Number.isFinite(values[j]));
|
|
586
|
+
columnMap.set(def.name, new Float64Column(values, count, validity, validity === undefined));
|
|
587
|
+
}
|
|
588
|
+
const store = ColumnarStore.fromTrustedStore(schema, keys, columnMap);
|
|
589
|
+
return TimeSeries.#fromTrustedStore(name, schema, store);
|
|
590
|
+
}
|
|
488
591
|
/**
|
|
489
592
|
* Example: `TimeSeries.fromEvents(events, { schema, name })`.
|
|
490
593
|
* Builds a typed series from an array of `Event` instances. The events
|