@glasshome/sync-layer 0.1.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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/area-views-B-m8vwce.js +321 -0
  4. package/dist/camera/fetch.d.ts +15 -0
  5. package/dist/camera/query.d.ts +15 -0
  6. package/dist/camera/types.d.ts +85 -0
  7. package/dist/camera/webrtc.d.ts +31 -0
  8. package/dist/commands/service.d.ts +53 -0
  9. package/dist/commands/types.d.ts +113 -0
  10. package/dist/connection/adapter.d.ts +48 -0
  11. package/dist/connection/auth.d.ts +126 -0
  12. package/dist/connection/manager.d.ts +42 -0
  13. package/dist/connection/subscriptions.d.ts +25 -0
  14. package/dist/connection/types.d.ts +161 -0
  15. package/dist/core/reducers.d.ts +99 -0
  16. package/dist/core/store.d.ts +64 -0
  17. package/dist/core/types.d.ts +336 -0
  18. package/dist/demo/demo-data.d.ts +24 -0
  19. package/dist/demo/demo-provider.d.ts +33 -0
  20. package/dist/demo/demo-provider.js +103 -0
  21. package/dist/demo-data-D8JXWRpw.js +419 -0
  22. package/dist/entities/area-views.d.ts +25 -0
  23. package/dist/entities/index.d.ts +54 -0
  24. package/dist/entities/views.d.ts +30 -0
  25. package/dist/history/constants.d.ts +16 -0
  26. package/dist/history/fetch.d.ts +19 -0
  27. package/dist/history/query.d.ts +13 -0
  28. package/dist/history/types.d.ts +146 -0
  29. package/dist/index.d.ts +38 -0
  30. package/dist/index.js +1747 -0
  31. package/dist/query/area-builder.d.ts +25 -0
  32. package/dist/query/builder.d.ts +18 -0
  33. package/dist/query/helpers.d.ts +21 -0
  34. package/dist/query/memoization.d.ts +18 -0
  35. package/dist/query/projections.d.ts +74 -0
  36. package/dist/query/types.d.ts +260 -0
  37. package/dist/solid/hooks.d.ts +88 -0
  38. package/dist/solid/index.d.ts +10 -0
  39. package/dist/solid/index.js +176 -0
  40. package/dist/solid/indices.d.ts +14 -0
  41. package/dist/store-B1sbyexX.js +54 -0
  42. package/dist/testing/fixtures.d.ts +161 -0
  43. package/dist/testing/helpers.d.ts +160 -0
  44. package/dist/testing/index.d.ts +13 -0
  45. package/dist/testing/index.js +2047 -0
  46. package/dist/testing/mock-behaviors/base.d.ts +73 -0
  47. package/dist/testing/mock-behaviors/climate.d.ts +22 -0
  48. package/dist/testing/mock-behaviors/cover.d.ts +22 -0
  49. package/dist/testing/mock-behaviors/index.d.ts +11 -0
  50. package/dist/testing/mock-behaviors/light.d.ts +20 -0
  51. package/dist/testing/mock-behaviors/lock.d.ts +20 -0
  52. package/dist/testing/mock-behaviors/media_player.d.ts +42 -0
  53. package/dist/testing/mock-behaviors/registry.d.ts +55 -0
  54. package/dist/testing/mock-behaviors/switch.d.ts +17 -0
  55. package/dist/testing/mock.d.ts +191 -0
  56. package/dist/testing/types.d.ts +87 -0
  57. package/dist/types-Csog0nqk.js +10 -0
  58. package/dist/weather/fetch.d.ts +19 -0
  59. package/dist/weather/query.d.ts +23 -0
  60. package/dist/weather/types.d.ts +126 -0
  61. package/package.json +85 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Core type definitions for @glasshome/sync-layer
