@glasshome/sync-layer 0.9.1 → 0.10.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as buildDeviceView, B as registerEntity, C as unloadDemoData, D as isEnergyEntity, E as demoEnergyPreferences, F as getAllEntityViews, G as setState, H as setResubscribeHandler, I as getEntityView, K as state, L as getEntityViews, M as getAreaViews, N as buildEntityView, O as synthesizeEnergyHistory, P as entityViewEquals, R as extractDomain, S as stopDemoEnergyTicker, T as demoAssetUrl, U as privilegedConn, V as setManagerConnection, W as setPrivilegedConn, _ as updateEntity, a as fetchForecastData, b as loadDemoData, c as normalizeStatisticTime, d as batchServiceCalls, f as callService, g as turnOn, h as turnOff, i as fetchForecast, j as getAreaView, k as buildAreaView, l as numericSensorHistory, m as toggle, o as fetchForecasts, p as sendCommand, s as fetchStatisticsDuringPeriod, t as forceResubscribeCalendars, u as batchEntityUpdates, v as applyDemoServiceCall, w as createDemoFixtures, x as startDemoEnergyTicker, y as isDemoMode, z as forceResubscribe } from "./track-IkwP6XmO.js";
1
+ import { $ as setPrivilegedConn, A as unloadDemoData, B as getAreaView, C as demoTimeMap, D as resetDemo, E as loadDemoHouse, F as MAX_HISTORY_POINTS, G as getEntityView, H as buildEntityView, I as appendHistoryPoint, J as forceResubscribe, K as getEntityViews, L as bulkAppendHistoryPoints, M as demoEnergyPreferences, N as isEnergyEntity, O as startDemoEnergyTicker, P as synthesizeEnergyHistory, Q as privilegedConn, R as buildAreaView, S as demoReplayModel, T as loadDemoData, U as entityViewEquals, V as getAreaViews, W as getAllEntityViews, X as setManagerConnection, Y as registerEntity, Z as setResubscribeHandler, _ as applyDemoServiceCall, a as fetchForecastData, b as demoCallLog, c as normalizeStatisticTime, d as callService, et as setState, f as sendCommand, g as updateEntity, h as turnOn, i as fetchForecast, j as demoAssetUrl, k as stopDemoEnergyTicker, l as batchEntityUpdates, m as turnOff, o as fetchForecasts, p as toggle, q as extractDomain, s as fetchStatisticsDuringPeriod, t as forceResubscribeCalendars, tt as state, u as batchServiceCalls, v as applyVisitorHolds, w as isDemoMode, x as demoModel, y as createDemoFixtures, z as buildDeviceView } from "./track-DssLNusE.js";
2
2
  import { t as runHaBridgeWorker } from "./worker-main-B6eeHPDQ.js";
3
3
  import { produce } from "solid-js/store";
4
4
  //#region src/core/reducers.ts
@@ -995,6 +995,92 @@ function entityIdHistoryNeedsAttributes(entityId) {
995
995
  return NEED_ATTRIBUTE_DOMAINS.includes(domain);
996
996
  }
997
997
  //#endregion
