@glasshome/sync-layer 0.8.2 → 0.9.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/README.md +3 -16
- package/dist/index.d.ts +21 -49
- package/dist/index.js +1364 -1541
- package/dist/{service-C8H42I-Q.d.ts → service-B1gegd-N.d.ts} +2 -2
- package/dist/solid/index.d.ts +5 -4
- package/dist/solid/index.js +111 -3
- package/dist/{statistics-DyCCTwv2.js → track-IkwP6XmO.js} +781 -496
- package/dist/worker/worker-main.js +1 -1
- package/dist/{worker-main-DamaVb7B.js → worker-main-B6eeHPDQ.js} +4 -3
- package/package.json +7 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AreaEntry, DeviceEntry, Domain, EntityCategory,
|
|
1
|
+
import { AreaEntry, DeviceEntry, Domain, EntityCategory, ServiceCall, ServiceName, WsCommandType } from "@glasshome/ha-types";
|
|
2
2
|
//#region src/core/types.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Unique entity identifier (e.g., "light.living_room")
|
|
@@ -436,7 +436,7 @@ declare function updateEntity(entityId: EntityId, updates: EntityUpdateFields):
|
|
|
436
436
|
* Send a generic WebSocket command
|
|
437
437
|
*/
|
|
438
438
|
declare function sendCommand<T = unknown>(command: {
|
|
439
|
-
type: WsCommandType | string;
|
|
439
|
+
type: WsCommandType | (string & {});
|
|
440
440
|
[key: string]: unknown;
|
|
441
441
|
}): Promise<T>;
|
|
442
442
|
/**
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as toggle, g as CalendarWindowOptions, h as CalendarEventsData, o as turnOff, r as callService, s as turnOn } from "../service-
|
|
1
|
+
import { a as toggle, g as CalendarWindowOptions, h as CalendarEventsData, o as turnOff, r as callService, s as turnOn } from "../service-B1gegd-N.js";
|
|
2
2
|
import { Accessor, Resource } from "solid-js";
|
|
3
3
|
import { HassConfig, HassUnitSystem } from "@glasshome/ha-types";
|
|
4
|
-
import { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, StatisticValue, StatisticsQueryOptions, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
4
|
+
import { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, ForecastType, GlassHomeState, HassEntity, StatisticValue, StatisticsQueryOptions, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
5
5
|
//#region src/solid/hooks.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Get a reactive EntityView for an entity ID.
|
|
@@ -96,9 +96,10 @@ declare function useCamera(entityId: Accessor<string> | string): {
|
|
|
96
96
|
*/
|
|
97
97
|
declare function useCalendarEvents(entityId: Accessor<string> | string, options?: CalendarWindowOptions): Accessor<CalendarEventsData | undefined>;
|
|
98
98
|
/**
|
|
99
|
-
* Get reactive weather forecast data.
|
|
99
|
+
* Get reactive weather forecast data. With `types`, the forecast stays live
|
|
100
|
+
* while the caller is mounted: Home Assistant pushes each new one, no polling.
|
|
100
101
|
*/
|
|
101
|
-
declare function useForecast(entityId: Accessor<string> | string): Accessor<WeatherForecastsData | undefined>;
|
|
102
|
+
declare function useForecast(entityId: Accessor<string> | string, types?: ForecastType[]): Accessor<WeatherForecastsData | undefined>;
|
|
102
103
|
/**
|
|
103
104
|
* Escape hatch for direct store access with a selector.
|
|
104
105
|
*
|
package/dist/solid/index.js
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
|
+
import { produce } from "solid-js/store";
|
|
2
3
|
import { createEffect, createMemo, createResource, onCleanup } from "solid-js";
|
|
4
|
+
//#region src/weather/track.ts
|
|
5
|
+
/**
|
|
6
|
+
* Ref-counted live forecasts, the weather analog of `calendar/track`. Each
|
|
7
|
+
* tracked (entity, type) holds one `weather/subscribe_forecast`; Home Assistant
|
|
8
|
+
* pushes the current forecast, then every new one. Data lands in
|
|
9
|
+
* `state.forecasts`, the same place `getForecasts` writes.
|
|
10
|
+
*/
|
|
11
|
+
/** Debounce for untrack, so dashboard switches don't churn subscriptions. */
|
|
12
|
+
const UNTRACK_FLUSH_DELAY_MS = 5e3;
|
|
13
|
+
const counts = /* @__PURE__ */ new Map();
|
|
14
|
+
const active = /* @__PURE__ */ new Map();
|
|
15
|
+
const chains = /* @__PURE__ */ new Map();
|
|
16
|
+
let untrackTimer = null;
|
|
17
|
+
const keyOf = (entityId, type) => `${entityId}|${type}`;
|
|
18
|
+
function parse(key) {
|
|
19
|
+
const i = key.lastIndexOf("|");
|
|
20
|
+
return [key.slice(0, i), key.slice(i + 1)];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Keep a forecast live while tracked. Returns the untrack function. Safe while
|
|
24
|
+
* disconnected: the subscription starts on the next `forceResubscribeForecasts()`.
|
|
25
|
+
*/
|
|
26
|
+
function trackForecast(entityId, type) {
|
|
27
|
+
const key = keyOf(entityId, type);
|
|
28
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
29
|
+
queueMicrotask(() => {
|
|
30
|
+
reconcile$1(key);
|
|
31
|
+
});
|
|
32
|
+
let untracked = false;
|
|
33
|
+
return () => {
|
|
34
|
+
if (untracked) return;
|
|
35
|
+
untracked = true;
|
|
36
|
+
const n = (counts.get(key) ?? 1) - 1;
|
|
37
|
+
if (n > 0) counts.set(key, n);
|
|
38
|
+
else counts.delete(key);
|
|
39
|
+
if (untrackTimer) return;
|
|
40
|
+
untrackTimer = setTimeout(() => {
|
|
41
|
+
untrackTimer = null;
|
|
42
|
+
flushAll();
|
|
43
|
+
}, UNTRACK_FLUSH_DELAY_MS);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function flushAll() {
|
|
47
|
+
const keys = /* @__PURE__ */ new Set([...counts.keys(), ...active.keys()]);
|
|
48
|
+
await Promise.all([...keys].map(reconcile$1));
|
|
49
|
+
}
|
|
50
|
+
function reconcile$1(key) {
|
|
51
|
+
const next = (chains.get(key) ?? Promise.resolve()).then(() => runReconcile(key));
|
|
52
|
+
chains.set(key, next.catch(() => {}));
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
function write(entityId, type, forecast, error) {
|
|
56
|
+
setState(produce((s) => {
|
|
57
|
+
const rec = s.forecasts[entityId] ??= {
|
|
58
|
+
entityId,
|
|
59
|
+
forecasts: {},
|
|
60
|
+
loading: {},
|
|
61
|
+
errors: {},
|
|
62
|
+
lastFetched: {}
|
|
63
|
+
};
|
|
64
|
+
if (forecast) {
|
|
65
|
+
rec.forecasts[type] = forecast;
|
|
66
|
+
rec.lastFetched[type] = Date.now();
|
|
67
|
+
}
|
|
68
|
+
rec.loading[type] = false;
|
|
69
|
+
rec.errors[type] = error;
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async function runReconcile(key) {
|
|
73
|
+
const [entityId, type] = parse(key);
|
|
74
|
+
const current = active.get(key);
|
|
75
|
+
if (!counts.has(key)) {
|
|
76
|
+
active.delete(key);
|
|
77
|
+
chains.delete(key);
|
|
78
|
+
try {
|
|
79
|
+
await current?.unsub?.();
|
|
80
|
+
} catch {}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (current?.unsub) return;
|
|
84
|
+
if (isDemoMode()) {
|
|
85
|
+
active.set(key, { unsub: () => {} });
|
|
86
|
+
write(entityId, type, buildDemoForecast(entityId, type), null);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const conn = privilegedConn();
|
|
90
|
+
if (!conn) return;
|
|
91
|
+
const entry = { unsub: null };
|
|
92
|
+
active.set(key, entry);
|
|
93
|
+
try {
|
|
94
|
+
entry.unsub = await conn.subscribeMessage((msg) => write(entityId, type, Array.isArray(msg?.forecast) ? msg.forecast : [], null), {
|
|
95
|
+
type: "weather/subscribe_forecast",
|
|
96
|
+
entity_id: entityId,
|
|
97
|
+
forecast_type: type
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
active.delete(key);
|
|
101
|
+
write(entityId, type, null, err instanceof Error ? err : new Error(String(err)));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
3
105
|
//#region src/solid/hooks.ts
|
|
4
106
|
/**
|
|
5
107
|
* SolidJS Hooks for Sync-Layer
|
|
@@ -228,10 +330,16 @@ function useCalendarEvents(entityId, options) {
|
|
|
228
330
|
return createMemo(() => state.calendars[getId()]);
|
|
229
331
|
}
|
|
230
332
|
/**
|
|
231
|
-
* Get reactive weather forecast data.
|
|
333
|
+
* Get reactive weather forecast data. With `types`, the forecast stays live
|
|
334
|
+
* while the caller is mounted: Home Assistant pushes each new one, no polling.
|
|
232
335
|
*/
|
|
233
|
-
function useForecast(entityId) {
|
|
336
|
+
function useForecast(entityId, types) {
|
|
234
337
|
const getId = typeof entityId === "function" ? entityId : () => entityId;
|
|
338
|
+
createEffect(() => {
|
|
339
|
+
const id = getId();
|
|
340
|
+
if (!id || !types) return;
|
|
341
|
+
for (const type of types) onCleanup(trackForecast(id, type));
|
|
342
|
+
});
|
|
235
343
|
return createMemo(() => state.forecasts[getId()]);
|
|
236
344
|
}
|
|
237
345
|
/**
|