@glasshome/sync-layer 0.1.12 → 0.1.14

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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Energy Preferences Fetching
3
+ *
4
+ * Reads HA's energy dashboard config via `energy/get_prefs`.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import type { SyncLayerConnection } from "../connection/types";
9
+ /**
10
+ * An energy source entry. Only the fields the widgets read are typed; the
11
+ * rest is passthrough. HA emits one of several shapes keyed by `type`:
12
+ * - grid: `flow_from[].stat_energy_from`, `flow_to[].stat_energy_to`
13
+ * - solar: `stat_energy_from`
14
+ * - battery: `stat_energy_from`, `stat_energy_to`
15
+ */
16
+ export interface EnergySource {
17
+ type: "grid" | "solar" | "battery" | "gas" | "water" | string;
18
+ stat_energy_from?: string;
19
+ stat_energy_to?: string;
20
+ flow_from?: Array<{
21
+ stat_energy_from: string;
22
+ [key: string]: unknown;
23
+ }>;
24
+ flow_to?: Array<{
25
+ stat_energy_to: string;
26
+ [key: string]: unknown;
27
+ }>;
28
+ [key: string]: unknown;
29
+ }
30
+ /** A device tracked on the energy dashboard. */
31
+ export interface EnergyDeviceConsumption {
32
+ stat_consumption: string;
33
+ name?: string;
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * HA energy dashboard preferences.
38
+ *
39
+ * Based on HA frontend: EnergyPreferences.
40
+ */
41
+ export interface EnergyPreferences {
42
+ energy_sources: EnergySource[];
43
+ device_consumption: EnergyDeviceConsumption[];
44
+ [key: string]: unknown;
45
+ }
46
+ /** Connection surface used by energy prefs fetching. */
47
+ type EnergyConnection = Pick<SyncLayerConnection, "sendMessagePromise">;
48
+ /**
49
+ * Fetch the HA energy dashboard preferences.
50
+ *
51
+ * Returns `null` when no energy dashboard is configured (HA raises an error
52
+ * for `energy/get_prefs` in that case); callers treat `null` as "not
53
+ * configured".
54
+ */
55
+ export declare function fetchEnergyPreferences(connection: EnergyConnection): Promise<EnergyPreferences | null>;
56
+ export {};
@@ -7,6 +7,8 @@ export interface HistoryPoint {
7
7
  lastUpdated: number;
8
8
  lastChanged?: number;
9
9
  }
10
+ /** Hard cap on retained points per entity timeline. */
11
+ export declare const MAX_HISTORY_POINTS = 5760;
10
12
  /** Check if an entity has active history tracking. */
11
13
  export declare function isHistoryTracked(entityId: EntityId): boolean;
12
14
  /** Start tracking history for an entity. Fetches initial backfill. */
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Long-term Statistics Fetching
3
+ *
4
+ * Reads HA's recorder long-term statistics via `recorder/statistics_during_period`.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import type { SyncLayerConnection } from "../connection/types";
9
+ /**
10
+ * A single statistics bucket for one statistic id.
11
+ *
12
+ * HA 2023.6+ omits columns that were not requested and may omit null
13
+ * columns, so every value field is optional. `start`/`end` are normalized
14
+ * to ms epoch numbers regardless of the version's wire format.
15
+ */
16
+ export interface StatisticValue {
17
+ /** Bucket start (ms epoch) */
18
+ start: number;
19
+ /** Bucket end (ms epoch) */
20
+ end: number;
21
+ mean?: number;
22
+ sum?: number;
23
+ min?: number;
24
+ max?: number;
25
+ state?: number;
26
+ change?: number;
27
+ }
28
+ /** Aggregation period for statistics buckets. */
29
+ export type StatisticsPeriod = "5minute" | "hour" | "day" | "week" | "month";
30
+ /**
31
+ * Options for {@link fetchStatisticsDuringPeriod}.
32
+ */
33
+ export interface StatisticsQueryOptions {
34
+ /** Start time (required) */
35
+ startTime: Date;
36
+ /** End time (optional, defaults to now on the HA side) */
37
+ endTime?: Date;
38
+ /** Aggregation period */
39
+ period: StatisticsPeriod;
40
+ }
41
+ /** Connection surface used by statistics fetching. */
42
+ type StatisticsConnection = Pick<SyncLayerConnection, "sendMessagePromise">;
43
+ /**
44
+ * HA returns start/end as ISO strings or ms numbers depending on version.
45
+ * Normalize both shapes to ms epoch numbers; unparseable values become NaN.
46
+ */
47
+ export declare function normalizeStatisticTime(value: unknown): number;
48
+ /**
49
+ * Fetch long-term statistics for one or more statistic ids during a period.
50
+ *
51
+ * Statistic ids with no data are returned as empty arrays so callers never
52
+ * hit `undefined`.
53
+ */
54
+ export declare function fetchStatisticsDuringPeriod(connection: StatisticsConnection, statisticIds: string[], options: StatisticsQueryOptions): Promise<Record<string, StatisticValue[]>>;
55
+ export {};
package/dist/index.d.ts CHANGED
@@ -26,14 +26,18 @@ export { resetStore, setState, state } from "./core/store";
26
26
  export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, RegistryEntryDisabler, RegistryEntryHider, ServiceCall, ServiceName, WsCommandType, WsResult, } from "@glasshome/ha-types";
