pond-ts 0.68.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.
@@ -4,7 +4,7 @@ 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';
@@ -24,7 +24,7 @@ type AlignSample = 'begin' | 'center' | 'end';
24
24
  * the one actually passed — harmless because injected columns are already
25
25
  * optional.
26
26
  */
27
- export type WithPartitionColumns<Mapping, By extends string> = string extends By ? Mapping : Mapping & {
27
+ export type WithPartitionColumns<S extends SeriesSchema, Mapping, By extends string> = string extends By ? Mapping : string extends ValueColumnsForSchema<S>[number]['name'] ? Mapping : Mapping & {
28
28
  readonly [C in Exclude<By, keyof Mapping>]: 'first';
29
29
  };
30
30
  /**
@@ -74,6 +74,22 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
74
74
  #private;
75
75
  readonly source: TimeSeries<S>;
76
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;
77
93
  /**
78
94
  * Declared partition values when `partitionBy(col, { groups })` was
79
95
  * used. When set, `toMap` iterates in declared order (not insertion
@@ -266,13 +282,13 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
266
282
  rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(window: DurationInput, mapping: Mapping, options?: {
267
283
  alignment?: RollingAlignment;
268
284
  minSamples?: number;
269
- }): PartitionedTimeSeries<RollingSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
285
+ }): PartitionedTimeSeries<RollingSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
270
286
  rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
271
287
  alignment?: RollingAlignment;
272
288
  sample?: AlignSample;
273
289
  range?: TemporalLike;
274
290
  minSamples?: number;
275
- }): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
291
+ }): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
276
292
  /** Per-partition `smooth`. See {@link TimeSeries.smooth}. */
277
293
  smooth<const Target extends NumericColumnNameForSchema<S>, const Output extends string | undefined = undefined>(column: Target, method: SmoothMethod, options: {
278
294
  alpha: number;
@@ -332,7 +348,7 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
332
348
  /** Per-partition `aggregate`. See {@link TimeSeries.aggregate}. */
333
349
  aggregate<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, mapping: Mapping, options?: {
334
350
  range?: TemporalLike;
335
- }): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
351
+ }): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
336
352
  }
337
353
  export {};
338
354
  //# sourceMappingURL=partitioned-time-series.d.ts.map
@@ -1,5 +1,5 @@
1
- import { Temporal } from '@js-temporal/polyfill';
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;
@@ -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 startDate = toPlainDateStart(referenceMs, timeZone, unit, weekStartsOn);
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: start.epochMilliseconds,
110
- end: end.epochMilliseconds,
63
+ start: zone.startOf(unit, referenceMs, { weekStartsOn }),
64
+ end: zone.next(unit, referenceMs, { weekStartsOn }),
111
65
  };
112
66
  }
113
67
  //# sourceMappingURL=calendar.js.map
@@ -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
@@ -3,4 +3,5 @@ export { Interval } from './interval.js';
3
3
  export { Time } from './time.js';
4
4
  export { TimeRange, toTimeRange } from './time-range.js';
5
5
  export { ValidationError } from './errors.js';