998
+ //#region src/demo/sim/history.ts
999
+ const DAY_MS = 864e5;
1000
+ const STEP_MS = 3e5;
1001
+ const TICK_WALL_MS = 1e3;
1002
+ /** Unix seconds, matching the energy branch's wire format (`synthesizeEnergyHistory`). */
1003
+ function toSeconds(ms) {
1004
+ return Math.round(ms / 1e3);
1005
+ }
1006
+ function pushPoint(track, point) {
1007
+ track.lastState = point.s;
1008
+ const last = track.points.at(-1);
1009
+ if (last && last.lu === point.lu) track.points[track.points.length - 1] = point;
1010
+ else track.points.push(point);
1011
+ }
1012
+ /**
1013
+ * Replays the demo house once from `startMs` to `endMs` (sim time) and reports each entity's state changes.
1014
+ * `toWallMs` stamps `lu` on the caller's clock.
1015
+ */
1016
+ function demoHistory(entityIds, startMs, endMs, toWallMs = (ms) => ms) {
1017
+ const result = {};
1018
+ for (const id of entityIds) result[id] = [];
1019
+ const live = demoModel();
1020
+ if (!live) return result;
1021
+ const tracks = [];
1022
+ for (const entityId of new Set(entityIds)) {
1023
+ const device = live.deviceOf(entityId);
1024
+ if (device) tracks.push({
1025
+ entityId,
1026
+ deviceKey: device.key,
1027
+ points: [],
1028
+ lastState: void 0
1029
+ });
1030
+ }
1031
+ if (tracks.length === 0) return result;
1032
+ const liveNowMs = live.nowMs;
1033
+ const endC = toWallMs(liveNowMs) - toWallMs(endMs) <= TICK_WALL_MS ? liveNowMs : endMs;
1034
+ const startC = Math.max(startMs, endC - DAY_MS);
1035
+ if (startC >= endC) return result;
1036
+ const log = demoCallLog();
1037
+ const first = log[0];
1038
+ const modelStartMs = first ? Math.min(startC, first.simMs) : startC;
1039
+ const replayModel = demoReplayModel(modelStartMs);
1040
+ if (!replayModel) return result;
1041
+ const replay = replayModel;
1042
+ function record(simMs) {
1043
+ if (simMs < startC) return;
1044
+ for (const track of tracks) {
1045
+ const p = replay.projectDevice(track.deviceKey)[track.entityId];
1046
+ if (!p || p.state === track.lastState) continue;
1047
+ pushPoint(track, {
1048
+ s: p.state,
1049
+ a: p.attributes,
1050
+ lu: toSeconds(toWallMs(simMs))
1051
+ });
1052
+ }
1053
+ }
1054
+ record(replay.nowMs);
1055
+ let logIdx = 0;
1056
+ while (replay.nowMs < endC) {
1057
+ const next = log[logIdx];
1058
+ const nextLogMs = next ? next.simMs : Number.POSITIVE_INFINITY;
1059
+ const windowBoundary = replay.nowMs < startC ? startC : Number.POSITIVE_INFINITY;
1060
+ const targetMs = Math.min(replay.nowMs + STEP_MS, endC, nextLogMs, windowBoundary);
1061
+ replay.advanceTo(targetMs, STEP_MS);
1062
+ record(replay.nowMs);
1063
+ if (next && next.simMs === replay.nowMs) {
1064
+ replay.dispatch(next.call);
1065
+ applyVisitorHolds(replay, next.call.entityIds);
1066
+ logIdx++;
1067
+ record(replay.nowMs);
1068
+ }
1069
+ }
1070
+ for (const track of tracks) {
1071
+ if (endC === liveNowMs) {
1072
+ const p = live.projectDevice(track.deviceKey)[track.entityId];
1073
+ if (p && p.state !== track.lastState) pushPoint(track, {
1074
+ s: p.state,
1075
+ a: p.attributes,
1076
+ lu: toSeconds(toWallMs(endC))
1077
+ });
1078
+ }
1079
+ result[track.entityId] = track.points.length > 5760 ? track.points.slice(-MAX_HISTORY_POINTS) : track.points;
1080
+ }
1081
+ return result;
1082
+ }
1083
+ //#endregion
998
1084
  //#region src/history/fetch.ts
999
1085
  /**
1000
1086
  * History Fetching
@@ -1006,18 +1092,19 @@ function entityIdHistoryNeedsAttributes(entityId) {
1006
1092
  */
