pond-ts 0.67.0 → 0.69.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/AGENTS.md +253 -0
- package/API.md +88 -83
- package/CHANGELOG.md +109 -1
- package/README.md +111 -9
- package/dist/batch/partitioned-time-series.d.ts +54 -20
- package/dist/batch/time-series.d.ts +2 -2
- package/dist/core/calendar.d.ts +2 -5
- package/dist/core/calendar.js +4 -50
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/core/time-zone.d.ts +122 -0
- package/dist/core/time-zone.js +413 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/sequence/sequence.d.ts +4 -1
- package/dist/sequence/sequence.js +15 -13
- package/package.json +29 -4
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
|
|
|
8
8
|
under a single `v*` tag, so this file covers them all. Pre-1.0: minor bumps may
|
|
9
9
|
include new features and type-level changes; patch bumps are strictly additive.
|
|
10
10
|
|
|
11
|
-
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.69.0...HEAD
|
|
12
|
+
[0.69.0]: https://github.com/pond-ts/pond/compare/v0.68.0...v0.69.0
|
|
13
|
+
[0.68.0]: https://github.com/pond-ts/pond/compare/v0.67.0...v0.68.0
|
|
12
14
|
[0.67.0]: https://github.com/pond-ts/pond/compare/v0.66.0...v0.67.0
|
|
13
15
|
[0.66.0]: https://github.com/pond-ts/pond/compare/v0.65.0...v0.66.0
|
|
14
16
|
[0.65.0]: https://github.com/pond-ts/pond/compare/v0.64.0...v0.65.0
|
|
@@ -70,6 +72,112 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
70
72
|
|
|
71
73
|
## [Unreleased]
|
|
72
74
|
|
|
75
|
+
## [0.69.0] — 2026-09-13
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- **`<ChartContainer timeZone>` — the time axis in any IANA zone
|
|
80
|
+
([PND-TZAXIS]).** Day / week / month ticks land on that zone's midnights,
|
|
81
|
+
Mondays and month starts; labels, the stacked date bands, the hierarchical
|
|
82
|
+
grid, session dividers and every cursor / marker / annotation readout read
|
|
83
|
+
in it. **Omitted ⇒ the viewer's zone**, exactly as before. The d3 specifier
|
|
84
|
+
strings on `timeFormat` / `cursorFormat` are unchanged; `%Z` / `%z` now read
|
|
85
|
+
the zone's abbreviation / offset. Sub-day ticks align to the zone's wall
|
|
86
|
+
clock, so a 6 h grain reads 00 / 06 / 12 / 18 across a DST jump instead of
|
|
87
|
+
drifting by an hour until the next midnight. The resolved zone is on the
|
|
88
|
+
chart context as `timeZone`. Built on core's `TimeZone` ([PND-TZCAL]), so a
|
|
89
|
+
`Sequence.calendar('day', { timeZone })` bucket edge and the tick that
|
|
90
|
+
labels it are one instant — pinned by a cross-package test.
|
|
91
|
+
- **`<XAxis timeZone>` — a second strip in another zone.** Two time axes
|
|
92
|
+
over one shared mapping, each ticking and labelling (and pilling) in its
|
|
93
|
+
own zone: `<XAxis side="top" timeZone="America/New_York" />` above a
|
|
94
|
+
UTC container's own strip below. Backed by
|
|
95
|
+
`TradingTimeScale.withTimeZone(zone)` / `.timeZone()` and an optional
|
|
96
|
+
`DiscontinuityProvider.withTimeZone` (the identity provider re-derives its
|
|
97
|
+
day anchors; a trading calendar's session opens are zone-independent).
|
|
98
|
+
- **`TradingCalendarLike.timeZone?`** — a calendar that carries its exchange
|
|
99
|
+
zone supplies the axis default (`calendar={cal}` renders in exchange time
|
|
100
|
+
wherever it is viewed); an explicit `timeZone` prop wins.
|
|
101
|
+
- `scaleTradingTime(provider, { timeZone })` and
|
|
102
|
+
`identityProvider({ timeZone })` take the zone directly for consumers
|
|
103
|
+
building the scale themselves; `identityProvider`, `TradingCalendarLike`
|
|
104
|
+
and `ScaleTimeZoneOptions` are now exported. Internally the tick ladder
|
|
105
|
+
runs on a `TickCalendar` seam whose local implementation is the previous
|
|
106
|
+
`Date` arithmetic verbatim — the default path is unchanged.
|
|
107
|
+
- **`TradingCalendar.timeZone` ([PND-TZFIN]).** `@pond-ts/financial`'s
|
|
108
|
+
calendar keeps the zone its sessions were resolved in — `fromRules` carries
|
|
109
|
+
`rules.timeZone`, `fromSessions(list, { timeZone })` takes it — so
|
|
110
|
+
`<ChartContainer calendar={cal}>` renders the axis in exchange time with no
|
|
111
|
+
further wiring.
|
|
112
|
+
- `@pond-ts/charts` now depends on `d3-time-format` directly (it was already
|
|
113
|
+
a transitive dependency via `d3-scale`).
|
|
114
|
+
- **`TimeZone` — the zone-calendar primitive ([PND-TZCAL]).** `pond-ts`
|
|
115
|
+
exports `TimeZone.of(id)` (interned; also `TimeZone.UTC`,
|
|
116
|
+
`TimeZone.local()`) with `startOf(unit, t)`, `next(unit, t)`, `parts(t)`,
|
|
117
|
+
`instant(parts, { disambiguation })`, `offsetAt(t)` and
|
|
118
|
+
`abbreviation(t, { locale })`. Temporal underneath, but each zone caches its
|
|
119
|
+
offset transitions as it discovers them, so steady-state calls are integer
|
|
120
|
+
arithmetic: `startOf('day')` went from ~24 µs to ~23 ns per call, and a
|
|
121
|
+
three-year hourly series aggregated to `America/New_York` days from 38 ms
|
|
122
|
+
to 0.5 ms. `Sequence.calendar`, `TimeRange.fromCalendar` and
|
|
123
|
+
`Interval.fromCalendar` now bucket through it (no behaviour change; pinned
|
|
124
|
+
against Temporal on eight zones including southern-hemisphere DST, a
|
|
125
|
+
30-minute DST shift, a +05:30 zone, a day with no midnight and Samoa's
|
|
126
|
+
skipped day). This is the primitive the charts' time axis will place and
|
|
127
|
+
label ticks with, so a bucket edge and the tick that labels it are one
|
|
128
|
+
instant. First task of the time-zone plan
|
|
129
|
+
(`docs/plans/PND_TIMEZONE_PLAN.md`).
|
|
130
|
+
- **`CalendarUnit` gains `'quarter'` and `'year'`** for
|
|
131
|
+
`Sequence.calendar`, `TimeRange.fromCalendar` and `Interval.fromCalendar`.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- **`Sequence.calendar` validates its inputs at construction.** An unknown
|
|
136
|
+
unit (`'hour'`) or zone (`'Nowhere'`) now throws `RangeError` immediately;
|
|
137
|
+
before, an unknown unit silently produced wrong buckets (the two unit
|
|
138
|
+
dispatchers fell through to different defaults — the 2026-06 audit's §6
|
|
139
|
+
finding) and an unknown zone failed only on first `bounded()`.
|
|
140
|
+
|
|
141
|
+
### Fixed
|
|
142
|
+
|
|
143
|
+
- **`pond-ts`: two type-level corners of [PND-PARTCOL] (0.68.0) found by the Codex pass on #724.** (1) On a broad `TimeSeries<SeriesSchema>` with a _literal_ partition column, the injected `'first'` could not look up a kind and typed the column as `undefined`; `WithPartitionColumns` now takes the schema and leaves the mapping alone when the schema is broad, so the result type is exactly 0.67's. (2) `By` had no variance pin, so `PartitionedTimeSeries<S, K, 'host'>` accepted a view partitioned by `region` (and an untyped view could be narrowed to any column); a phantom contravariant member now rejects both while a specialised view still assigns to the legacy `PartitionedTimeSeries<S>` shape. Type tests cover both plus the `K`-survives-`smooth`/`baseline` claim. No runtime change.
|
|
144
|
+
- **Docs said a wall-clock string without `parse.timeZone` throws. It never
|
|
145
|
+
did** ([PND-TZDOCS]) — it is read as UTC, silently. `creating.mdx`, the
|
|
146
|
+
agent guide (`AGENTS.md`) and the decision table now say so and describe
|
|
147
|
+
how the shift shows up. The agent guide also gains the one time-zone rule:
|
|
148
|
+
pass the same `timeZone` to `Sequence.calendar` and `<ChartContainer>`.
|
|
149
|
+
The aggregation page cross-links `Sequence.calendar` for weekly / monthly
|
|
150
|
+
bars (issue #358 item 1, supersedes #359). The finance gallery's off-chart
|
|
151
|
+
readout takes the calendar's zone instead of hard-coding New York; the
|
|
152
|
+
Niño 3.4 heat map's year grain uses `Sequence.calendar('year')`.
|
|
153
|
+
|
|
154
|
+
## [0.68.0] — 2026-09-13
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
|
|
158
|
+
- **Agent adoption tranche ([PND-ADOPTMETA] / [PND-ADOPTLINKS] /
|
|
159
|
+
[PND-LLMSTXT] / [PND-AGENTGUIDE] / [PND-SKILL] / [PND-CONTEXT7]).** Every
|
|
160
|
+
package now declares `keywords`, `homepage` and `bugs` (there were none —
|
|
161
|
+
`pond-ts` ranked last in `npm search "time series"`). Every tarball ships an
|
|
162
|
+
`AGENTS.md` (source `docs/agents/USING_POND.md`): which package for which
|
|
163
|
+
task, the core idioms, the mistakes agents make. `pond-ts.org/llms.txt` is
|
|
164
|
+
now llmstxt.org-shaped (titles + descriptions per page, one section per
|
|
165
|
+
docs area, `Optional` links to `API.md` / the agent guide) with per-area
|
|
166
|
+
`llms-<area>.txt` dumps so a single fetch stays small. A Claude Code plugin
|
|
167
|
+
marketplace lives in the repo (`/plugin marketplace add pond-ts/pond`) with
|
|
168
|
+
`pond-ts`, `pond-charts` and `pond-financial` skills. `context7.json`
|
|
169
|
+
configures docs-MCP indexing. Plan and baseline:
|
|
170
|
+
`docs/plans/PND_ADOPTION_PLAN.md`.
|
|
171
|
+
|
|
172
|
+
- **Agent guide + skill hardened by the first cold-start run** (`docs/agents/USING_POND.md`, shipped as `AGENTS.md`; `plugins/pond-ts/skills/pond-ts`): install with `@latest` and the `.d.ts` paths that carry signatures. Cold-start harness committed at `docs/adoption/cold-start/`.
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- **`pond-ts`: the partition column is now in the static type after a partitioned `aggregate` / `rolling` ([PND-PARTCOL]).** `series.partitionBy('host').aggregate(seq, { p95: { from: 'ms', using: 'p95' } }).collect()` always carried `host` at runtime (auto-injected as `'first'`) but the result type omitted it, so `e.get('host')` failed to compile — every fresh agent in the cold-start experiment hit or pre-empted it. `PartitionedTimeSeries` gains a third type parameter `By` (the partition column names, captured by `partitionBy`, default `never`), and the two schema-replacing operators are typed over `WithPartitionColumns<Mapping, By>` — the user's keys win, kind and all; missing partition columns are added as `'first'`. Composite partitions and typed `groups` carry through; `smooth` / `baseline` under `partitionBy` now also keep `K`. Additive: untyped views are unchanged.
|
|
177
|
+
- `@pond-ts/charts` and `@pond-ts/fit` READMEs (rendered on npm) and eight
|
|
178
|
+
docs pages pointed at the retired `pjm17971.github.io/pond-ts` site /
|
|
179
|
+
`pjm17971/pond-ts` repo; now `pond-ts.org` / `pond-ts/pond`.
|
|
180
|
+
|
|
73
181
|
## [0.67.0] — 2026-09-11
|
|
74
182
|
|
|
75
183
|
### Added
|
package/README.md
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
# pond-ts
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pond-ts)
|
|
4
|
+
[](https://github.com/pond-ts/pond/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/pond-ts/pond/blob/main/LICENSE)
|
|
6
|
+
[](https://pond-ts.org)
|
|
7
|
+
|
|
3
8
|
**Highly optimised, fully typed Timeseries library for TypeScript**
|
|
4
9
|
|
|
5
10
|
Schema-driven events, composable batch transforms, push-based streaming
|
|
6
|
-
ingest, multi-entity partitioning
|
|
7
|
-
|
|
11
|
+
ingest, multi-entity partitioning — and, optionally, React hooks and
|
|
12
|
+
canvas charts that read the series directly. All strict TypeScript end to
|
|
13
|
+
end, all immutable.
|
|
8
14
|
|
|
9
15
|
**pond-ts** is the TypeScript-first successor to
|
|
10
16
|
[pondjs](https://github.com/esnet/pond), rewritten from scratch with a
|
|
11
17
|
focus on type safety, composability, and the live-streaming patterns
|
|
12
18
|
that pondjs never grew.
|
|
13
19
|
|
|
20
|
+
## The packages
|
|
21
|
+
|
|
22
|
+
Three packages carry most projects. The core has no dependency on the other
|
|
23
|
+
two; add them only if you render.
|
|
24
|
+
|
|
25
|
+
| Package | What it is | Needs |
|
|
26
|
+
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
27
|
+
| **[`pond-ts`](https://www.npmjs.com/package/pond-ts)** — core | `TimeSeries` (batch) and `LiveSeries` (streaming) with one operator vocabulary: aggregate, rolling, align, fill, partition, join, typed columns. Node or browser, no React. | nothing |
|
|
28
|
+
| **[`@pond-ts/charts`](https://www.npmjs.com/package/@pond-ts/charts)** — optional | Declarative React charts on a canvas data plane that consume a pond series with no adapter: line, area, band, bar, scatter, box, candlestick, heat map; cursors, selection, pan/zoom, annotations. | `pond-ts`, `@pond-ts/react`, React 18/19 |
|
|
29
|
+
| **[`@pond-ts/react`](https://www.npmjs.com/package/@pond-ts/react)** — optional | Hooks to own a series in a component and read live views on a throttled snapshot cadence (`useLiveSeries`, `useSnapshot`, …). | `pond-ts`, React 18/19 |
|
|
30
|
+
|
|
14
31
|
```sh
|
|
15
|
-
npm install pond-ts
|
|
16
|
-
npm install @pond-ts/react
|
|
32
|
+
npm install pond-ts # core — enough for Node pipelines and non-React apps
|
|
33
|
+
npm install @pond-ts/charts @pond-ts/react pond-ts # add the React chart stack
|
|
17
34
|
```
|
|
18
35
|
|
|
36
|
+
Two domain packages ([`@pond-ts/financial`](#domain-packages) for markets,
|
|
37
|
+
[`@pond-ts/fit`](#domain-packages) for activity data) and one experimental
|
|
38
|
+
runtime ([`@pond-ts/process`](#domain-packages)) sit on top — see
|
|
39
|
+
[Domain packages](#domain-packages) below. All six release together under
|
|
40
|
+
one version; keep them in step.
|
|
41
|
+
|
|
19
42
|
- **Typed schemas** — declare once, every transform downstream narrows
|
|
20
43
|
off it. `event.get('cpu')` returns `number | undefined` straight from
|
|
21
44
|
the schema; no `as` casts.
|
|
@@ -108,6 +131,43 @@ The full live surface (`filter`, `map`, `select`, `window`, `aggregate`,
|
|
|
108
131
|
`sample`) is incremental — events flow, views emit, retention bounds
|
|
109
132
|
memory.
|
|
110
133
|
|
|
134
|
+
## Quick start: charts (React)
|
|
135
|
+
|
|
136
|
+
`@pond-ts/charts` reads a `TimeSeries` or `LiveSeries` directly — do the maths
|
|
137
|
+
in pond, hand the result to a layer. Rows share one x scale, so they pan,
|
|
138
|
+
zoom and track the cursor together.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
import {
|
|
142
|
+
BandChart,
|
|
143
|
+
ChartContainer,
|
|
144
|
+
ChartRow,
|
|
145
|
+
Layers,
|
|
146
|
+
LineChart,
|
|
147
|
+
YAxis,
|
|
148
|
+
} from '@pond-ts/charts';
|
|
149
|
+
|
|
150
|
+
// `bands` is the baseline() result from the batch quick start:
|
|
151
|
+
// cpu + avg / sd / upper / lower columns.
|
|
152
|
+
export function CpuChart({ width }: { width: number }) {
|
|
153
|
+
return (
|
|
154
|
+
<ChartContainer width={width} cursor="crosshair" panZoom>
|
|
155
|
+
<ChartRow height={240}>
|
|
156
|
+
<YAxis id="cpu" format=".0%" />
|
|
157
|
+
<Layers>
|
|
158
|
+
<BandChart series={bands} lower="lower" upper="upper" axis="cpu" />
|
|
159
|
+
<LineChart series={bands} column="cpu" axis="cpu" />
|
|
160
|
+
</Layers>
|
|
161
|
+
</ChartRow>
|
|
162
|
+
</ChartContainer>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Pass `width="auto"` to measure the parent instead. Live data renders through
|
|
168
|
+
the same layers: own the series with `useLiveSeries` from `@pond-ts/react`
|
|
169
|
+
and pass its snapshot as `series`.
|
|
170
|
+
|
|
111
171
|
## Quick start: multi-entity
|
|
112
172
|
|
|
113
173
|
`partitionBy` routes events into per-key buffers. Every stateful
|
|
@@ -182,6 +242,26 @@ it is behind. Run locally:
|
|
|
182
242
|
npm run build && node packages/core/bench/vs-pondjs.cjs
|
|
183
243
|
```
|
|
184
244
|
|
|
245
|
+
## Domain packages
|
|
246
|
+
|
|
247
|
+
Optional, domain-specific, all on plain pond series:
|
|
248
|
+
|
|
249
|
+
- **[`@pond-ts/financial`](https://www.npmjs.com/package/@pond-ts/financial)**
|
|
250
|
+
— sixty-plus oracle-verified technical studies (SMA, EMA, RSI, MACD,
|
|
251
|
+
Bollinger, ATR, VWAP, …) that append columns to a bar series, a fluent
|
|
252
|
+
`bars.sma({ period: 20 }).rsi({ period: 14 })` form, and a
|
|
253
|
+
`TradingCalendar` so session-aligned bars, rolling windows and chart axes
|
|
254
|
+
stop at the close.
|
|
255
|
+
- **[`@pond-ts/fit`](https://www.npmjs.com/package/@pond-ts/fit)** — fitness
|
|
256
|
+
and activity analytics: typed quantities with units, canonical activity
|
|
257
|
+
series, geo (distance, elevation, best efforts), power (NP / IF / TSS,
|
|
258
|
+
curves), heart-rate zones, splits.
|
|
259
|
+
- **[`@pond-ts/process`](https://www.npmjs.com/package/@pond-ts/process)** —
|
|
260
|
+
**experimental.** Computations as data: processing graphs authored fluently
|
|
261
|
+
or composed as JSON, resolved against a declared op vocabulary with
|
|
262
|
+
content-addressed caching, provenance and per-node timings. The API is
|
|
263
|
+
expected to move.
|
|
264
|
+
|
|
185
265
|
## Documentation
|
|
186
266
|
|
|
187
267
|
The full guide is at **<https://pond-ts.org/>**.
|
|
@@ -202,6 +282,26 @@ The full guide is at **<https://pond-ts.org/>**.
|
|
|
202
282
|
— TypeDoc output, every public class and method.
|
|
203
283
|
- **[CHANGELOG](./CHANGELOG.md)** — what shipped in each release.
|
|
204
284
|
|
|
285
|
+
## For coding agents
|
|
286
|
+
|
|
287
|
+
pond is built by agents and expects to be used by them. Three things exist so
|
|
288
|
+
an agent can go from "never heard of pond" to working code without a human in
|
|
289
|
+
the loop:
|
|
290
|
+
|
|
291
|
+
- **`AGENTS.md` + `API.md` ship inside every npm tarball** —
|
|
292
|
+
`node_modules/pond-ts/AGENTS.md` is a one-read guide (which package for
|
|
293
|
+
which task, the idioms, the mistakes agents make); `API.md` maps every
|
|
294
|
+
public export to its source file. Source:
|
|
295
|
+
[docs/agents/USING_POND.md](docs/agents/USING_POND.md), [API.md](API.md).
|
|
296
|
+
- **<https://pond-ts.org/llms.txt>** — every docs page with a one-line
|
|
297
|
+
description, plus `llms-<area>.txt` single-fetch dumps per package.
|
|
298
|
+
- **Claude Code plugin** — skills for core, charts and financial, versioned
|
|
299
|
+
with the library:
|
|
300
|
+
```
|
|
301
|
+
/plugin marketplace add pond-ts/pond
|
|
302
|
+
/plugin install pond-ts@pond-ts
|
|
303
|
+
```
|
|
304
|
+
|
|
205
305
|
## Examples
|
|
206
306
|
|
|
207
307
|
- **[pond-ts-dashboard](https://github.com/pjm17971/pond-ts-dashboard)**
|
|
@@ -213,20 +313,22 @@ The full guide is at **<https://pond-ts.org/>**.
|
|
|
213
313
|
|
|
214
314
|
## Develop
|
|
215
315
|
|
|
216
|
-
The repo is an npm-workspaces monorepo with
|
|
217
|
-
(`pond-ts`, `@pond-ts/react
|
|
316
|
+
The repo is an npm-workspaces monorepo with six published packages
|
|
317
|
+
(`pond-ts`, `@pond-ts/react`, `@pond-ts/charts`, `@pond-ts/financial`,
|
|
318
|
+
`@pond-ts/fit`, `@pond-ts/process`). Node 18+ for runtime; Node 20+ for the
|
|
218
319
|
docs site (Docusaurus).
|
|
219
320
|
|
|
220
321
|
```sh
|
|
221
|
-
npm install # one-time, hoists deps for
|
|
322
|
+
npm install # one-time, hoists deps for all packages
|
|
222
323
|
npm run build # build both packages
|
|
223
324
|
npm test # runtime + type-level tests on both packages
|
|
224
325
|
npm run format # prettier write across the repo
|
|
225
326
|
npm run verify # format check + build + test (CI parity)
|
|
226
327
|
```
|
|
227
328
|
|
|
228
|
-
`packages
|
|
229
|
-
|
|
329
|
+
Each package lives under `packages/<name>/` (`core` is `pond-ts`, the rest
|
|
330
|
+
match their scoped names). Docs live in `website/` — its own npm root, not a
|
|
331
|
+
workspace.
|
|
230
332
|
|
|
231
333
|
## License
|
|
232
334
|
|
|
@@ -4,11 +4,29 @@ 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, AppendColumn, 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, ValueColumnsForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
|
|
8
8
|
import type { ScanStep } from './operators/scan.js';
|
|
9
9
|
type SequenceLike = Sequence | BoundedSequence;
|
|
10
10
|
type AlignMethod = 'hold' | 'linear';
|
|
11
11
|
type AlignSample = 'begin' | 'center' | 'end';
|
|
12
|
+
/**
|
|
13
|
+
* The mapping a partitioned `aggregate` / `rolling` actually runs with
|
|
14
|
+
* ([PND-PARTCOL]). The runtime (`augmentMappingWithPartitionCols`) appends
|
|
15
|
+
* every partition column the user's mapping does not already name as a
|
|
16
|
+
* `'first'` spec, so the collected series carries the partition key. This
|
|
17
|
+
* is the same rule at the type level: keys the user wrote win (kind and
|
|
18
|
+
* all — `host: 'count'` stays an optional number), the rest are added as
|
|
19
|
+
* `'first'` and so keep the source column's kind. `By` defaults to `never`
|
|
20
|
+
* on an untyped view, which leaves the mapping untouched; a widened
|
|
21
|
+
* `string` `By` does too (see the conditional). One knowing lie: when the
|
|
22
|
+
* partition column is a union-typed variable (`c: 'host' | 'region'`), the
|
|
23
|
+
* type names both as `string | undefined` while the runtime carries only
|
|
24
|
+
* the one actually passed — harmless because injected columns are already
|
|
25
|
+
* optional.
|
|
26
|
+
*/
|
|
27
|
+
export type WithPartitionColumns<S extends SeriesSchema, Mapping, By extends string> = string extends By ? Mapping : string extends ValueColumnsForSchema<S>[number]['name'] ? Mapping : Mapping & {
|
|
28
|
+
readonly [C in Exclude<By, keyof Mapping>]: 'first';
|
|
29
|
+
};
|
|
12
30
|
/**
|
|
13
31
|
* View over a `TimeSeries` that scopes stateful transforms to within
|
|
14
32
|
* each partition. Created by `TimeSeries.partitionBy(by)`.
|
|
@@ -52,10 +70,26 @@ type AlignSample = 'begin' | 'center' | 'end';
|
|
|
52
70
|
* );
|
|
53
71
|
* ```
|
|
54
72
|
*/
|
|
55
|
-
export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends string = string> {
|
|
73
|
+
export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends string = string, By extends string = never> {
|
|
56
74
|
#private;
|
|
57
75
|
readonly source: TimeSeries<S>;
|
|
58
76
|
readonly by: ReadonlyArray<keyof EventDataForSchema<S> & string>;
|
|
77
|
+
/**
|
|
78
|
+
* Phantom, erased at runtime. Pins `By` **contravariantly** so a view
|
|
79
|
+
* partitioned by one column cannot be assigned where a view partitioned
|
|
80
|
+
* by another is claimed (`PartitionedTimeSeries<S, K, 'host'>` ←
|
|
81
|
+
* `partitionBy('region')` is an error), while a specialised view still
|
|
82
|
+
* assigns to the legacy `PartitionedTimeSeries<S>` / `<S, K>` shape.
|
|
83
|
+
* Without it `By` only appears inside a conditional in return positions
|
|
84
|
+
* and TypeScript treats it as freely convertible (Codex finding on #724).
|
|
85
|
+
*
|
|
86
|
+
* Spelling note: **`never` (the default) is the "any / unknown column"
|
|
87
|
+
* view**, and it is the top of this pin — every specialised view assigns
|
|
88
|
+
* to it. `string` is the *bottom* (a `string`-typed `By` assigns to any
|
|
89
|
+
* literal), so do not write `PartitionedTimeSeries<S, K, string>` to mean
|
|
90
|
+
* "any column"; the `partitionBy` overloads never produce it.
|
|
91
|
+
*/
|
|
92
|
+
readonly __partitionColumns?: (by: By) => void;
|
|
59
93
|
/**
|
|
60
94
|
* Declared partition values when `partitionBy(col, { groups })` was
|
|
61
95
|
* used. When set, `toMap` iterates in declared order (not insertion
|
|
@@ -205,12 +239,12 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
205
239
|
* reservoir. Safe by construction; no `unsafeGlobal: true` token.
|
|
206
240
|
* See {@link TimeSeries.sample}.
|
|
207
241
|
*/
|
|
208
|
-
sample(strategy: BatchSampleStrategy): PartitionedTimeSeries<S, K>;
|
|
242
|
+
sample(strategy: BatchSampleStrategy): PartitionedTimeSeries<S, K, By>;
|
|
209
243
|
/** Per-partition `fill`. See {@link TimeSeries.fill}. */
|
|
210
244
|
fill(strategy: FillStrategy | FillMapping<S>, options?: {
|
|
211
245
|
limit?: number;
|
|
212
246
|
maxGap?: DurationInput;
|
|
213
|
-
}): PartitionedTimeSeries<S, K>;
|
|
247
|
+
}): PartitionedTimeSeries<S, K, By>;
|
|
214
248
|
/**
|
|
215
249
|
* Per-partition `dedupe`. The duplicate key becomes "same partition
|
|
216
250
|
* columns AND same timestamp" — `partitionBy` provides the partition
|
|
@@ -221,13 +255,13 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
221
255
|
*/
|
|
222
256
|
dedupe(options?: {
|
|
223
257
|
keep?: DedupeKeep<S>;
|
|
224
|
-
}): PartitionedTimeSeries<S, K>;
|
|
258
|
+
}): PartitionedTimeSeries<S, K, By>;
|
|
225
259
|
/** Per-partition `align`. See {@link TimeSeries.align}. */
|
|
226
260
|
align(sequence: SequenceLike, options?: {
|
|
227
261
|
method?: AlignMethod;
|
|
228
262
|
sample?: AlignSample;
|
|
229
263
|
range?: TemporalLike;
|
|
230
|
-
}): PartitionedTimeSeries<AlignSchema<S>, K>;
|
|
264
|
+
}): PartitionedTimeSeries<AlignSchema<S>, K, By>;
|
|
231
265
|
/**
|
|
232
266
|
* Per-partition `materialize`. See {@link TimeSeries.materialize}.
|
|
233
267
|
*
|
|
@@ -243,18 +277,18 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
243
277
|
sample?: AlignSample;
|
|
244
278
|
select?: 'first' | 'last' | 'nearest';
|
|
245
279
|
range?: TemporalLike;
|
|
246
|
-
}): PartitionedTimeSeries<MaterializeSchema<S>, K>;
|
|
280
|
+
}): PartitionedTimeSeries<MaterializeSchema<S>, K, By>;
|
|
247
281
|
/** Per-partition `rolling`. See {@link TimeSeries.rolling}. */
|
|
248
282
|
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(window: DurationInput, mapping: Mapping, options?: {
|
|
249
283
|
alignment?: RollingAlignment;
|
|
250
284
|
minSamples?: number;
|
|
251
|
-
}): PartitionedTimeSeries<RollingSchema<S, Mapping
|
|
285
|
+
}): PartitionedTimeSeries<RollingSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
252
286
|
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
253
287
|
alignment?: RollingAlignment;
|
|
254
288
|
sample?: AlignSample;
|
|
255
289
|
range?: TemporalLike;
|
|
256
290
|
minSamples?: number;
|
|
257
|
-
}): PartitionedTimeSeries<AggregateSchema<S, Mapping
|
|
291
|
+
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
258
292
|
/** Per-partition `smooth`. See {@link TimeSeries.smooth}. */
|
|
259
293
|
smooth<const Target extends NumericColumnNameForSchema<S>, const Output extends string | undefined = undefined>(column: Target, method: SmoothMethod, options: {
|
|
260
294
|
alpha: number;
|
|
@@ -267,7 +301,7 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
267
301
|
} | {
|
|
268
302
|
span: number;
|
|
269
303
|
output?: Output;
|
|
270
|
-
}): PartitionedTimeSeries<Output extends string ? SmoothAppendSchema<S, Output> : SmoothSchema<S, Target
|
|
304
|
+
}): PartitionedTimeSeries<Output extends string ? SmoothAppendSchema<S, Output> : SmoothSchema<S, Target>, K, By>;
|
|
271
305
|
/** Per-partition `baseline`. See {@link TimeSeries.baseline}. */
|
|
272
306
|
baseline<const Col extends NumericColumnNameForSchema<S>, const AvgName extends string = 'avg', const SdName extends string = 'sd', const UpperName extends string = 'upper', const LowerName extends string = 'lower'>(col: Col, options: {
|
|
273
307
|
window: DurationInput;
|
|
@@ -280,41 +314,41 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
280
314
|
upper?: UpperName;
|
|
281
315
|
lower?: LowerName;
|
|
282
316
|
};
|
|
283
|
-
}): PartitionedTimeSeries<BaselineSchema<S, AvgName, SdName, UpperName, LowerName
|
|
317
|
+
}): PartitionedTimeSeries<BaselineSchema<S, AvgName, SdName, UpperName, LowerName>, K, By>;
|
|
284
318
|
/** Per-partition `outliers`. See {@link TimeSeries.outliers}. */
|
|
285
319
|
outliers<const Col extends NumericColumnNameForSchema<S>>(col: Col, options: {
|
|
286
320
|
window: DurationInput;
|
|
287
321
|
sigma: number;
|
|
288
322
|
alignment?: RollingAlignment;
|
|
289
323
|
minSamples?: number;
|
|
290
|
-
}): PartitionedTimeSeries<S, K>;
|
|
324
|
+
}): PartitionedTimeSeries<S, K, By>;
|
|
291
325
|
/** Per-partition `diff`. See {@link TimeSeries.diff}. */
|
|
292
326
|
diff<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], options?: {
|
|
293
327
|
drop?: boolean;
|
|
294
|
-
}): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
|
|
328
|
+
}): PartitionedTimeSeries<DiffSchema<S, Target>, K, By>;
|
|
295
329
|
/** Per-partition `rate`. See {@link TimeSeries.rate}. */
|
|
296
330
|
rate<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], options?: {
|
|
297
331
|
drop?: boolean;
|
|
298
|
-
}): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
|
|
332
|
+
}): PartitionedTimeSeries<DiffSchema<S, Target>, K, By>;
|
|
299
333
|
/** Per-partition `pctChange`. See {@link TimeSeries.pctChange}. */
|
|
300
334
|
pctChange<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], options?: {
|
|
301
335
|
drop?: boolean;
|
|
302
|
-
}): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
|
|
336
|
+
}): PartitionedTimeSeries<DiffSchema<S, Target>, K, By>;
|
|
303
337
|
/** Per-partition `cumulative`. See {@link TimeSeries.cumulative}. */
|
|
304
338
|
cumulative<const Targets extends NumericColumnNameForSchema<S>>(spec: {
|
|
305
339
|
[K in Targets]: 'sum' | 'max' | 'min' | 'count' | ((acc: number, value: number) => number);
|
|
306
|
-
}): PartitionedTimeSeries<DiffSchema<S, Targets>, K>;
|
|
340
|
+
}): PartitionedTimeSeries<DiffSchema<S, Targets>, K, By>;
|
|
307
341
|
/** 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>;
|
|
342
|
+
scan<const Source extends NumericColumnNameForSchema<S>, A>(source: Source, step: ScanStep<A>, init: A): PartitionedTimeSeries<DiffSchema<S, Source>, K, By>;
|
|
309
343
|
scan<const Source extends NumericColumnNameForSchema<S>, const Name extends string, A>(source: Source, step: ScanStep<A>, init: A, options: {
|
|
310
344
|
output: Name;
|
|
311
|
-
}): PartitionedTimeSeries<AppendColumn<S, Name, 'number'>, K>;
|
|
345
|
+
}): PartitionedTimeSeries<AppendColumn<S, Name, 'number'>, K, By>;
|
|
312
346
|
/** Per-partition `shift`. See {@link TimeSeries.shift}. */
|
|
313
|
-
shift<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], n: number): PartitionedTimeSeries<DiffSchema<S, Target>, K>;
|
|
347
|
+
shift<const Target extends NumericColumnNameForSchema<S>>(columns: Target | readonly Target[], n: number): PartitionedTimeSeries<DiffSchema<S, Target>, K, By>;
|
|
314
348
|
/** Per-partition `aggregate`. See {@link TimeSeries.aggregate}. */
|
|
315
349
|
aggregate<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
316
350
|
range?: TemporalLike;
|
|
317
|
-
}): PartitionedTimeSeries<AggregateSchema<S, Mapping
|
|
351
|
+
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
318
352
|
}
|
|
319
353
|
export {};
|
|
320
354
|
//# sourceMappingURL=partitioned-time-series.d.ts.map
|
|
@@ -956,8 +956,8 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
956
956
|
*/
|
|
957
957
|
partitionBy<Col extends keyof EventDataForSchema<S> & string, const Groups extends ReadonlyArray<string>>(by: Col | readonly [Col], options: {
|
|
958
958
|
groups: Groups;
|
|
959
|
-
}): PartitionedTimeSeries<S, Groups[number]>;
|
|
960
|
-
partitionBy
|
|
959
|
+
}): PartitionedTimeSeries<S, Groups[number], Col>;
|
|
960
|
+
partitionBy<const Col extends keyof EventDataForSchema<S> & string>(by: Col | ReadonlyArray<Col>): PartitionedTimeSeries<S, string, Col>;
|
|
961
961
|
/**
|
|
962
962
|
* Example: `series.pivotByGroup("host", "cpu")`.
|
|
963
963
|
* Reshapes long-form data into wide rows. Each distinct value of
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export type CalendarUnit = 'day' | 'week' | 'month';
|
|
1
|
+
/** A calendar bucket unit. Boundaries are wall-clock in an IANA zone, so a `'day'` is 23, 24 or 25 hours across DST. */
|
|
2
|
+
export type CalendarUnit = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
3
3
|
export type WeekStartsOn = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
4
4
|
export type TimeZoneOptions = {
|
|
5
5
|
timeZone?: string;
|
|
@@ -10,9 +10,6 @@ export type CalendarOptions = TimeZoneOptions & {
|
|
|
10
10
|
export declare function resolveTimeZone(options?: TimeZoneOptions): string;
|
|
11
11
|
export declare function normalizeWeekStartsOn(value: number | undefined): WeekStartsOn;
|
|
12
12
|
export declare function parseTimestampString(value: string, options?: TimeZoneOptions): number;
|
|
13
|
-
export declare function toPlainDateStart(instantMs: number, timeZone: string, unit: CalendarUnit, weekStartsOn: WeekStartsOn): Temporal.PlainDate;
|
|
14
|
-
export declare function plainDateToStart(date: Temporal.PlainDate, timeZone: string): Temporal.ZonedDateTime;
|
|
15
|
-
export declare function nextCalendarStart(current: Temporal.PlainDate, unit: CalendarUnit): Temporal.PlainDate;
|
|
16
13
|
export declare function dayRangeForDate(reference: string, options?: TimeZoneOptions): {
|
|
17
14
|
start: number;
|
|
18
15
|
end: number;
|
package/dist/core/calendar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import { TimeZone } from './time-zone.js';
|
|
2
3
|
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
4
|
const YEAR_MONTH_RE = /^\d{4}-\d{2}$/;
|
|
4
5
|
const DATE_TIME_LOCAL_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
@@ -42,51 +43,6 @@ export function parseTimestampString(value, options = {}) {
|
|
|
42
43
|
}
|
|
43
44
|
return Temporal.Instant.from(value).epochMilliseconds;
|
|
44
45
|
}
|
|
45
|
-
export function toPlainDateStart(instantMs, timeZone, unit, weekStartsOn) {
|
|
46
|
-
// **Floor to the containing millisecond.** `Temporal.Instant` refuses a
|
|
47
|
-
// fractional epoch ms outright — `fromEpochMilliseconds(1577836800000.37)`
|
|
48
|
-
// throws `epoch milliseconds must be an integer` — and a fraction is not a
|
|
49
|
-
// caller error here. A chart's wheel-zoom derives its view range from pixel
|
|
50
|
-
// positions through `xScale.invert()`, so a perfectly ordinary gesture hands
|
|
51
|
-
// us `1.7e12 + 0.37`; realizing a calendar sequence over that range then
|
|
52
|
-
// crashed the page outright.
|
|
53
|
-
//
|
|
54
|
-
// Flooring is the only sane reading rather than a papering-over: the epoch
|
|
55
|
-
// millisecond *is* the atomic unit of this model, so an over-precise input
|
|
56
|
-
// can only mean the millisecond containing it, and calendar boundaries are
|
|
57
|
-
// themselves whole milliseconds — so the bucket containing `t` and the one
|
|
58
|
-
// containing `t + 0.37` are necessarily the same. Integer inputs are
|
|
59
|
-
// untouched.
|
|
60
|
-
//
|
|
61
|
-
// `Math.floor`, not `Math.trunc`: before 1970 they disagree, and `-5.5` lies
|
|
62
|
-
// inside the millisecond spanning `[-6, -5)`, which is `-6`.
|
|
63
|
-
const zoned = Temporal.Instant.fromEpochMilliseconds(Math.floor(instantMs)).toZonedDateTimeISO(timeZone);
|
|
64
|
-
const date = zoned.toPlainDate();
|
|
65
|
-
if (unit === 'day') {
|
|
66
|
-
return date;
|
|
67
|
-
}
|
|
68
|
-
if (unit === 'month') {
|
|
69
|
-
return Temporal.PlainDate.from({
|
|
70
|
-
year: date.year,
|
|
71
|
-
month: date.month,
|
|
72
|
-
day: 1,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const offset = (date.dayOfWeek - weekStartsOn + 7) % 7;
|
|
76
|
-
return date.subtract({ days: offset });
|
|
77
|
-
}
|
|
78
|
-
export function plainDateToStart(date, timeZone) {
|
|
79
|
-
return date.toZonedDateTime({ timeZone }).startOfDay();
|
|
80
|
-
}
|
|
81
|
-
export function nextCalendarStart(current, unit) {
|
|
82
|
-
if (unit === 'day') {
|
|
83
|
-
return current.add({ days: 1 });
|
|
84
|
-
}
|
|
85
|
-
if (unit === 'week') {
|
|
86
|
-
return current.add({ weeks: 1 });
|
|
87
|
-
}
|
|
88
|
-
return current.add({ months: 1 });
|
|
89
|
-
}
|
|
90
46
|
export function dayRangeForDate(reference, options = {}) {
|
|
91
47
|
const timeZone = resolveTimeZone(options);
|
|
92
48
|
const start = Temporal.PlainDate.from(reference)
|
|
@@ -102,12 +58,10 @@ export function calendarRangeForReference(unit, reference, options = {}) {
|
|
|
102
58
|
const timeZone = resolveTimeZone(options);
|
|
103
59
|
const weekStartsOn = normalizeWeekStartsOn(options.weekStartsOn);
|
|
104
60
|
const referenceMs = parseTimestampString(reference, { timeZone });
|
|
105
|
-
const
|
|
106
|
-
const start = plainDateToStart(startDate, timeZone);
|
|
107
|
-
const end = plainDateToStart(nextCalendarStart(startDate, unit), timeZone);
|
|
61
|
+
const zone = TimeZone.of(timeZone);
|
|
108
62
|
return {
|
|
109
|
-
start:
|
|
110
|
-
end:
|
|
63
|
+
start: zone.startOf(unit, referenceMs, { weekStartsOn }),
|
|
64
|
+
end: zone.next(unit, referenceMs, { weekStartsOn }),
|
|
111
65
|
};
|
|
112
66
|
}
|
|
113
67
|
//# sourceMappingURL=calendar.js.map
|
package/dist/core/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export type { DurationInput, DurationUnit } from './duration.js';
|
|
|
6
6
|
export { ValidationError } from './errors.js';
|
|
7
7
|
export type { EventKey, IntervalInput, IntervalValue, TemporalLike, TimeRangeInput, TimestampInput, } from './temporal.js';
|
|
8
8
|
export type { CalendarOptions, CalendarUnit, TimeZoneOptions, } from './calendar.js';
|
|
9
|
+
export { TimeZone } from './time-zone.js';
|
|
10
|
+
export type { Disambiguation, StartOfOptions, ZonedParts, ZonedPartsInput, } from './time-zone.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.js
CHANGED