27
27
  export { fetchStream, fetchStreamData } from "./camera/fetch";
28
28
  export { getStream, refreshStream } from "./camera/query";
29
+ export { fetchEnergyPreferences } from "./energy/prefs";
30
+ export type { EnergyDeviceConsumption, EnergyPreferences, EnergySource, } from "./energy/prefs";
29
31
  export type { CameraStream, CameraStreamData, CameraStreams, EntityStreamQueryOptions, StreamFormat, StreamQueryOptions, StreamResult, } from "./camera/types";
30
32
  export { getWebRtcClientConfig, sendWebRtcCandidate, startWebRtcSession, } from "./camera/webrtc";
31
33
  export { buildAreaView, buildDeviceView, getAreaView, getAreaViews, } from "./entities/area-views";
32
34
  export { buildEntityView, entityViewEquals, getAllEntityViews, getEntityView, getEntityViews, } from "./entities/views";
33
35
  export { DOMAINS_USE_LAST_UPDATED, entityIdHistoryNeedsAttributes, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, } from "./history/constants";
34
36
  export { fetchEntityHistory, fetchHistory, historyStateToTimeline } from "./history/fetch";
35
- export { appendHistoryPoint, bulkAppendHistoryPoints, isHistoryTracked, trackEntityHistory, untrackEntityHistory, } from "./history/query";
37
+ export { appendHistoryPoint, bulkAppendHistoryPoints, isHistoryTracked, MAX_HISTORY_POINTS, trackEntityHistory, untrackEntityHistory, } from "./history/query";
36
38
  export type { HistoryPoint } from "./history/query";
39
+ export { fetchStatisticsDuringPeriod, normalizeStatisticTime, } from "./history/statistics";
40
+ export type { StatisticsPeriod, StatisticsQueryOptions, StatisticValue, } from "./history/statistics";
37
41
  export type { EntityHistoryData, EntityHistoryQueryOptions, EntityHistoryResult, EntityHistoryState, HistoryData, HistoryQueryOptions, HistoryResult, HistoryStates, TimelineState, } from "./history/types";
38
42
  export { fetchForecast, fetchForecastData, fetchForecasts } from "./weather/fetch";
39
43
  export { getForecast, getForecasts, refreshForecast, refreshForecasts } from "./weather/query";