3
+ *
4
+ * This file defines types that are:
5
+ * 1. **Used from ha-types**: WebSocket commands, events, registry entries
6
+ * 2. **Runtime data structures**: Entity states, device/area/floor/label registries
7
+ * (These should eventually be moved to ha-types but are defined here for now
8
+ * as ha-types currently only has WebSocket command types)
9
+ * 3. **Sync-layer specific**: EntityView, views
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import type { AreaEntry, DeviceEntry, EntityCategory, EntityRegistryEntry } from "@glasshome/ha-types";
14
+ /**
15
+ * Unique entity identifier (e.g., "light.living_room")
16
+ */
17
+ export type EntityId = string;
18
+ /**
19
+ * Domain extracted from entity_id (e.g., "light")
20
+ */
21
+ export type EntityDomain = string;
22
+ /**
23
+ * Unique device identifier (UUID)
24
+ */
25
+ export type DeviceId = string;
26
+ /**
27
+ * Unique area identifier
28
+ */
29
+ export type AreaId = string;
30
+ /**
31
+ * Floor identifier
32
+ */
33
+ export type FloorId = string;
34
+ /**
35
+ * Label identifier
36
+ */
37
+ export type LabelId = string;
38
+ /**
39
+ * Runtime entity state from Home Assistant
40
+ *
41
+ * This represents the live state of an entity as received
42
+ * from the WebSocket API's `get_states` command or `subscribe_entities` subscription.
43
+ */
44
+ export interface HassEntity {
45
+ /** Unique entity identifier */
46
+ entity_id: EntityId;
47
+ /** Current state value (e.g., "on", "off", "23.5") */
48
+ state: string;
49
+ /** Entity attributes (brightness, temperature, etc.) */
50
+ attributes: Record<string, any>;
51
+ /** ISO 8601 timestamp of last state change */
52
+ last_changed: string;
53
+ /** ISO 8601 timestamp of last update */
54
+ last_updated: string;
55
+ /** Event context */
56
+ context: {
57
+ /** Context ID */
58
+ id: string;
59
+ /** Parent context ID */
60
+ parent_id: string | null;
61
+ /** User ID that triggered the change */
62
+ user_id: string | null;
63
+ };
64
+ }
65
+ /**
66
+ * Device registry entry
67
+ *
68
+ * @alias DeviceEntry
69
+ */
70
+ export type DeviceRegistryEntry = DeviceEntry;
71
+ /**
72
+ * Area registry entry
73
+ *
74
+ * @alias AreaEntry
75
+ * @note AreaEntry uses 'id' field, not 'area_id'
76
+ */
77
+ export type AreaRegistryEntry = AreaEntry;
78
+ /**
79
+ * Floor registry entry
80
+ *
81
+ * Response structure from `config/floor_registry/list`
82
+ */
83
+ export interface FloorRegistryEntry {
84
+ /** Unique floor identifier */
85
+ floor_id: FloorId;
86
+ /** Floor name */
87
+ name: string;
88
+ /** Floor level (integer, 0 = ground floor) */
89
+ level: number;
90
+ /** Floor icon */
91
+ icon: string | null;
92
+ /** Floor aliases */
93
+ aliases: string[];
94
+ /** Created timestamp */
95
+ created_at: string;
96
+ /** Modified timestamp */
97
+ modified_at: string;
98
+ }
99
+ /**
100
+ * Label registry entry
101
+ *
102
+ * Response structure from `config/label_registry/list`
103
+ */
104
+ export interface LabelRegistryEntry {
105
+ /** Unique label identifier */
106
+ label_id: LabelId;
107
+ /** Label name */
108
+ name: string;
109
+ /** Label color (hex) */
110
+ color: string | null;
111
+ /** Label icon */
112
+ icon: string | null;
113
+ /** Label description */
114
+ description: string | null;
115
+ /** Created timestamp */
116
+ created_at: string;
117
+ /** Modified timestamp */
118
+ modified_at: string;
119
+ }
120
+ /**
121
+ * Statistics metadata
122
+ *
123
+ * Response structure from `recorder/get_statistics_metadata`
124
+ */
125
+ export interface StatisticMetadata {
126
+ /** Statistic ID */
127
+ statistic_id: string;
128
+ /** Data source */
129
+ source: string;
130
+ /** Unit of measurement */
131
+ unit_of_measurement: string | null;
132
+ /** Whether statistic has mean values */
133
+ has_mean: boolean;
134
+ /** Whether statistic has sum values */
135
+ has_sum: boolean;
136
+ /** Display name */
137
+ name: string | null;
138
+ }
139
+ /**
140
+ * Statistics data point
141
+ *
142
+ * Response structure from `recorder/statistics_during_period`
143
+ */
144
+ export interface StatisticData {
145
+ /** Statistic ID */
146
+ statistic_id: string;
147
+ /** Period start (ISO 8601) */
148
+ start: string;
149
+ /** Period end (ISO 8601) */
150
+ end: string;
151
+ /** Mean value */
152
+ mean?: number;
153
+ /** Minimum value */
154
+ min?: number;
155
+ /** Maximum value */
156
+ max?: number;
157
+ /** Last reset timestamp */
158
+ last_reset?: string;
159
+ /** State value */
160
+ state?: number;
161
+ /** Sum value */
162
+ sum?: number;
163
+ }
164
+ /**
165
+ * Unified entity view combining runtime state and registry metadata
166
+ *
167
+ * This is the primary interface for working with entities in the sync-layer.
168
+ * It combines live state data with registry metadata to provide a complete
169
+ * view of an entity.
170
+ */
171
+ export interface EntityView {
172
+ /** Unique entity identifier */
173
+ id: EntityId;
174
+ /** Entity domain (e.g., "light", "sensor") */
175
+ domain: EntityDomain;
176
+ /** Current state value */
177
+ state: string;
178
+ /** Entity attributes */
179
+ attributes: Record<string, any>;
180
+ /** Last state change timestamp */
181
+ lastChanged: Date;
182
+ /** Last update timestamp */
183
+ lastUpdated: Date;
184
+ /** Event context */
185
+ context: {
186
+ id: string;
187
+ parentId: string | null;
188
+ userId: string | null;
189
+ };
190
+ /** Entity name (from registry or friendly_name) */
191
+ name: string;
192
+ /** Friendly display name */
193
+ friendlyName: string;
194
+ /** Area ID where entity is located */
195
+ areaId: AreaId | null;
196
+ /** Device ID this entity belongs to */
197
+ deviceId: DeviceId | null;
198
+ /** Platform/integration that provides this entity */
199
+ platform: string;
200
+ /** Unique ID from integration */
201
+ uniqueId: string | null;
202
+ /** Whether entity is disabled */
203
+ isDisabled: boolean;
204
+ /** Whether entity is hidden */
205
+ isHidden: boolean;
206
+ /** Resolved icon (registry icon, attribute icon, or domain default) */
207
+ icon: string | null;
208
+ /** Icon source (registry, attribute, or default) */
209
+ iconSource: "registry" | "attribute" | "default";
210
+ /** Entity category (config, diagnostic, or null) */
211
+ entityCategory: EntityCategory | null;
212
+ /** Labels applied to this entity */
213
+ labels: LabelId[];
214
+ /** Entity aliases */
215
+ aliases: string[];
216
+ /** Device class (e.g., "temperature", "humidity") */
217
+ deviceClass?: string | null;
218
+ /** Unit of measurement */
219
+ unitOfMeasurement?: string | null;
220
+ /** Supported features bitmask */
221
+ supportedFeatures?: number;
222
+ }
223
+ /**
224
+ * Device view combining registry data with computed properties
225
+ */
226
+ export interface DeviceView {
227
+ /** Unique device identifier */
228
+ id: DeviceId;
229
+ /** Device name */
230
+ name: string;
231
+ /** User-friendly name */
232
+ nameByUser: string | null;
233
+ /** Manufacturer */
234
+ manufacturer: string | null;
235
+ /** Model */
236
+ model: string | null;
237
+ /** Software version */
238
+ swVersion: string | null;
239
+ /** Area ID where device is located */
240
+ areaId: AreaId | null;
241
+ /** Configuration entry IDs */
242
+ configEntries: string[];
243
+ /** Device connections */
244
+ connections: Array<[string, string]>;
245
+ /** Device identifiers */
246
+ identifiers: Array<[string, string]>;
247
+ /** Whether device is disabled */
248
+ disabledBy: string | null;
249
+ }
250
+ /**
251
+ * Area view combining registry data with computed entities and devices
252
+ */
253
+ export interface AreaView {
254
+ /** Unique area identifier */
255
+ id: AreaId;
256
+ /** Area name */
257
+ name: string;
258
+ /** Normalized area name (for searching) */
259
+ normalizedName: string;
260
+ /** Area aliases */
261
+ aliases: string[];
262
+ /** Floor ID this area belongs to */
263
+ floorId: FloorId | null;
264
+ /** Area icon */
265
+ icon: string | null;
266
+ /** Area picture */
267
+ picture: string | null;
268
+ /** Labels applied to this area */
269
+ labels: LabelId[];
270
+ /** Temperature entity ID for this area */
271
+ temperatureEntityId: EntityId | null;
272
+ /** Humidity entity ID for this area */
273
+ humidityEntityId: EntityId | null;
274
+ /** Created timestamp */
275
+ createdAt: string;
276
+ /** Last modified timestamp */
277
+ modifiedAt: string;
278
+ /** Entities in this area (direct + via devices) - uses query engine */
279
+ entities: EntityView[];
280
+ /** Devices in this area */
281
+ devices: DeviceView[];
282
+ /** Entity IDs for fast lookups */
283
+ entityIds: EntityId[];
284
+ /** Device IDs for fast lookups */
285
+ deviceIds: DeviceId[];
286
+ }
287
+ /**
288
+ * WebSocket connection state
289
+ */
290
+ export type ConnectionState = "connecting" | "connected" | "disconnected" | "reconnecting" | "error";
291
+ /**
292
+ * Unsubscribe function returned by subscriptions
293
+ */
294
+ export type UnsubscribeFunc = () => void;
295
+ /**
296
+ * Generic callback type
297
+ */
298
+ export type Callback<T> = (data: T) => void;
299
+ /**
300
+ * Predicate function for filtering
301
+ */
302
+ export type PredicateFunc<T> = (item: T) => boolean;
303
+ /**
304
+ * Comparison function for sorting
305
+ */
306
+ export type CompareFunc<T> = (a: T, b: T) => number;
307
+ /**
308
+ * Mapping function for transformations
309
+ */
310
+ export type MapFunc<T, R> = (item: T) => R;
311
+ /**
312
+ * Registry update event data
313
+ */
314
+ export interface RegistryUpdateEvent {
315
+ action: "create" | "update" | "remove";
316
+ entity_id: EntityId;
317
+ entity?: EntityRegistryEntry;
318
+ }
319
+ /**
320
+ * Statistics update event data
321
+ */
322
+ export interface StatisticsUpdateEvent {
323
+ statistic_ids: string[];
324
+ }
325
+ /**
326
+ * Check if a value is a valid EntityId
327
+ */
328
+ export declare function isEntityId(value: unknown): value is EntityId;
329
+ /**
330
+ * Extract domain from entity ID
331
+ */
332
+ export declare function extractDomain(entityId: EntityId): EntityDomain;
333
+ /**
334
+ * Check if entity is in a specific domain
335
+ */
336
+ export declare function isDomain(entityId: EntityId, domain: EntityDomain): boolean;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Demo Mode Fixtures
3
+ *
4
+ * ~30 curated apartment entities for showcasing the dashboard without
5
+ * a real Home Assistant instance. Covers all widget-relevant domains.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import type { EntityRegistryEntry } from "@glasshome/ha-types";
10
+ import type { AreaRegistryEntry, DeviceRegistryEntry, HassEntity } from "../core/types";
11
+ export interface DemoFixtures {
12
+ entities: Record<string, HassEntity>;
13
+ entityRegistry: Record<string, EntityRegistryEntry>;
14
+ areas: Record<string, AreaRegistryEntry>;
15
+ devices: Record<string, DeviceRegistryEntry>;
16
+ }
17
+ /**
18
+ * Create ~30 curated apartment entity fixtures.
19
+ *
20
+ * Entities span lights, sensors, binary sensors, climate, covers,
21
+ * locks, switches, media player, weather, camera, scenes, and buttons
22
+ * across 4 areas: living_room, bedroom, kitchen, entry.
23
+ */
24
+ export declare function createDemoFixtures(): DemoFixtures;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Demo Mode Provider
3
+ *
4
+ * Loads demo fixtures into the SolidJS store and provides
5
+ * optimistic local state updates for service calls (toggle, turnOn, turnOff).
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Whether demo mode is currently active.
11
+ */
12
+ export declare function isDemoMode(): boolean;
13
+ /**
14
+ * Load demo fixtures into the SolidJS store.
15
+ *
16
+ * Populates entities, entity registry, areas, and devices.
17
+ * Sets connection state to "connected" (pretend connected).
18
+ */
19
+ export declare function loadDemoData(): Promise<void>;
20
+ /**
21
+ * Unload demo data and reset to disconnected state.
22
+ */
23
+ export declare function unloadDemoData(): void;
24
+ /**
25
+ * Apply an optimistic state update for demo mode.
26
+ *
27
+ * When a service call is made in demo mode, this updates the entity
28
+ * state locally (no WebSocket message sent). State resets on page refresh.
29
+ */
30
+ export declare function applyDemoServiceCall(domain: string, service: string, _serviceData?: Record<string, any>, target?: {
31
+ entity_id?: string | string[];
32
+ }): void;
33
+ export { createDemoFixtures } from "./demo-data";
@@ -0,0 +1,103 @@
1
+ import { setState, state } from "../store-B1sbyexX.js";
2
+ import { extractDomain } from "../types-Csog0nqk.js";
3
+ import { createDemoFixtures } from "../demo-data-D8JXWRpw.js";
4
+ import { produce, reconcile } from "solid-js/store";
5
+
6
+ //#region src/demo/demo-provider.ts
7
+ let _isDemoMode = false;
8
+ /**
9
+ * Whether demo mode is currently active.
10
+ */
11
+ function isDemoMode() {
12
+ return _isDemoMode;
13
+ }
14
+ /**
15
+ * Load demo fixtures into the SolidJS store.
16
+ *
17
+ * Populates entities, entity registry, areas, and devices.
18
+ * Sets connection state to "connected" (pretend connected).
19
+ */
20
+ async function loadDemoData() {
21
+ const fixtures = createDemoFixtures();
22
+ setState("entities", reconcile(fixtures.entities));
23
+ setState("entityRegistry", reconcile(fixtures.entityRegistry));
24
+ setState("areas", reconcile(fixtures.areas));
25
+ setState("devices", reconcile(fixtures.devices));
26
+ setState("connectionState", "connected");
27
+ _isDemoMode = true;
28
+ }
29
+ /**
30
+ * Unload demo data and reset to disconnected state.
31
+ */
32
+ function unloadDemoData() {
33
+ setState("entities", reconcile({}));
34
+ setState("entityRegistry", reconcile({}));
35
+ setState("areas", reconcile({}));
36
+ setState("devices", reconcile({}));
37
+ setState("connectionState", "disconnected");
38
+ _isDemoMode = false;
39
+ }
40
+ /**
41
+ * Apply an optimistic state update for demo mode.
42
+ *
43
+ * When a service call is made in demo mode, this updates the entity
44
+ * state locally (no WebSocket message sent). State resets on page refresh.
45
+ */
46
+ function applyDemoServiceCall(domain, service, _serviceData = {}, target = {}) {
47
+ if (!_isDemoMode) return;
48
+ const entityIds = Array.isArray(target.entity_id) ? target.entity_id : target.entity_id ? [target.entity_id] : [];
49
+ for (const entityId of entityIds) {
50
+ const entity = state.entities[entityId];
51
+ if (!entity) continue;
52
+ const entityDomain = extractDomain(entityId);
53
+ setState(produce((s) => {
54
+ const e = s.entities[entityId];
55
+ if (!e) return;
56
+ const now = (/* @__PURE__ */ new Date()).toISOString();
57
+ e.last_changed = now;
58
+ e.last_updated = now;
59
+ if (service === "turn_on") applyTurnOn(e, entityDomain, _serviceData);
60
+ else if (service === "turn_off") applyTurnOff(e, entityDomain);
61
+ else if (service === "toggle") if (e.state === "on" || e.state === "playing" || e.state === "open") applyTurnOff(e, entityDomain);
62
+ else applyTurnOn(e, entityDomain, _serviceData);
63
+ else if (service === "lock") e.state = "locked";
64
+ else if (service === "unlock") e.state = "unlocked";
65
+ else if (service === "open_cover") {
66
+ e.state = "open";
67
+ e.attributes.current_position = 100;
68
+ } else if (service === "close_cover") {
69
+ e.state = "closed";
70
+ e.attributes.current_position = 0;
71
+ } else if (service === "set_cover_position" && _serviceData.position != null) {
72
+ e.attributes.current_position = _serviceData.position;
73
+ e.state = _serviceData.position > 0 ? "open" : "closed";
74
+ } else if (service === "set_temperature" && _serviceData.temperature != null) e.attributes.temperature = _serviceData.temperature;
75
+ else if (service === "set_hvac_mode" && _serviceData.hvac_mode != null) e.state = _serviceData.hvac_mode;
76
+ else if (service === "volume_set" && _serviceData.volume_level != null) e.attributes.volume_level = _serviceData.volume_level;
77
+ }));
78
+ }
79
+ }
80
+ function applyTurnOn(e, domain, serviceData = {}) {
81
+ if (domain === "light") {
82
+ e.state = "on";
83
+ e.attributes.brightness = serviceData.brightness ?? e.attributes.brightness ?? 255;
84
+ if (serviceData.color_temp_kelvin) e.attributes.color_temp_kelvin = serviceData.color_temp_kelvin;
85
+ } else if (domain === "media_player") e.state = "playing";
86
+ else if (domain === "cover") {
87
+ e.state = "open";
88
+ e.attributes.current_position = 100;
89
+ } else e.state = "on";
90
+ }
91
+ function applyTurnOff(e, domain) {
92
+ if (domain === "light") {
93
+ e.state = "off";
94
+ delete e.attributes.brightness;
95
+ } else if (domain === "media_player") e.state = "paused";
96
+ else if (domain === "cover") {
97
+ e.state = "closed";
98
+ e.attributes.current_position = 0;
99
+ } else e.state = "off";
100
+ }
101
+
102
+ //#endregion
103
+ export { applyDemoServiceCall, createDemoFixtures, isDemoMode, loadDemoData, unloadDemoData };