1007
1093
  async function fetchHistory(options) {
1008
1094
  const { startTime, endTime, entityIds = [], includeStartTimeState = true, significantChangesOnly = true, minimalResponse = true, noAttributes: noAttributesOverride } = options;
1009
- if (isDemoMode()) {
1010
- const endMs = (endTime ?? /* @__PURE__ */ new Date()).getTime();
1095
+ const timeMap = demoTimeMap();
1096
+ if (timeMap) {
1097
+ const startMs = timeMap.toSim(startTime.getTime());
1098
+ const endMs = timeMap.toSim((endTime ?? /* @__PURE__ */ new Date()).getTime());
1099
+ const energyStepMs = timeMap.toSim(6e4) - timeMap.toSim(0);
1100
+ const toWallSeconds = (simSeconds) => Math.round(timeMap.toWall(simSeconds * 1e3) / 1e3);
1101
+ const replayed = demoHistory(entityIds.filter((id) => !isEnergyEntity(id)), startMs, endMs, (ms) => timeMap.toWall(ms));
1011
1102
  const result = {};
1012
- for (const id of entityIds) {
1013
- const points = isEnergyEntity(id) ? synthesizeEnergyHistory(id, startTime.getTime(), endMs) : numericSensorHistory(id, startTime.getTime(), endMs);
1014
- if (!points) continue;
1015
- result[id] = points.map((p) => ({
1016
- s: p.s,
1017
- a: {},
1018
- lu: p.lu
1019
- }));
1020
- }
1103
+ for (const id of entityIds) result[id] = isEnergyEntity(id) ? synthesizeEnergyHistory(id, startMs, endMs, energyStepMs).map((p) => ({
1104
+ s: p.s,
1105
+ a: {},
1106
+ lu: toWallSeconds(p.lu)
1107
+ })) : replayed[id] ?? [];
1021
1108
  return result;
1022
1109
  }
1023
1110
  const startTimeStr = startTime.toISOString();
@@ -1088,8 +1175,6 @@ function historyStateToTimeline(s) {
1088
1175
  }
1089
1176
  //#endregion
1090
1177
  //#region src/history/query.ts
1091
- /** Hard cap on retained points per entity timeline. */
1092
- const MAX_HISTORY_POINTS = 5760;
1093
1178
  /** Live tracker count per entity. Ref-counted because a timeline is shared: two
1094
1179
  * widgets on the same entity must not let the first unmount delete the store
1095
1180
  * data the second is still rendering (appendHistoryPoint would then silently
@@ -1120,64 +1205,6 @@ function untrackEntityHistory(entityId) {
1120
1205
  delete s.history[entityId];
1121
1206
  }));
1122
1207
  }
1123
- /**
1124
- * Append a state change to an entity's history.
1125
- * Called by the entity subscription handler when a tracked entity changes.
1126
- */
1127
- function appendHistoryPoint(entityId, stateValue, attributes, lastUpdated, lastChanged) {
1128
- setState(produce((s) => {
1129
- const history = s.history[entityId];
1130
- if (!history) return;
1131
- const point = {
1132
- s: stateValue,
1133
- a: attributes,
1134
- lu: lastUpdated,
1135
- lc: lastChanged
1136
- };
1137
- history.entityHistory.push(point);
1138
- history.timeline.push({
1139
- timestamp: lastUpdated,
1140
- state: stateValue,
1141
- attributes,
1142
- lastChanged,
1143
- lastUpdated
1144
- });
1145
- }));
1146
- }
1147
- /**
1148
- * Append multiple history points in a single setState(produce(...)).
1149
- * Called by the rAF-batched subscription flush.
1150
- */
1151
- function bulkAppendHistoryPoints(points) {
1152
- if (points.length === 0) return;
1153
- setState(produce((s) => {
1154
- const touched = /* @__PURE__ */ new Set();
1155
- for (const { entityId, stateValue, attributes, lastUpdated, lastChanged } of points) {
1156
- const history = s.history[entityId];
1157
- if (!history) continue;
1158
- history.entityHistory.push({
1159
- s: stateValue,
1160
- a: attributes,
1161
- lu: lastUpdated,
1162
- lc: lastChanged
1163
- });
1164
- history.timeline.push({
1165
- timestamp: lastUpdated,
1166
- state: stateValue,
1167
- attributes,
1168
- lastChanged,
1169
- lastUpdated
1170
- });
1171
- touched.add(entityId);
1172
- }
1173
- for (const entityId of touched) {
1174
- const history = s.history[entityId];
1175
- if (!history) continue;
1176
- if (history.entityHistory.length > 5760) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
1177
- if (history.timeline.length > 5760) history.timeline.splice(0, history.timeline.length - MAX_HISTORY_POINTS);
1178
- }
1179
- }));
1180
- }
1181
1208
  //#endregion
1182
1209
  //#region src/weather/query.ts
1183
1210
  /**
@@ -1811,7 +1838,7 @@ function detachBridgeFromStore() {
1811
1838
  * host's link with one the widget controls — every subsequent service call is
1812
1839
  * intercepted and every entity the dashboard renders can be fabricated.
1813
1840
  * - disconnect / detachBridgeFromStore: drop the household's connection.
1814
- * - loadDemoData / unloadDemoData: overwrite live state with fixtures.
1841
+ * - loadDemoData / loadDemoHouse / resetDemo / unloadDemoData: overwrite live state with the demo house.
1815
1842
  */
1816
1843
  let unclaimed = {
1817
1844
  attachBridgeToStore,
@@ -1821,6 +1848,8 @@ let unclaimed = {
1821
1848
  createHaBridge,
1822
1849
  disconnect,
1823
1850
  loadDemoData,
1851
+ loadDemoHouse,
1852
+ resetDemo,
1824
1853
  unloadDemoData
1825
1854
  };
1826
1855
  /**
@@ -1,4 +1,4 @@
1
- import { B as registerEntity, G as setState, I as getEntityView, K as state, M as getAreaViews, P as entityViewEquals, R as extractDomain, U as privilegedConn, f as callService, g as turnOn, h as turnOff, k as buildAreaView, m as toggle, n as trackCalendarEvents, r as buildDemoForecast, s as fetchStatisticsDuringPeriod, y as isDemoMode } from "../track-IkwP6XmO.js";
1
+ import { G as getEntityView, Q as privilegedConn, R as buildAreaView, U as entityViewEquals, V as getAreaViews, Y as registerEntity, d as callService, et as setState, h as turnOn, m as turnOff, n as trackCalendarEvents, p as toggle, q as extractDomain, r as buildDemoForecast, s as fetchStatisticsDuringPeriod, tt as state, w as isDemoMode } from "../track-DssLNusE.js";
2
2
  import { produce } from "solid-js/store";
3
3
  import { createEffect, createMemo, createResource, onCleanup } from "solid-js";
4
4
  //#region src/weather/track.ts