@devicechain/widgets 0.21.1-rc.1 → 0.22.0-rc.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/README.md +28 -7
- package/dist/aggregate-feed.d.ts +14 -0
- package/dist/aggregate-fold.d.ts +11 -0
- package/dist/aggregate-reading.d.ts +19 -0
- package/dist/aggregate-strings.d.ts +16 -0
- package/dist/binding-policy.d.ts +3 -1
- package/dist/builtin-descriptors.d.ts +1210 -8
- package/dist/builtin-registry.d.ts +1197 -8
- package/dist/category-geometry.d.ts +19 -0
- package/dist/category-items.d.ts +10 -0
- package/dist/chunks/aggregate-feed-6ZYKOQOB.js +130 -0
- package/dist/chunks/aggregate-feed-6ZYKOQOB.js.map +7 -0
- package/dist/chunks/aggregate-widgets-WVXAW3UL.js +895 -0
- package/dist/chunks/aggregate-widgets-WVXAW3UL.js.map +7 -0
- package/dist/chunks/chunk-2D7JNKFE.js +65 -0
- package/dist/chunks/chunk-2D7JNKFE.js.map +7 -0
- package/dist/chunks/chunk-S2OMBJLI.js +200 -0
- package/dist/chunks/chunk-S2OMBJLI.js.map +7 -0
- package/dist/chunks/chunk-XQQQNTBX.js +76 -0
- package/dist/chunks/chunk-XQQQNTBX.js.map +7 -0
- package/dist/chunks/chunk-YPFB6DAL.js +157 -0
- package/dist/chunks/chunk-YPFB6DAL.js.map +7 -0
- package/dist/chunks/chunk-ZR74ORIT.js +35 -0
- package/dist/chunks/chunk-ZR74ORIT.js.map +7 -0
- package/dist/chunks/control-widgets-UDS4ET3T.js +775 -0
- package/dist/chunks/control-widgets-UDS4ET3T.js.map +7 -0
- package/dist/chunks/fleet-feed-FK4ZMKIK.js +252 -0
- package/dist/chunks/fleet-feed-FK4ZMKIK.js.map +7 -0
- package/dist/chunks/level-widgets-MJB7TOLU.js +833 -0
- package/dist/chunks/level-widgets-MJB7TOLU.js.map +7 -0
- package/dist/command-control-core.d.ts +68 -0
- package/dist/command-control-strings.d.ts +32 -0
- package/dist/command-control.d.ts +31 -0
- package/dist/dashboard-renderer.d.ts +15 -4
- package/dist/fleet-feed.d.ts +21 -0
- package/dist/fleet-model.d.ts +43 -0
- package/dist/fleet-seed.d.ts +7 -0
- package/dist/fleet-strings.d.ts +25 -0
- package/dist/heading.d.ts +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1774 -676
- package/dist/index.js.map +4 -4
- package/dist/level-reading.d.ts +29 -0
- package/dist/level-readout.d.ts +25 -0
- package/dist/level-strings.d.ts +13 -0
- package/dist/options.d.ts +1 -1
- package/dist/time-range-bar.d.ts +28 -0
- package/dist/use-held.d.ts +1 -0
- package/dist/value-scale.d.ts +30 -0
- package/dist/widgets/aggregate-lazy.d.ts +305 -0
- package/dist/widgets/aggregate-widgets.d.ts +4 -0
- package/dist/widgets/alarm-count.d.ts +1 -0
- package/dist/widgets/alarm-table.d.ts +4 -1
- package/dist/widgets/bar-chart.d.ts +5 -0
- package/dist/widgets/battery.d.ts +7 -0
- package/dist/widgets/command-slider.d.ts +2 -0
- package/dist/widgets/command-toggle.d.ts +4 -0
- package/dist/widgets/compass.d.ts +2 -0
- package/dist/widgets/control-lazy.d.ts +92 -0
- package/dist/widgets/control-widgets.d.ts +2 -0
- package/dist/widgets/fleet-table.d.ts +13 -0
- package/dist/widgets/gauge.d.ts +52 -0
- package/dist/widgets/label.d.ts +1 -1
- package/dist/widgets/latest-card.d.ts +44 -1
- package/dist/widgets/lazy-chunk.d.ts +8 -0
- package/dist/widgets/level-gauge.d.ts +2 -0
- package/dist/widgets/level-lazy.d.ts +647 -0
- package/dist/widgets/level-widgets.d.ts +7 -0
- package/dist/widgets/linear-gauge.d.ts +2 -0
- package/dist/widgets/pie-chart.d.ts +2 -0
- package/dist/widgets/progress-ring.d.ts +3 -0
- package/dist/widgets/severity.d.ts +2 -1
- package/dist/widgets/signal-bars.d.ts +3 -0
- package/dist/widgets/stat-card.d.ts +3 -0
- package/dist/widgets/status-light.d.ts +7 -0
- package/dist/widgets/table.d.ts +9 -1
- package/dist/widgets/time-series-chart.d.ts +62 -4
- package/package.json +8 -8
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface NiceDomain {
|
|
2
|
+
min: number;
|
|
3
|
+
max: number;
|
|
4
|
+
step: number;
|
|
5
|
+
ticks: number[];
|
|
6
|
+
}
|
|
7
|
+
export declare function niceDomain(values: readonly number[], intervals?: number): NiceDomain;
|
|
8
|
+
export interface Slice {
|
|
9
|
+
index: number;
|
|
10
|
+
fraction: number;
|
|
11
|
+
from: number;
|
|
12
|
+
to: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function pieSlices(values: readonly (number | null)[]): Slice[];
|
|
15
|
+
export declare function polar(cx: number, cy: number, r: number, turns: number): {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function wedgePath(cx: number, cy: number, r: number, from: number, to: number): string | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MeasurementStreamState, WidgetInstance } from '@devicechain/dashboards';
|
|
2
|
+
export interface CategoryItem {
|
|
3
|
+
label: string;
|
|
4
|
+
value: number | null;
|
|
5
|
+
}
|
|
6
|
+
export declare const CATEGORY_SORTS: readonly ['none', 'ascending', 'descending'];
|
|
7
|
+
export type CategorySort = (typeof CATEGORY_SORTS)[number];
|
|
8
|
+
export declare function parseSort(v: unknown): CategorySort;
|
|
9
|
+
export declare function categoryItems(widget: WidgetInstance, data: Pick<MeasurementStreamState, 'latest'>, sort: CategorySort): CategoryItem[];
|
|
10
|
+
export declare function pieProblem(items: readonly CategoryItem[]): 'negative' | 'noShare' | 'noData' | undefined;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
measurementSpan
|
|
3
|
+
} from "./chunk-2D7JNKFE.js";
|
|
4
|
+
|
|
5
|
+
// src/aggregate-feed.tsx
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_TIME_RANGE_SECONDS
|
|
8
|
+
} from "@devicechain/dashboards";
|
|
9
|
+
import { useMeasurementStream, useWidgetTimeRange } from "@devicechain/widget-sdk";
|
|
10
|
+
import { useEffect, useMemo, useState } from "react";
|
|
11
|
+
|
|
12
|
+
// src/aggregate-fold.ts
|
|
13
|
+
import { bucketSecondsFor } from "@devicechain/dashboards";
|
|
14
|
+
var AggregateFieldsError = class extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "AggregateFieldsError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function aggregateBuckets(buckets) {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const b of buckets) {
|
|
23
|
+
if (typeof b.count !== "number") {
|
|
24
|
+
throw new AggregateFieldsError(`the bucket at ${b.bucketStart} for "${b.name}" carries no reading count`);
|
|
25
|
+
}
|
|
26
|
+
if (b.count === 0) continue;
|
|
27
|
+
const { min, max, sum } = b;
|
|
28
|
+
if (min == null || max == null || sum == null) {
|
|
29
|
+
const missing = [min == null && "min", max == null && "max", sum == null && "sum"].filter(Boolean);
|
|
30
|
+
throw new AggregateFieldsError(
|
|
31
|
+
`the bucket at ${b.bucketStart} for "${b.name}" holds ${b.count} readings but no ${missing.join(", ")}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const a = out[b.name];
|
|
35
|
+
if (!a) out[b.name] = { count: b.count, min, max, sum, avg: sum / b.count };
|
|
36
|
+
else {
|
|
37
|
+
a.count += b.count;
|
|
38
|
+
a.sum += sum;
|
|
39
|
+
a.min = Math.min(a.min, min);
|
|
40
|
+
a.max = Math.max(a.max, max);
|
|
41
|
+
a.avg = a.sum / a.count;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
var MINUTE_MS = 6e4;
|
|
47
|
+
function aggregateRefreshMs(spanSeconds) {
|
|
48
|
+
return bucketSecondsFor(spanSeconds) * 1e3;
|
|
49
|
+
}
|
|
50
|
+
function aggregateWindow(spanSeconds, at) {
|
|
51
|
+
const end = Math.floor(at / MINUTE_MS) * MINUTE_MS;
|
|
52
|
+
const start = Math.ceil((end - spanSeconds * 1e3) / MINUTE_MS) * MINUTE_MS;
|
|
53
|
+
const covered = (end - start) / 1e3;
|
|
54
|
+
return {
|
|
55
|
+
window: {
|
|
56
|
+
startTime: new Date(start).toISOString(),
|
|
57
|
+
endTime: new Date(end - 1).toISOString(),
|
|
58
|
+
intervalSeconds: Math.max(60, covered)
|
|
59
|
+
},
|
|
60
|
+
spanSeconds: covered
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/aggregate-feed.tsx
|
|
65
|
+
import { jsx } from "react/jsx-runtime";
|
|
66
|
+
var LOADING = { status: "loading" };
|
|
67
|
+
function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }) {
|
|
68
|
+
const range = useWidgetTimeRange();
|
|
69
|
+
const now = useMemo(() => hub.now ? () => hub.now() : Date.now, [hub]);
|
|
70
|
+
const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;
|
|
71
|
+
const pausedAt = range?.paused ? range.pausedAt : void 0;
|
|
72
|
+
const ds = widget.datasource;
|
|
73
|
+
const stream = useMeasurementStream(hub, ds, { window: 1, pausedAt, now });
|
|
74
|
+
const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null]);
|
|
75
|
+
const supported = hub.bucketMeasurements !== void 0;
|
|
76
|
+
const [tagged, setTagged] = useState({ key, result: LOADING });
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!supported || !ds) return;
|
|
79
|
+
let current = true;
|
|
80
|
+
const run = () => {
|
|
81
|
+
const { window, spanSeconds: covered } = aggregateWindow(spanSeconds, pausedAt ?? now());
|
|
82
|
+
hub.bucketMeasurements(ds, window).then(aggregateBuckets).then(
|
|
83
|
+
(byName) => {
|
|
84
|
+
if (!current) return;
|
|
85
|
+
const { startTime, endTime } = window;
|
|
86
|
+
setTagged({ key, result: { status: "ready", byName, startTime, endTime, spanSeconds: covered } });
|
|
87
|
+
},
|
|
88
|
+
(err) => {
|
|
89
|
+
if (!current) return;
|
|
90
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
setTagged({ key, result: { status: "unavailable", reason: "failed", message } });
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
run();
|
|
96
|
+
if (pausedAt !== void 0) return () => void (current = false);
|
|
97
|
+
const hidden = () => document.hidden;
|
|
98
|
+
const every = aggregateRefreshMs(spanSeconds);
|
|
99
|
+
let timer;
|
|
100
|
+
const schedule = () => {
|
|
101
|
+
timer = setTimeout(
|
|
102
|
+
() => {
|
|
103
|
+
if (!hidden()) run();
|
|
104
|
+
schedule();
|
|
105
|
+
},
|
|
106
|
+
every - now() % every
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
schedule();
|
|
110
|
+
const shown = () => {
|
|
111
|
+
if (!hidden()) run();
|
|
112
|
+
};
|
|
113
|
+
document.addEventListener("visibilitychange", shown);
|
|
114
|
+
return () => {
|
|
115
|
+
current = false;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
document.removeEventListener("visibilitychange", shown);
|
|
118
|
+
};
|
|
119
|
+
}, [hub, supported, key]);
|
|
120
|
+
let aggregates;
|
|
121
|
+
if (!supported) aggregates = { status: "unavailable", reason: "unsupported" };
|
|
122
|
+
else if (!ds) aggregates = { status: "ready", byName: {}, startTime: "", endTime: "", spanSeconds };
|
|
123
|
+
else aggregates = tagged.key === key ? tagged.result : LOADING;
|
|
124
|
+
const data = { aggregates, latest: stream.latest, error: stream.error };
|
|
125
|
+
return data.error ? /* @__PURE__ */ jsx(ErrorFrame, { widget, error: data.error }) : /* @__PURE__ */ jsx(Component, { widget, data, actions });
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
AggregateFeed as default
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=aggregate-feed-6ZYKOQOB.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/aggregate-feed.tsx", "../../src/aggregate-fold.ts"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The data feed of an aggregate widget: the exact aggregates of its measurements over its span,\n// plus the newest live reading of each. Loaded on first use (connected-widget.tsx imports it\n// dynamically), so a board with no aggregate widget never downloads it.\n//\n// The aggregates are folded from the source's own buckets (WidgetDataSource.bucketMeasurements: each\n// carries the exact count, min, max and sum), never from the chart seed: a seed is a series of bucket\n// AVERAGES, and a minimum or maximum cannot be recovered from those. A source that produces no\n// buckets is an explained state, not a guess.\n//\n// - the span is the widget's pinned one, else the board's (DEFAULT_TIME_RANGE_SECONDS off a board);\n// - the window is whole minutes (aggregateWindow), so it holds no reading from before the span;\n// - live, the read ends at the last minute and repeats on the boundaries of aggregateRefreshMs\n// (one chart bucket of the span: a minute for an hour, 42 minutes for a week), so every card on\n// the board reads at the same moment and the hub serves identical reads once; paused, it ends\n// at the freeze instant and is read once, so the number on screen does not move;\n// - a hidden tab does not read; it reads once when it is shown again;\n// - a read for a different selector, span or freeze instant is not shown under the new one: a\n// result is tagged with the key it was read for, and a mismatch reads as loading.\n\nimport {\n DEFAULT_TIME_RANGE_SECONDS,\n type AggregateResult,\n type AggregateStreamState,\n type WidgetActions,\n type WidgetDataSource,\n type WidgetDescriptor,\n type WidgetInstance,\n} from '@devicechain/dashboards';\nimport { useMeasurementStream, useWidgetTimeRange } from '@devicechain/widget-sdk';\nimport { useEffect, useMemo, useState, type ComponentType } from 'react';\n\nimport { aggregateBuckets, aggregateRefreshMs, aggregateWindow } from './aggregate-fold';\nimport { measurementSpan } from './binding-policy';\n\nconst LOADING: AggregateResult = { status: 'loading' };\n\nexport interface AggregateFeedProps {\n hub: WidgetDataSource;\n widget: WidgetInstance;\n descriptor: WidgetDescriptor;\n // The widget's component, and the host's pane for a stream that failed (before any figure).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Component: ComponentType<any>;\n actions: WidgetActions | undefined;\n ErrorFrame: ComponentType<{ widget: WidgetInstance; error: unknown }>;\n}\n\nexport default function AggregateFeed({ hub, widget, descriptor, Component, actions, ErrorFrame }: AggregateFeedProps) {\n const range = useWidgetTimeRange();\n const now = useMemo(() => (hub.now ? () => hub.now!() : Date.now), [hub]);\n const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;\n const pausedAt = range?.paused ? range.pausedAt : undefined;\n const ds = widget.datasource;\n\n // The newest live reading per measurement. It holds one sample: only `latest` is read, and the\n // pause keeps it from moving.\n const stream = useMeasurementStream(hub, ds, { window: 1, pausedAt, now });\n\n const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null]);\n const supported = hub.bucketMeasurements !== undefined;\n const [tagged, setTagged] = useState<{ key: string; result: AggregateResult }>({ key, result: LOADING });\n\n useEffect(() => {\n if (!supported || !ds) return;\n let current = true;\n const run = (): void => {\n const { window, spanSeconds: covered } = aggregateWindow(spanSeconds, pausedAt ?? now());\n // Folded inside the chain, so a bucket that cannot be trusted fails the read like a network error.\n hub.bucketMeasurements!(ds, window).then(aggregateBuckets).then(\n (byName) => {\n if (!current) return;\n const { startTime, endTime } = window;\n setTagged({ key, result: { status: 'ready', byName, startTime, endTime, spanSeconds: covered } });\n },\n (err: unknown) => {\n if (!current) return;\n const message = err instanceof Error ? err.message : String(err);\n setTagged({ key, result: { status: 'unavailable', reason: 'failed', message } });\n },\n );\n };\n run();\n // Paused, the window is fixed at the freeze instant: nothing new can land in it.\n if (pausedAt !== undefined) return () => void (current = false);\n const hidden = () => document.hidden;\n const every = aggregateRefreshMs(spanSeconds);\n let timer: ReturnType<typeof setTimeout>;\n const schedule = (): void => {\n timer = setTimeout(\n () => {\n if (!hidden()) run();\n schedule();\n },\n every - (now() % every),\n );\n };\n schedule();\n const shown = (): void => {\n if (!hidden()) run();\n };\n document.addEventListener('visibilitychange', shown);\n return () => {\n current = false;\n clearTimeout(timer);\n document.removeEventListener('visibilitychange', shown);\n };\n // `ds` is read through `key` (value identity), not by reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, supported, key]);\n\n let aggregates: AggregateResult;\n if (!supported) aggregates = { status: 'unavailable', reason: 'unsupported' };\n else if (!ds) aggregates = { status: 'ready', byName: {}, startTime: '', endTime: '', spanSeconds };\n else aggregates = tagged.key === key ? tagged.result : LOADING;\n\n const data: AggregateStreamState = { aggregates, latest: stream.latest, error: stream.error };\n return data.error ? <ErrorFrame widget={widget} error={data.error} /> : <Component widget={widget} data={data} actions={actions} />;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Aggregates over a span, folded from the backend's time buckets. Each bucket carries the exact\n// count, minimum, maximum and sum of the readings in it, so the span's aggregates are exact too:\n// the min of the mins, the max of the maxes, the sum of the sums, the count of the counts, and a\n// mean that is the total sum over the total count (which is the count-weighted mean of the\n// buckets' averages, without ever reading an average).\n//\n// \uD83D\uDD34 A MINIMUM OR MAXIMUM IS NEVER DERIVED FROM AVERAGES. A bucket's average hides its extremes, so\n// the \"max of the avgs\" is a plausible number that is not the maximum. A bucket that holds\n// readings but lacks a field the aggregate needs is therefore a loud failure here, never a\n// quiet substitution.\n\nimport { bucketSecondsFor, type HistoryWindow, type MeasurementAggregate, type MeasurementBucket } from '@devicechain/dashboards';\n\nexport class AggregateFieldsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AggregateFieldsError';\n }\n}\n\n// aggregateBuckets folds the buckets (any number of devices) into one aggregate per measurement\n// name. A name with no readings in any bucket is absent from the result, not zero.\nexport function aggregateBuckets(buckets: readonly MeasurementBucket[]): Record<string, MeasurementAggregate> {\n const out: Record<string, MeasurementAggregate> = {};\n for (const b of buckets) {\n if (typeof b.count !== 'number') {\n throw new AggregateFieldsError(`the bucket at ${b.bucketStart} for \"${b.name}\" carries no reading count`);\n }\n if (b.count === 0) continue;\n const { min, max, sum } = b;\n if (min == null || max == null || sum == null) {\n const missing = [min == null && 'min', max == null && 'max', sum == null && 'sum'].filter(Boolean);\n throw new AggregateFieldsError(\n `the bucket at ${b.bucketStart} for \"${b.name}\" holds ${b.count} readings but no ${missing.join(', ')}`,\n );\n }\n const a = out[b.name];\n if (!a) out[b.name] = { count: b.count, min, max, sum, avg: sum / b.count };\n else {\n a.count += b.count;\n a.sum += sum;\n a.min = Math.min(a.min, min);\n a.max = Math.max(a.max, max);\n a.avg = a.sum / a.count;\n }\n }\n return out;\n}\n\n// ---- The window a stat reads ------------------------------------------------------------------\n\nconst MINUTE_MS = 60_000;\n\n// aggregateRefreshMs is how often a live stat reads again: one bucket of the span's chart\n// (bucketSecondsFor), so never more often than once a minute. Finer is wasted -- the window it\n// reads moves in whole minutes (aggregateWindow) -- and a 7-day figure re-read every few seconds\n// costs the event store a scan of a week of summaries to move by a few seconds' readings.\nexport function aggregateRefreshMs(spanSeconds: number): number {\n return bucketSecondsFor(spanSeconds) * 1000;\n}\n\nexport interface AggregateWindow {\n window: HistoryWindow;\n // The length the window actually covers, in seconds: the span, or less when the span is not a\n // whole number of minutes. This, not the span asked for, is what the card says it covers.\n spanSeconds: number;\n}\n\n// aggregateWindow is the window a stat over `spanSeconds` reads at `at` (epoch ms): whole minutes,\n// ending at the last minute boundary at or before `at`.\n//\n// \uD83D\uDD34 Whole minutes because the event store answers from one-minute summaries, and a summary is\n// read whole: a window starting mid-minute would also count the readings from that minute's\n// start, BEFORE the span, so \"Maximum \u00B7 1h\" could be the maximum of an hour and a minute. Both\n// edges fall on minute boundaries instead, and the end is asked for as the instant just before\n// its boundary (the store's end is inclusive), so the summaries read are exactly the window's.\n// The start rounds UP, so a span that is not a whole number of minutes covers less, never more,\n// and says so (`spanSeconds`).\n//\n// Live cards read again on the boundaries of aggregateRefreshMs (a whole number of minutes), so\n// every card of one span ends its window on the same boundary: cards that read the same\n// measurement ask the same question at the same moment, and the hub answers them with one read.\n// One bucket wide: the card needs only the totals, so the store returns one or two rows per\n// measurement rather than a chart's worth.\nexport function aggregateWindow(spanSeconds: number, at: number): AggregateWindow {\n const end = Math.floor(at / MINUTE_MS) * MINUTE_MS;\n const start = Math.ceil((end - spanSeconds * 1000) / MINUTE_MS) * MINUTE_MS;\n const covered = (end - start) / 1000;\n return {\n window: {\n startTime: new Date(start).toISOString(),\n endTime: new Date(end - 1).toISOString(),\n intervalSeconds: Math.max(60, covered),\n },\n spanSeconds: covered,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|