package/dist/index.js CHANGED
@@ -807,6 +807,8 @@ function historyStateToTimeline(s) {
807
807
 
808
808
  //#endregion
809
809
  //#region src/history/query.ts
810
+ /** Hard cap on retained points per entity timeline. */
811
+ const MAX_HISTORY_POINTS = 5760;
810
812
  /** Set of entity IDs with active history tracking. */
811
813
  const trackedEntities = /* @__PURE__ */ new Set();
812
814
  /** Check if an entity has active history tracking. */
@@ -860,6 +862,7 @@ function appendHistoryPoint(entityId, stateValue, attributes, lastUpdated, lastC
860
862
  function bulkAppendHistoryPoints(points) {
861
863
  if (points.length === 0) return;
862
864
  setState(produce((s) => {
865
+ const touched = /* @__PURE__ */ new Set();
863
866
  for (const { entityId, stateValue, attributes, lastUpdated, lastChanged } of points) {
864
867
  const history = s.history[entityId];
865
868
  if (!history) continue;
@@ -876,6 +879,13 @@ function bulkAppendHistoryPoints(points) {
876
879
  lastChanged,
877
880
  lastUpdated
878
881
  });
882
+ touched.add(entityId);
883
+ }
884
+ for (const entityId of touched) {
885
+ const history = s.history[entityId];
886
+ if (!history) continue;
887
+ if (history.entityHistory.length > MAX_HISTORY_POINTS) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
888
+ if (history.timeline.length > MAX_HISTORY_POINTS) history.timeline.splice(0, history.timeline.length - MAX_HISTORY_POINTS);
879
889
  }
880
890
  }));
881
891
  }
@@ -2164,6 +2174,35 @@ async function refreshStream(entityId, format = "hls") {
2164
2174
  return getStream(entityId, { format });
2165
2175
  }
2166
2176
 
2177
+ //#endregion
2178
+ //#region src/energy/prefs.ts
2179
+ function isRecord(value) {
2180
+ return typeof value === "object" && value !== null;
2181
+ }
2182
+ /**
2183
+ * Fetch the HA energy dashboard preferences.
2184
+ *
2185
+ * Returns `null` when no energy dashboard is configured (HA raises an error
2186
+ * for `energy/get_prefs` in that case); callers treat `null` as "not
2187
+ * configured".
2188
+ */
2189
+ async function fetchEnergyPreferences(connection) {
2190
+ let response;
2191
+ try {
2192
+ response = await connection.sendMessagePromise({ type: "energy/get_prefs" });
2193
+ } catch {
2194
+ return null;
2195
+ }
2196
+ if (!isRecord(response)) return null;
2197
+ const sources = Array.isArray(response.energy_sources) ? response.energy_sources.filter(isRecord) : [];
2198
+ const devices = Array.isArray(response.device_consumption) ? response.device_consumption.filter(isRecord) : [];
2199
+ return {
2200
+ ...response,
2201
+ energy_sources: sources,
2202
+ device_consumption: devices
2203
+ };
2204
+ }
2205
+
2167
2206
  //#endregion
2168
2207
  //#region src/camera/webrtc.ts
2169
2208
  /**
@@ -2248,6 +2287,70 @@ async function sendWebRtcCandidate(entityId, sessionId, candidate) {
2248
2287
  });
2249
2288
  }
2250
2289
 
2290
+ //#endregion
2291
+ //#region src/history/statistics.ts
2292
+ const STATISTIC_TYPES = [
2293
+ "mean",
2294
+ "sum",
2295
+ "min",
2296
+ "max",
2297
+ "state",
2298
+ "change"
2299
+ ];
2300
+ /**
2301
+ * HA returns start/end as ISO strings or ms numbers depending on version.
2302
+ * Normalize both shapes to ms epoch numbers; unparseable values become NaN.
2303
+ */
2304
+ function normalizeStatisticTime(value) {
2305
+ if (typeof value === "number") return value;
2306
+ if (typeof value === "string") return new Date(value).getTime();
2307
+ return NaN;
2308
+ }
2309
+ function normalizeNumber(value) {
2310
+ return typeof value === "number" ? value : void 0;
2311
+ }
2312
+ function normalizeStatisticValue(raw) {
2313
+ const value = {
2314
+ start: normalizeStatisticTime(raw.start),
2315
+ end: normalizeStatisticTime(raw.end)
2316
+ };
2317
+ const mean = normalizeNumber(raw.mean);
2318
+ if (mean !== void 0) value.mean = mean;
2319
+ const sum = normalizeNumber(raw.sum);
2320
+ if (sum !== void 0) value.sum = sum;
2321
+ const min = normalizeNumber(raw.min);
2322
+ if (min !== void 0) value.min = min;
2323
+ const max = normalizeNumber(raw.max);
2324
+ if (max !== void 0) value.max = max;
2325
+ const stateVal = normalizeNumber(raw.state);
2326
+ if (stateVal !== void 0) value.state = stateVal;
2327
+ const change = normalizeNumber(raw.change);
2328
+ if (change !== void 0) value.change = change;
2329
+ return value;
2330
+ }
2331
+ /**
2332
+ * Fetch long-term statistics for one or more statistic ids during a period.
2333
+ *
2334
+ * Statistic ids with no data are returned as empty arrays so callers never
2335
+ * hit `undefined`.
2336
+ */
2337
+ async function fetchStatisticsDuringPeriod(connection, statisticIds, options) {
2338
+ const { startTime, endTime, period } = options;
2339
+ const params = {
2340
+ type: "recorder/statistics_during_period",
2341
+ start_time: startTime.toISOString(),
2342
+ statistic_ids: statisticIds,
2343
+ period,
2344
+ types: STATISTIC_TYPES
2345
+ };
2346
+ if (endTime) params.end_time = endTime.toISOString();
2347
+ const response = await connection.sendMessagePromise(params);
2348
+ const result = {};
2349
+ for (const id of statisticIds) result[id] = [];
2350
+ if (response && typeof response === "object") for (const [id, buckets] of Object.entries(response)) result[id] = Array.isArray(buckets) ? buckets.map(normalizeStatisticValue) : [];
2351
+ return result;
2352
+ }
2353
+
2251
2354
  //#endregion
2252
2355
  //#region src/weather/fetch.ts
2253
2356
  /**
@@ -2510,4 +2613,4 @@ async function refreshForecasts(entityId, types) {
2510
2613
  const VERSION = "0.1.0";
2511
2614
 
2512
2615
  //#endregion
2513
- export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
2616
+ export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, MAX_HISTORY_POINTS, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEnergyPreferences, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStatisticsDuringPeriod, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, normalizeStatisticTime, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
@@ -10,8 +10,8 @@
10
10
  *
11
11
  * @packageDocumentation
12
12
  */
13
- import type { Accessor } from "solid-js";
14
- import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, WeatherForecastsData } from "@glasshome/sync-layer";
13
+ import type { Accessor, Resource } from "solid-js";
14
+ import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, StatisticsQueryOptions, StatisticValue, WeatherForecastsData } from "@glasshome/sync-layer";
15
15
  import { callService, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
16
16
  /**
17
17
  * Get a reactive EntityView for an entity ID.
@@ -71,6 +71,14 @@ export declare function useArea(areaId: Accessor<string> | string): Accessor<Are
71
71
  * this hook simply reads it reactively.
72
72
  */
73
73
  export declare function useEntityHistory(entityId: Accessor<string> | string): Accessor<EntityHistoryData | undefined>;
74
+ /**
75
+ * Get reactive long-term statistics for a statistic id.
76
+ *
77
+ * Fetches via `recorder/statistics_during_period` and re-fetches whenever the
78
+ * statistic id or options change. The returned Resource exposes `.loading`
79
+ * and `.error` alongside the value accessor.
80
+ */
81
+ export declare function useEntityStatistics(statisticId: Accessor<string> | string, options: Accessor<StatisticsQueryOptions> | StatisticsQueryOptions): Resource<StatisticValue[]>;
74
82
  /**
75
83
  * Get reactive camera stream data.
76
84
  */
@@ -6,5 +6,5 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
- export { useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn, } from "./hooks";
9
+ export { useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn, } from "./hooks";
10
10
  export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, } from "./indices";
