@devicechain/dashboards 0.22.0 → 0.23.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.
@@ -4,6 +4,7 @@ export interface MeasurementStreamState {
4
4
  samples: MeasurementSample[];
5
5
  error: unknown | null;
6
6
  truncatedBefore?: string;
7
+ gaps?: string[];
7
8
  members?: FleetState;
8
9
  }
9
10
  export interface FleetMember {
@@ -0,0 +1,104 @@
1
+ // src/recorded-extra.ts
2
+ function lowerBound(a, x) {
3
+ let lo = 0;
4
+ let hi = a.length;
5
+ while (lo < hi) {
6
+ const mid = lo + hi >>> 1;
7
+ if (a[mid] < x) lo = mid + 1;
8
+ else hi = mid;
9
+ }
10
+ return lo;
11
+ }
12
+ function upperBound(a, x) {
13
+ let lo = 0;
14
+ let hi = a.length;
15
+ while (lo < hi) {
16
+ const mid = lo + hi >>> 1;
17
+ if (a[mid] <= x) lo = mid + 1;
18
+ else hi = mid;
19
+ }
20
+ return lo;
21
+ }
22
+ function foldBuckets(rec, groups, byDevice, window, originMs, cursor) {
23
+ const series = [];
24
+ for (const g of groups) {
25
+ for (const s of byDevice.get(g.deviceToken) ?? []) {
26
+ if (g.names.size === 0 || g.names.has(s.name)) series.push(s);
27
+ }
28
+ }
29
+ const widthMs = window.intervalSeconds * 1e3;
30
+ const fromMs = Date.parse(window.startTime) - originMs;
31
+ const toMs = Math.min(Date.parse(window.endTime) - originMs, cursor);
32
+ if (!(widthMs > 0) || !Number.isFinite(fromMs) || !Number.isFinite(toMs)) {
33
+ throw new RangeError(`bucketMeasurements: not a window: ${JSON.stringify(window)}`);
34
+ }
35
+ const { t, v } = rec.measurements;
36
+ const out = [];
37
+ for (const s of series) {
38
+ const byStart = /* @__PURE__ */ new Map();
39
+ for (let i = lowerBound(s.times, fromMs), end = upperBound(s.times, toMs); i < end; i++) {
40
+ const row = s.rows[i];
41
+ const start = Math.floor((originMs + t[row]) / widthMs) * widthMs;
42
+ const value = v[row];
43
+ const b = byStart.get(start);
44
+ if (!b) {
45
+ byStart.set(start, { bucketStart: new Date(start).toISOString(), name: s.name, avg: value, min: value, max: value, sum: value, count: 1 });
46
+ } else {
47
+ b.count += 1;
48
+ b.sum += value;
49
+ if (value < b.min) b.min = value;
50
+ if (value > b.max) b.max = value;
51
+ b.avg = b.sum / b.count;
52
+ }
53
+ }
54
+ out.push(...byStart.values());
55
+ }
56
+ out.sort((a, b) => a.bucketStart < b.bucketStart ? -1 : a.bucketStart > b.bucketStart ? 1 : a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
57
+ return out;
58
+ }
59
+ var positionsCache = /* @__PURE__ */ new WeakMap();
60
+ function positionsOf(rec) {
61
+ let positions = positionsCache.get(rec);
62
+ if (positions) return positions;
63
+ positions = /* @__PURE__ */ new Map();
64
+ const loc = rec.locations;
65
+ for (let i = 0; i < loc.t.length; i++) {
66
+ const token = rec.devices[loc.d[i]].token;
67
+ let p = positions.get(token);
68
+ if (!p) positions.set(token, p = { rows: [], times: [] });
69
+ p.rows.push(i);
70
+ p.times.push(loc.t[i]);
71
+ }
72
+ positionsCache.set(rec, positions);
73
+ return positions;
74
+ }
75
+ function lastPositions(rec, tokens, cursor, iso) {
76
+ const loc = rec.locations;
77
+ const positions = positionsOf(rec);
78
+ const rows = [];
79
+ for (const token of tokens) {
80
+ const p = positions.get(token);
81
+ if (!p) continue;
82
+ const at = upperBound(p.times, cursor) - 1;
83
+ if (at >= 0) rows.push([token, p.rows[at]]);
84
+ }
85
+ return {
86
+ signature: rows.map(([, row]) => row).join(","),
87
+ locations: rows.map(([token, row]) => ({
88
+ id: `${token}|location|${row}`,
89
+ deviceToken: token,
90
+ latitude: loc.lat[row],
91
+ longitude: loc.lon[row],
92
+ elevation: null,
93
+ accuracy: null,
94
+ speed: loc.spd[row],
95
+ heading: loc.hdg[row],
96
+ occurredTime: iso(loc.t[row])
97
+ }))
98
+ };
99
+ }
100
+ export {
101
+ foldBuckets,
102
+ lastPositions
103
+ };
104
+ //# sourceMappingURL=recorded-extra-C2BHFOCB.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/recorded-extra.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The parts of RecordedDataSource only some boards use, loaded on first use (recorded.ts\n// imports this module dynamically): a board with no map and no stat widget never downloads\n// them. Pure functions over the recording's columns; the source owns the cursor and the shift.\n\nimport type { HistoryWindow } from './history';\nimport type { MeasurementBucket } from './queries';\nimport type { BoardRecording } from './recording';\nimport type { LocationSample } from './types';\n\n// First index whose value is >= x (the array is non-decreasing).\nfunction lowerBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] < x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// First index whose value is > x (the array is non-decreasing).\nfunction upperBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] <= x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// One series: its name and its rows (indices into the measurement columns), with their times.\nexport interface SeriesRows {\n name: string;\n rows: number[];\n times: number[];\n}\n\n// foldBuckets is RecordedDataSource.bucketMeasurements past its channel check: the rows of\n// the selected (device, measurement) series whose times fall in the window AND at or before\n// `cursor`, grouped into buckets `intervalSeconds` wide aligned on the epoch at `originMs + t`\n// (the source's own, possibly shifted, clock), each with its exact count, minimum, maximum and\n// sum. A bucket that holds no row is not returned. Ordered by bucket start and then name, as\n// the event store orders them. The window is in the source's clock and is moved back to\n// recording offsets by `originMs` before the rows are read.\nexport function foldBuckets(\n rec: BoardRecording,\n groups: ReadonlyArray<{ deviceToken: string; names: ReadonlySet<string> }>,\n byDevice: ReadonlyMap<string, readonly SeriesRows[]>,\n window: HistoryWindow,\n originMs: number,\n cursor: number,\n): MeasurementBucket[] {\n const series: SeriesRows[] = [];\n for (const g of groups) {\n for (const s of byDevice.get(g.deviceToken) ?? []) {\n if (g.names.size === 0 || g.names.has(s.name)) series.push(s);\n }\n }\n const widthMs = window.intervalSeconds * 1000;\n const fromMs = Date.parse(window.startTime) - originMs;\n const toMs = Math.min(Date.parse(window.endTime) - originMs, cursor);\n if (!(widthMs > 0) || !Number.isFinite(fromMs) || !Number.isFinite(toMs)) {\n throw new RangeError(`bucketMeasurements: not a window: ${JSON.stringify(window)}`);\n }\n const { t, v } = rec.measurements;\n const out: MeasurementBucket[] = [];\n for (const s of series) {\n const byStart = new Map<number, { bucketStart: string; name: string; avg: number; min: number; max: number; sum: number; count: number }>();\n for (let i = lowerBound(s.times, fromMs), end = upperBound(s.times, toMs); i < end; i++) {\n const row = s.rows[i];\n const start = Math.floor((originMs + t[row]) / widthMs) * widthMs;\n const value = v[row];\n const b = byStart.get(start);\n if (!b) {\n byStart.set(start, { bucketStart: new Date(start).toISOString(), name: s.name, avg: value, min: value, max: value, sum: value, count: 1 });\n } else {\n b.count += 1;\n b.sum += value;\n if (value < b.min) b.min = value;\n if (value > b.max) b.max = value;\n b.avg = b.sum / b.count;\n }\n }\n out.push(...byStart.values());\n }\n out.sort((a, b) => (a.bucketStart < b.bucketStart ? -1 : a.bucketStart > b.bucketStart ? 1 : a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n return out;\n}\n\n// Each device's position rows (indices into the location columns) and their times, built once\n// per parsed recording (a view is rebuilt after every seek).\nconst positionsCache = new WeakMap<BoardRecording, Map<string, { rows: number[]; times: number[] }>>();\n\nfunction positionsOf(rec: BoardRecording): Map<string, { rows: number[]; times: number[] }> {\n let positions = positionsCache.get(rec);\n if (positions) return positions;\n positions = new Map();\n const loc = rec.locations;\n for (let i = 0; i < loc.t.length; i++) {\n const token = rec.devices[loc.d[i]].token;\n let p = positions.get(token);\n if (!p) positions.set(token, (p = { rows: [], times: [] }));\n p.rows.push(i);\n p.times.push(loc.t[i]);\n }\n positionsCache.set(rec, positions);\n return positions;\n}\n\n// lastPositions is each device's last position at `cursor` (absent for a device not yet\n// located), and a signature that changes exactly when one of them moves on to a new fix.\nexport function lastPositions(\n rec: BoardRecording,\n tokens: readonly string[],\n cursor: number,\n iso: (offsetMs: number) => string,\n): { signature: string; locations: LocationSample[] } {\n const loc = rec.locations;\n const positions = positionsOf(rec);\n const rows: Array<[string, number]> = [];\n for (const token of tokens) {\n const p = positions.get(token);\n if (!p) continue;\n const at = upperBound(p.times, cursor) - 1;\n if (at >= 0) rows.push([token, p.rows[at]]);\n }\n return {\n signature: rows.map(([, row]) => row).join(','),\n locations: rows.map(([token, row]) => ({\n id: `${token}|location|${row}`,\n deviceToken: token,\n latitude: loc.lat[row],\n longitude: loc.lon[row],\n elevation: null,\n accuracy: null,\n speed: loc.spd[row],\n heading: loc.hdg[row],\n occurredTime: iso(loc.t[row]),\n })),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/history.d.ts CHANGED
@@ -6,4 +6,6 @@ export interface HistoryWindow {
6
6
  }
7
7
  export declare function historyWindowFor(spanSeconds: number, end: number): HistoryWindow;
8
8
  export declare function defaultHistoryWindow(): HistoryWindow;
9
- export declare function fetchWidgetHistory(widget: WidgetInstance, window: HistoryWindow): Promise<MeasurementSample[]>;
9
+ export declare function fetchWidgetHistory(widget: WidgetInstance, window: HistoryWindow, options?: {
10
+ rethrow?: boolean;
11
+ }): Promise<MeasurementSample[]>;
package/dist/hub.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface DeviceResolver {
14
14
  export interface WidgetStreamSink {
15
15
  next: (sample: MeasurementSample) => void;
16
16
  error?: (err: unknown) => void;
17
+ reconnected?: () => void;
17
18
  }
18
19
  export interface AlarmSubscription {
19
20
  datasource?: DatasourceSelector;
@@ -81,6 +82,7 @@ export interface WidgetDataSource {
81
82
  subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;
82
83
  subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;
83
84
  isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;
85
+ isDatasourceSupported?(datasource: DatasourceSelector | undefined): Promise<boolean>;
84
86
  now?(): number;
85
87
  bucketMeasurements?(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]>;
86
88
  subscribeMembers?(datasource: DatasourceSelector, sink: MemberSink): () => void;
@@ -128,6 +130,7 @@ export declare class DashboardHub implements WidgetDataSource, WidgetActions {
128
130
  disposeAll(): void;
129
131
  get openStreamCount(): number;
130
132
  isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;
133
+ isDatasourceSupported(datasource: DatasourceSelector | undefined): Promise<boolean>;
131
134
  private availabilityToken;
132
135
  private resolveDevices;
133
136
  private resolveBinding;
package/dist/index.d.ts CHANGED
@@ -11,8 +11,8 @@ export { sameLabel } from './same-label';
11
11
  export { ConnectionTracker, OFFLINE_AFTER_MS, type ConnectionHandle, type HubConnectionState } from './connection';
12
12
  export { DashboardHub, type DashboardHubConfig, type DeviceResolver, type WidgetStreamSink, type WidgetDataSource, type AlarmSubscription, type AlarmSnapshot, type AlarmStreamSink, type CommandSubscription, type CommandSnapshot, type CommandStreamSink, type CommandDispatch, type LocationSubscription, type LocationSnapshot, type LocationStreamSink, type WidgetActions, } from './hub';
13
13
  export { SyntheticDataSource, SYNTHETIC_GENERATORS, type SyntheticGenerator, type SyntheticDataSourceConfig, } from './synthetic';
14
- export { RECORDING_FORMAT_VERSION, RECORDING_KIND, RecordingFormatError, parseBoardRecording, recordedWallTimeMs, recordingBounds, simulationScaleAt, type BoardRecording, type RecordedAlarm, type RecordedAlarmEvent, type RecordedAlarmSnapshot, type RecordedChannels, type RecordedChapter, type RecordedClockSegment, type RecordedDevice, type RecordedMeasurementColumns, } from './recording';
15
- export { PLAYBACK_RATES, RECORDED_HISTORY_WINDOW_MS, NotInRecordingError, RecordedClock, RecordedDataSource, createRecordedClock, createRecordedLister, createRecordedResolver, type ClockEvent, type ClockTicker, type PlaybackRate, type RecordedChannel, type RecordedSiteOptions, } from './recorded';
14
+ export { RECORDING_FORMAT_VERSION, RECORDING_FORMAT_VERSIONS, RECORDING_KIND, RecordingFormatError, parseBoardRecording, recordedWallTimeMs, recordingBounds, simulationScaleAt, type BoardRecording, type RecordedAlarm, type RecordedAlarmEvent, type RecordedAlarmSnapshot, type RecordedChannels, type RecordedChapter, type RecordedClockSegment, type RecordedDevice, type RecordedLocationColumns, type RecordedMeasurementColumns, } from './recording';
15
+ export { PLAYBACK_RATES, RECORDED_HISTORY_WINDOW_MS, NotInRecordingError, RecordedClock, RecordedDataSource, createRecordedClock, createRecordedLister, createRecordedResolver, retimeShiftMs, type ClockEvent, type ClockTicker, type PlaybackRate, type RecordedChannel, type RecordedSiteOptions, type RecordedSourceOptions, } from './recorded';
16
16
  export { parseDashboardDefinition, type ParseDefinitionOptions, parseSlotBinding, serializeDefinition, storedDefinition, isDirty, withoutConversionMark, generateWidgetId, canScopeSlot, DashboardDefinitionError, BASE_BREAKPOINT, } from './definition';
17
17
  export { basePlacement, clampSpan, moveWidget, resizeWidget, setWidgetPlacement, deleteWidget, setTitle, setCanvasGrid, setCanvasSizing, setCanvasDensity, updateWidget, addWidget, canHostRelative, fitToLimits, formatSpan, OFFSET_STEP, OFFSET_STEP_FINE, placeOnGrid, placeRelative, setRelativeAnchor, setRelativeOffset, snapOffset, gridPlacementToPx, pxToGridPlacement, type EditOptions, type PixelRect, type RelativeBounds, type GridGeometry, } from './editor-model';
18
18
  export { DEFAULT_HISTORY_LIMIT, canRedo, canUndo, commitEdit, createEditHistory, rebaseHistory, redoEdit, sealHistory, undoEdit, type CommitOptions, type EditHistory, type HistoryEntry, type Step, } from './editor-history';