6
+ export { TimeZone } from './time-zone.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,122 @@
1
+ import type { CalendarUnit, WeekStartsOn } from './calendar.js';
2
+ /**
3
+ * How `TimeZone.instant()` resolves a wall-clock time that a transition
4
+ * makes ambiguous (a fall-back repeats an hour) or skips (a spring-forward
5
+ * drops one). Same vocabulary and same defaults as Temporal:
6
+ *
7
+ * - `'compatible'` (default) — the earlier instant when the time repeats,
8
+ * the instant *after* the gap when it is skipped (02:30 on a spring-forward
9
+ * night becomes 03:30). This is also what `startOf('day')` needs on the
10
+ * rare days that have no midnight.
11
+ * - `'earlier'` / `'later'` — always the earlier / later of the two readings.
12
+ * - `'reject'` — throw a `RangeError` for either case.
13
+ */
14
+ export type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';
15
+ /** The wall-clock reading of an instant in a zone. `weekday` is ISO: 1 = Monday … 7 = Sunday. */
16
+ export type ZonedParts = {
17
+ year: number;
18
+ month: number;
19
+ day: number;
20
+ hour: number;
21
+ minute: number;
22
+ second: number;
23
+ millisecond: number;
24
+ weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7;
25
+ };
26
+ /** The date-and-time fields `TimeZone.instant()` accepts. Time fields default to zero. */
27
+ export type ZonedPartsInput = {
28
+ year: number;
29
+ month: number;
30
+ day: number;
31
+ hour?: number;
32
+ minute?: number;
33
+ second?: number;
34
+ millisecond?: number;
35
+ };
36
+ export type StartOfOptions = {
37
+ /** First day of the week for `'week'`, ISO numbering (1 = Monday, default) … 7 = Sunday. */
38
+ weekStartsOn?: WeekStartsOn;
39
+ };
40
+ declare function assertCalendarUnit(unit: string): asserts unit is CalendarUnit;
41
+ /**
42
+ * An IANA time zone as a calendar: the one place in pond that turns instants
43
+ * (UTC epoch milliseconds) into wall-clock readings and calendar boundaries,
44
+ * and back.
45
+ *
46
+ * Instances are interned — `TimeZone.of('Europe/Berlin')` returns the same
47
+ * object every time — and cache the zone's offset transitions as they are
48
+ * discovered, so after the first query in a stretch of history every call
49
+ * is integer arithmetic. `Sequence.calendar` buckets with it; the charts'
50
+ * time axis places and labels ticks with it; the two therefore agree on
51
+ * every boundary by construction.
52
+ *
53
+ * A `TimeZone` holds no instant. Values (`Time`, `TimeRange`, `Interval`)
54
+ * stay plain UTC milliseconds; the zone is a parameter to the code that
55
+ * interprets them.
56
+ */
57
+ export declare class TimeZone {
58
+ #private;
59
+ /** The canonical IANA identifier, e.g. `'America/New_York'` or `'UTC'`. */
60
+ readonly id: string;
61
+ private constructor();
62
+ /**
63
+ * Example: `TimeZone.of('Australia/Sydney')`. Looks up a zone by IANA
64
+ * identifier. Throws `RangeError` for an identifier the runtime does not
65
+ * know. Case-insensitive on input; `id` reports the canonical spelling.
66
+ * A fixed-offset identifier such as `'+05:30'` is accepted too, as
67
+ * Temporal accepts it, and behaves as a zone with a single segment.
68
+ */
69
+ static of(id: string): TimeZone;
70
+ /** Example: `TimeZone.UTC.startOf('month', t)`. The UTC zone. */
71
+ static get UTC(): TimeZone;
72
+ /**
73
+ * Example: `TimeZone.local()`. The runtime's own zone, as the environment
74
+ * reports it. Resolved on every call, so a test that changes `TZ` between
75
+ * calls sees the change.
76
+ */
77
+ static local(): TimeZone;
78
+ /** Example: `zone.offsetAt(t)`. The zone's UTC offset at an instant, in milliseconds east of UTC. */
79
+ offsetAt(ms: number): number;
80
+ /**
81
+ * Example: `zone.parts(t).hour`. The wall-clock reading of an instant in
82
+ * this zone.
83
+ */
84
+ parts(ms: number): ZonedParts;
85
+ /**
86
+ * Example: `zone.instant({ year: 2026, month: 3, day: 29, hour: 2, minute: 30 })`.
87
+ * The instant at which this zone's clocks read the given wall time. When
88
+ * a transition makes the reading ambiguous or nonexistent,
89
+ * `disambiguation` decides (default `'compatible'`, as Temporal).
90
+ */
91
+ instant(parts: ZonedPartsInput, options?: {
92
+ disambiguation?: Disambiguation;
93
+ }): number;
94
+ /**
95
+ * Example: `zone.startOf('week', t, { weekStartsOn: 7 })`. The first
96
+ * instant of the calendar unit containing `ms`, in this zone. On a day
97
+ * with no midnight (a spring-forward at 00:00) this is the first instant
98
+ * that exists, as `Temporal.ZonedDateTime.startOfDay` does.
99
+ */
100
+ startOf(unit: CalendarUnit, ms: number, options?: StartOfOptions): number;
101
+ /**
102
+ * Example: `zone.next('month', t)`. The first instant of the calendar unit
103
+ * *after* the one containing `ms` — i.e. the exclusive end of `startOf`'s
104
+ * bucket.
105
+ */
106
+ next(unit: CalendarUnit, ms: number, options?: StartOfOptions): number;
107
+ /**
108
+ * Example: `zone.abbreviation(t)` → `'EDT'`. The zone's short name at an
109
+ * instant, as `Intl` reports it for `locale` (default: the runtime's
110
+ * locale). Which zones have a conventional abbreviation is a locale
111
+ * question — `en-US` knows `EST`, `en-AU` knows `AEDT`, and neither knows
112
+ * the other's; the fallback is a `GMT±h[:mm]` offset string. Pass the
113
+ * viewer's locale when rendering.
114
+ */
115
+ abbreviation(ms: number, options?: {
116
+ locale?: string;
117
+ }): string;
118
+ toString(): string;
119
+ toJSON(): string;
120
+ }
121
+ export { assertCalendarUnit };
122
+ //# sourceMappingURL=time-zone.d.ts.map