@@ -1,5 +1,5 @@
1
- import { createEffect, createMemo, onCleanup } from "solid-js";
2
- import { buildAreaView, callService, entityViewEquals, extractDomain, getEntityView, registerEntity, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
1
+ import { createEffect, createMemo, createResource, onCleanup } from "solid-js";
2
+ import { buildAreaView, callService, entityViewEquals, extractDomain, fetchStatisticsDuringPeriod, getEntityView, registerEntity, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
3
3
 
4
4
  //#region src/solid/hooks.ts
5
5
  /**
@@ -152,6 +152,28 @@ function useEntityHistory(entityId) {
152
152
  return createMemo(() => state.history[getId()]);
153
153
  }
154
154
  /**
155
+ * Get reactive long-term statistics for a statistic id.
156
+ *
157
+ * Fetches via `recorder/statistics_during_period` and re-fetches whenever the
158
+ * statistic id or options change. The returned Resource exposes `.loading`
159
+ * and `.error` alongside the value accessor.
160
+ */
161
+ function useEntityStatistics(statisticId, options) {
162
+ const getId = typeof statisticId === "function" ? statisticId : () => statisticId;
163
+ const getOptions = typeof options === "function" ? options : () => options;
164
+ const [data] = createResource(() => ({
165
+ id: getId(),
166
+ options: getOptions()
167
+ }), async ({ id, options: opts }) => {
168
+ if (!id) return [];
169
+ const conn = state.conn;
170
+ if (!conn) return [];
171
+ const result = await fetchStatisticsDuringPeriod(conn, [id], opts);
172
+ return result[id] ?? [];
173
+ });
174
+ return data;
175
+ }
176
+ /**
155
177
  * Get reactive camera stream data.
156
178
  */
157
179
  function useCamera(entityId) {
@@ -179,9 +201,6 @@ function useStore(selector) {
179
201
 
180
202
  //#endregion
181
203
  //#region src/solid/indices.ts
182
- /**
183
- * Group items by a key function
184
- */
185
204
  function groupBy(items, keyFn) {
186
205
  const result = {};
187
206
  for (const item of items) {
@@ -190,26 +209,14 @@ function groupBy(items, keyFn) {
190
209
  }
191
210
  return result;
192
211
  }
193
- /**
194
- * Create a lazily-initialized memo. The actual createMemo runs on first
195
- * access, inside whichever Solid root is current at that point.
196
- * This avoids top-level createMemo calls that execute outside any root.
197
- */
198
- function lazyMemo(fn) {
199
- let memo;
200
- return () => {
201
- if (!memo) memo = createMemo(fn);
202
- return memo();
203
- };
204
- }
205
212
  /** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
206
- const byDomain = lazyMemo(() => groupBy(Object.keys(state.entities), (id) => extractDomain(id)));
213
+ const byDomain = () => groupBy(Object.keys(state.entities), (id) => extractDomain(id));
207
214
  /** Entity IDs grouped by area (via entity registry area_id) */
208
- const byArea = lazyMemo(() => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.area_id ?? null));
215
+ const byArea = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.area_id ?? null);
209
216
  /** Entity IDs grouped by device (via entity registry device_id) */
210
- const byDevice = lazyMemo(() => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.device_id ?? null));
217
+ const byDevice = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.device_id ?? null);
211
218
  /** Entity IDs grouped by floor (device -> area -> floor chain) */
212
- const byFloor = lazyMemo(() => {
219
+ const byFloor = () => {
213
220
  const result = {};
214
221
  for (const [entityId, reg] of Object.entries(state.entityRegistry)) if (reg.device_id) {
215
222
  const device = state.devices[reg.device_id];
@@ -219,15 +226,15 @@ const byFloor = lazyMemo(() => {
219
226
  }
220
227
  }
221
228
  return result;
222
- });
229
+ };
223
230
  /** Entity IDs grouped by label */
224
- const byLabel = lazyMemo(() => {
231
+ const byLabel = () => {
225
232
  const result = {};
226
233
  for (const [entityId, reg] of Object.entries(state.entityRegistry)) for (const label of reg.labels ?? []) (result[label] ??= []).push(entityId);
227
234
  return result;
228
- });
235
+ };
229
236
  /** All entity IDs currently in the store */
230
- const allEntityIds = lazyMemo(() => Object.keys(state.entities));
237
+ const allEntityIds = () => Object.keys(state.entities);
231
238
 
232
239
  //#endregion
233
- export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
240
+ export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
@@ -1,4 +1,5 @@
1
- /** Reactive derived entity indices — lazily created inside the caller's root. */
1
+ /** Reactive derived entity indices. Each call reads from the SolidJS store directly,
2
+ * so consumers wrap in createMemo within their own reactive root for memoization. */
2
3
  import type { Accessor } from "solid-js";
3
4
  /** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
4
5
  export declare const byDomain: Accessor<Record<string, string[]>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/sync-layer",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Type-safe, reactive state synchronization layer for Home Assistant",
5
5
  "repository": {
6
6
  "type": "git",