@glasshome/sync-layer 0.9.1 → 0.10.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/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-BAfgOjMk.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,91 @@ 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 modelStartMs = log.length > 0 ? Math.min(startC, log[0].simMs) : startC;
1038
+ const replayModel = demoReplayModel(modelStartMs);
1039
+ if (!replayModel) return result;
1040
+ const replay = replayModel;
1041
+ function record(simMs) {
1042
+ if (simMs < startC) return;
1043
+ for (const track of tracks) {
1044
+ const p = replay.projectDevice(track.deviceKey)[track.entityId];
1045
+ if (!p || p.state === track.lastState) continue;
1046
+ pushPoint(track, {
1047
+ s: p.state,
1048
+ a: p.attributes,
1049
+ lu: toSeconds(toWallMs(simMs))
1050
+ });
1051
+ }
1052
+ }
1053
+ record(replay.nowMs);
1054
+ let logIdx = 0;
1055
+ while (replay.nowMs < endC) {
1056
+ const nextLogMs = logIdx < log.length ? log[logIdx].simMs : Number.POSITIVE_INFINITY;
1057
+ const windowBoundary = replay.nowMs < startC ? startC : Number.POSITIVE_INFINITY;
1058
+ const targetMs = Math.min(replay.nowMs + STEP_MS, endC, nextLogMs, windowBoundary);
1059
+ replay.advanceTo(targetMs, STEP_MS);
1060
+ record(replay.nowMs);
1061
+ if (logIdx < log.length && log[logIdx].simMs === replay.nowMs) {
1062
+ const entry = log[logIdx];
1063
+ replay.dispatch(entry.call);
1064
+ applyVisitorHolds(replay, entry.call.entityIds);
1065
+ logIdx++;
1066
+ record(replay.nowMs);
1067
+ }
1068
+ }
1069
+ for (const track of tracks) {
1070
+ if (endC === liveNowMs) {
1071
+ const p = live.projectDevice(track.deviceKey)[track.entityId];
1072
+ if (p && p.state !== track.lastState) pushPoint(track, {
1073
+ s: p.state,
1074
+ a: p.attributes,
1075
+ lu: toSeconds(toWallMs(endC))
1076
+ });
1077
+ }
1078
+ result[track.entityId] = track.points.length > 5760 ? track.points.slice(-MAX_HISTORY_POINTS) : track.points;
1079
+ }
1080
+ return result;
1081
+ }
1082
+ //#endregion
998
1083
  //#region src/history/fetch.ts
999
1084
  /**
1000
1085
  * History Fetching
@@ -1006,18 +1091,19 @@ function entityIdHistoryNeedsAttributes(entityId) {
1006
1091
  */
1007
1092
  async function fetchHistory(options) {
1008
1093
  const { startTime, endTime, entityIds = [], includeStartTimeState = true, significantChangesOnly = true, minimalResponse = true, noAttributes: noAttributesOverride } = options;
1009
- if (isDemoMode()) {
1010
- const endMs = (endTime ?? /* @__PURE__ */ new Date()).getTime();
1094
+ const timeMap = demoTimeMap();
1095
+ if (timeMap) {
1096
+ const startMs = timeMap.toSim(startTime.getTime());
1097
+ const endMs = timeMap.toSim((endTime ?? /* @__PURE__ */ new Date()).getTime());
1098
+ const energyStepMs = timeMap.toSim(6e4) - timeMap.toSim(0);
1099
+ const toWallSeconds = (simSeconds) => Math.round(timeMap.toWall(simSeconds * 1e3) / 1e3);
1100
+ const replayed = demoHistory(entityIds.filter((id) => !isEnergyEntity(id)), startMs, endMs, timeMap.toWall);
1011
1101
  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
- }
1102
+ for (const id of entityIds) result[id] = isEnergyEntity(id) ? synthesizeEnergyHistory(id, startMs, endMs, energyStepMs).map((p) => ({
1103
+ s: p.s,
1104
+ a: {},
1105
+ lu: toWallSeconds(p.lu)
1106
+ })) : replayed[id] ?? [];
1021
1107
  return result;
1022
1108
  }
1023
1109
  const startTimeStr = startTime.toISOString();
@@ -1088,8 +1174,6 @@ function historyStateToTimeline(s) {
1088
1174
  }
1089
1175
  //#endregion
1090
1176
  //#region src/history/query.ts
1091
- /** Hard cap on retained points per entity timeline. */
1092
- const MAX_HISTORY_POINTS = 5760;
1093
1177
  /** Live tracker count per entity. Ref-counted because a timeline is shared: two
1094
1178
  * widgets on the same entity must not let the first unmount delete the store
1095
1179
  * data the second is still rendering (appendHistoryPoint would then silently
@@ -1120,64 +1204,6 @@ function untrackEntityHistory(entityId) {
1120
1204
  delete s.history[entityId];
1121
1205
  }));
1122
1206
  }
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
1207
  //#endregion
1182
1208
  //#region src/weather/query.ts
1183
1209
  /**
@@ -1811,7 +1837,7 @@ function detachBridgeFromStore() {
1811
1837
  * host's link with one the widget controls — every subsequent service call is
1812
1838
  * intercepted and every entity the dashboard renders can be fabricated.
1813
1839
  * - disconnect / detachBridgeFromStore: drop the household's connection.
1814
- * - loadDemoData / unloadDemoData: overwrite live state with fixtures.
1840
+ * - loadDemoData / loadDemoHouse / resetDemo / unloadDemoData: overwrite live state with the demo house.
1815
1841
  */
1816
1842
  let unclaimed = {
1817
1843
  attachBridgeToStore,
@@ -1821,6 +1847,8 @@ let unclaimed = {
1821
1847
  createHaBridge,
1822
1848
  disconnect,
1823
1849
  loadDemoData,
1850
+ loadDemoHouse,
1851
+ resetDemo,
1824
1852
  unloadDemoData
1825
1853
  };
1826
1854
  /**
@@ -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-BAfgOjMk.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