@digital-alchemy/hass 25.5.1 → 25.5.2

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 (82) hide show
  1. package/dist/hass.module.d.mts +13 -1
  2. package/dist/hass.module.mjs +16 -1
  3. package/dist/hass.module.mjs.map +1 -1
  4. package/dist/helpers/fetch.mjs +0 -1
  5. package/dist/helpers/fetch.mjs.map +1 -1
  6. package/dist/helpers/utility.d.mts +1 -0
  7. package/dist/helpers/utility.mjs +4 -0
  8. package/dist/helpers/utility.mjs.map +1 -1
  9. package/dist/mock_assistant/mock-assistant.module.d.mts +12 -0
  10. package/dist/services/area.service.mjs +9 -1
  11. package/dist/services/area.service.mjs.map +1 -1
  12. package/dist/services/call-proxy.service.mjs +10 -2
  13. package/dist/services/call-proxy.service.mjs.map +1 -1
  14. package/dist/services/config.service.mjs +15 -13
  15. package/dist/services/config.service.mjs.map +1 -1
  16. package/dist/services/device.service.mjs +3 -1
  17. package/dist/services/device.service.mjs.map +1 -1
  18. package/dist/services/diagnostics.service.d.mts +26 -0
  19. package/dist/services/diagnostics.service.mjs +41 -0
  20. package/dist/services/diagnostics.service.mjs.map +1 -0
  21. package/dist/services/entity.service.mjs +12 -1
  22. package/dist/services/entity.service.mjs.map +1 -1
  23. package/dist/services/fetch-api.service.mjs +8 -2
  24. package/dist/services/fetch-api.service.mjs.map +1 -1
  25. package/dist/services/floor.service.mjs +3 -1
  26. package/dist/services/floor.service.mjs.map +1 -1
  27. package/dist/services/id-by.service.mjs +2 -1
  28. package/dist/services/id-by.service.mjs.map +1 -1
  29. package/dist/services/index.d.mts +1 -0
  30. package/dist/services/index.mjs +1 -0
  31. package/dist/services/index.mjs.map +1 -1
  32. package/dist/services/label.service.mjs +3 -1
  33. package/dist/services/label.service.mjs.map +1 -1
  34. package/dist/services/reference.service.mjs +15 -4
  35. package/dist/services/reference.service.mjs.map +1 -1
  36. package/dist/services/registry.service.mjs +8 -8
  37. package/dist/services/registry.service.mjs.map +1 -1
  38. package/dist/services/websocket-api.service.mjs +10 -2
  39. package/dist/services/websocket-api.service.mjs.map +1 -1
  40. package/dist/services/zone.service.mjs +3 -1
  41. package/dist/services/zone.service.mjs.map +1 -1
  42. package/dist/testing/area.spec.mjs +141 -132
  43. package/dist/testing/area.spec.mjs.map +1 -1
  44. package/dist/testing/device.spec.mjs +17 -0
  45. package/dist/testing/device.spec.mjs.map +1 -1
  46. package/dist/testing/entity.spec.mjs +167 -0
  47. package/dist/testing/entity.spec.mjs.map +1 -1
  48. package/dist/testing/fetch.spec.d.mts +1 -0
  49. package/dist/testing/fetch.spec.mjs +45 -0
  50. package/dist/testing/fetch.spec.mjs.map +1 -0
  51. package/dist/testing/floor.spec.mjs +17 -0
  52. package/dist/testing/floor.spec.mjs.map +1 -1
  53. package/dist/testing/label.spec.mjs +17 -0
  54. package/dist/testing/label.spec.mjs.map +1 -1
  55. package/dist/testing/zone.spec.mjs +24 -5
  56. package/dist/testing/zone.spec.mjs.map +1 -1
  57. package/package.json +17 -17
  58. package/src/hass.module.mts +18 -0
  59. package/src/helpers/fetch.mts +1 -1
  60. package/src/helpers/utility.mts +5 -0
  61. package/src/services/area.service.mts +9 -0
  62. package/src/services/call-proxy.service.mts +16 -9
  63. package/src/services/config.service.mts +21 -16
  64. package/src/services/device.service.mts +3 -0
  65. package/src/services/diagnostics.service.mts +45 -0
  66. package/src/services/entity.service.mts +12 -0
  67. package/src/services/fetch-api.service.mts +11 -2
  68. package/src/services/floor.service.mts +3 -0
  69. package/src/services/id-by.service.mts +2 -1
  70. package/src/services/index.mts +1 -0
  71. package/src/services/label.service.mts +3 -0
  72. package/src/services/reference.service.mts +15 -3
  73. package/src/services/registry.service.mts +8 -8
  74. package/src/services/websocket-api.service.mts +10 -2
  75. package/src/services/zone.service.mts +3 -0
  76. package/src/testing/area.spec.mts +153 -140
  77. package/src/testing/device.spec.mts +22 -0
  78. package/src/testing/entity.spec.mts +201 -0
  79. package/src/testing/fetch.spec.mts +54 -0
  80. package/src/testing/floor.spec.mts +22 -0
  81. package/src/testing/label.spec.mts +22 -0
  82. package/src/testing/zone.spec.mts +29 -5
@@ -19,6 +19,7 @@ import {
19
19
  EntityHistoryResult,
20
20
  EntityRegistryItem,
21
21
  HassEntityManager,
22
+ perf,
22
23
  TMasterState,
23
24
  } from "../index.mts";
24
25
  import { ALL_DOMAINS, ANY_ENTITY, PICK_ENTITY } from "../user.mts";
@@ -44,6 +45,7 @@ export function EntityManager({
44
45
  const PREVIOUS_STATE = new Map<ANY_ENTITY, ENTITY_STATE<ANY_ENTITY>>();
45
46
  let lastRefresh: Dayjs;
46
47
  function warnEarly(method: string) {
48
+ hass.diagnostics.entity?.warn_ready.publish({ method });
47
49
  if (!init) {
48
50
  lifecycle.onReady(() => {
49
51
  if (config.boilerplate.LOG_LEVEL !== "trace") {
@@ -76,12 +78,14 @@ export function EntityManager({
76
78
  payload: Omit<EntityHistoryDTO<ENTITES>, "type">,
77
79
  ) {
78
80
  logger.trace({ payload }, `looking up entity history`);
81
+ const ms = perf();
79
82
  const result = (await hass.socket.sendMessage({
80
83
  ...payload,
81
84
  end_time: dayjs(payload.end_time).toISOString(),
82
85
  start_time: dayjs(payload.start_time).toISOString(),
83
86
  type: "history/history_during_period",
84
87
  })) as Record<ANY_ENTITY, EntityHistoryItem[]>;
88
+ hass.diagnostics.entity?.history_lookup.publish({ ms: ms(), payload, result });
85
89
 
86
90
  const entities = Object.keys(result) as ANY_ENTITY[];
87
91
  return Object.fromEntries(
@@ -191,6 +195,7 @@ export function EntityManager({
191
195
  internal.utils.object.get(oldState, entity.entity_id),
192
196
  ),
193
197
  );
198
+ hass.diagnostics.entity?.refresh_entities.publish({ emitUpdates });
194
199
  });
195
200
  init = true;
196
201
  }
@@ -201,6 +206,7 @@ export function EntityManager({
201
206
  new_state: ENTITY_STATE<ENTITY>,
202
207
  old_state: ENTITY_STATE<ENTITY>,
203
208
  ) {
209
+ hass.diagnostics.entity?.entity_updated.publish({ entity_id, new_state, old_state });
204
210
  PREVIOUS_STATE.set(entity_id, old_state);
205
211
  if (new_state === null) {
206
212
  logger.warn(
@@ -209,8 +215,12 @@ export function EntityManager({
209
215
  entity_id,
210
216
  );
211
217
  internal.utils.object.del(MASTER_STATE, entity_id);
218
+ hass.diagnostics.entity?.entity_remove.publish({ entity_id });
212
219
  return;
213
220
  }
221
+ if (old_state === null) {
222
+ hass.diagnostics.entity?.entity_add.publish({ entity_id });
223
+ }
214
224
  internal.utils.object.set(MASTER_STATE, entity_id, new_state);
215
225
  if (!hass.socket.pauseMessages) {
216
226
  event.emit(entity_id, new_state, old_state);
@@ -293,10 +303,12 @@ export function EntityManager({
293
303
  context,
294
304
  event_type: "entity_registry_updated",
295
305
  async exec() {
306
+ const ms = perf();
296
307
  await debounce(ENTITY_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
297
308
  logger.debug("entity registry updated");
298
309
  hass.entity.registry.current = await hass.entity.registry.list();
299
310
  event.emit(ENTITY_REGISTRY_UPDATED);
311
+ hass.diagnostics.entity?.registry_updated.publish({ ms: ms() });
300
312
  },
301
313
  });
302
314
  hass.entity.registry.current = await hass.entity.registry.list();
@@ -13,6 +13,7 @@ import {
13
13
  HassConfig,
14
14
  HassServiceDTO,
15
15
  HomeAssistantServerLogItem,
16
+ perf,
16
17
  PICK_SERVICE,
17
18
  PICK_SERVICE_PARAMETERS,
18
19
  PostConfigPriorities,
@@ -253,6 +254,15 @@ export function FetchAPI({
253
254
  });
254
255
  }
255
256
 
257
+ async function fetch<T, BODY extends TFetchBody = undefined>(
258
+ options: Partial<FetchArguments<BODY>>,
259
+ ) {
260
+ const ms = perf();
261
+ const out = await fetcher.exec<T, BODY>(options);
262
+ hass.diagnostics.fetch?.fetch.publish({ ms: ms(), options, out });
263
+ return out;
264
+ }
265
+
256
266
  return {
257
267
  _fetcher: fetcher,
258
268
  calendarSearch,
@@ -260,8 +270,7 @@ export function FetchAPI({
260
270
  checkConfig,
261
271
  checkCredentials,
262
272
  download,
263
- fetch: async <T, BODY extends TFetchBody = undefined>(options: Partial<FetchArguments<BODY>>) =>
264
- await fetcher.exec<T, BODY>(options),
273
+ fetch,
265
274
  fetchEntityCustomizations,
266
275
  fetchEntityHistory,
267
276
  fireEvent,
@@ -6,6 +6,7 @@ import {
6
6
  FloorCreate,
7
7
  FloorDetails,
8
8
  HassFloorService,
9
+ perf,
9
10
  } from "../helpers/index.mts";
10
11
  import { TFloorId } from "../user.mts";
11
12
 
@@ -29,10 +30,12 @@ export function Floor({
29
30
  context,
30
31
  event_type: "floor_registry_updated",
31
32
  async exec() {
33
+ const ms = perf();
32
34
  await debounce(FLOOR_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
33
35
  hass.floor.current = await hass.floor.list();
34
36
  logger.debug(`floor registry updated`);
35
37
  event.emit(FLOOR_REGISTRY_UPDATED);
38
+ hass.diagnostics.floor?.registry_update.publish({ ms: ms() });
36
39
  },
37
40
  });
38
41
  });
@@ -78,8 +78,9 @@ export function IDByExtension({
78
78
  domain(i.entity_id) !== "update",
79
79
  );
80
80
  if (trimmed.length > SINGLE) {
81
+ const available_entity_ids = trimmed.map(i => i.entity_id);
81
82
  logger.warn(
82
- { available_entity_ids: trimmed.map(i => i.entity_id), unique_id },
83
+ { available_entity_ids, unique_id },
83
84
  `unique_id collision during lookup (chose first in list)`,
84
85
  );
85
86
  } else {
@@ -3,6 +3,7 @@ export * from "./backup.service.mts";
3
3
  export * from "./call-proxy.service.mts";
4
4
  export * from "./config.service.mts";
5
5
  export * from "./device.service.mts";
6
+ export * from "./diagnostics.service.mts";
6
7
  export * from "./entity.service.mts";
7
8
  export * from "./events.service.mts";
8
9
  export * from "./fetch-api.service.mts";
@@ -6,6 +6,7 @@ import {
6
6
  LABEL_REGISTRY_UPDATED,
7
7
  LabelDefinition,
8
8
  LabelOptions,
9
+ perf,
9
10
  } from "../helpers/index.mts";
10
11
  import { TLabelId } from "../user.mts";
11
12
 
@@ -29,10 +30,12 @@ export function Label({
29
30
  context,
30
31
  event_type: "label_registry_updated",
31
32
  async exec() {
33
+ const ms = perf();
32
34
  await debounce(LABEL_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
33
35
  hass.label.current = await hass.label.list();
34
36
  logger.debug(`label registry updated`);
35
37
  event.emit(LABEL_REGISTRY_UPDATED);
38
+ hass.diagnostics.label?.registry_update.publish({ ms: ms() });
36
39
  },
37
40
  });
38
41
  });
@@ -8,6 +8,7 @@ import {
8
8
  domain,
9
9
  ENTITY_STATE,
10
10
  HassReferenceService,
11
+ perf,
11
12
  RemoveCallback,
12
13
  } from "../helpers/index.mts";
13
14
  import {
@@ -164,10 +165,11 @@ export function ReferenceService({
164
165
  const listeners = new Set<() => void>();
165
166
 
166
167
  // just because you can't do generics properly....
167
- return new Proxy(thing, {
168
+ const proxy = new Proxy(thing, {
168
169
  // things that shouldn't be needed: this extract
169
170
  // eslint-disable-next-line sonarjs/function-return-type
170
171
  get: (_, property: Extract<keyof ByIdProxy<ENTITY_ID>, string>) => {
172
+ hass.diagnostics.reference?.get_property.publish({ entity_id, property });
171
173
  switch (property) {
172
174
  // #MARK: onUpdate
173
175
  case "onUpdate": {
@@ -304,7 +306,6 @@ export function ReferenceService({
304
306
  listeners.add(remove);
305
307
 
306
308
  const complete = (entity: ENTITY_STATE<ENTITY_ID>) => {
307
- // eslint-disable-next-line sonarjs/different-types-comparison
308
309
  if (entity.state !== state) {
309
310
  logger.trace(
310
311
  {
@@ -340,11 +341,20 @@ export function ReferenceService({
340
341
  // #MARK: service calls
341
342
  if (hass.configure.isService(entity_domain, property)) {
342
343
  return async function (data = {}) {
344
+ const ms = perf();
343
345
  // @ts-expect-error i don't care, this is fine
344
- return await hass.call[entity_domain][property]({
346
+ const result = await hass.call[entity_domain][property]({
345
347
  entity_id,
346
348
  ...data,
347
349
  });
350
+ hass.diagnostics.reference?.call_service.publish({
351
+ data,
352
+ entity_domain,
353
+ entity_id,
354
+ ms: ms(),
355
+ property,
356
+ });
357
+ return result;
348
358
  };
349
359
  }
350
360
  return proxyGetLogic(entity_id, property);
@@ -392,6 +402,8 @@ export function ReferenceService({
392
402
  return false;
393
403
  },
394
404
  });
405
+ hass.diagnostics.reference?.create_proxy.publish({ entity_id, proxy });
406
+ return proxy;
395
407
  }
396
408
 
397
409
  // #MARK: <return>
@@ -10,35 +10,35 @@ import {
10
10
  } from "../helpers/index.mts";
11
11
 
12
12
  export function Registry({ hass }: TServiceParams): HassRegistryService {
13
- async function ManifestList() {
13
+ async function manifestList() {
14
14
  return await hass.socket.sendMessage<ManifestItem[]>({
15
15
  type: "manifest/list",
16
16
  });
17
17
  }
18
18
 
19
- async function UpdateCore(options: UpdateCoreOptions) {
19
+ async function updateCore(options: UpdateCoreOptions) {
20
20
  await hass.socket.sendMessage<ZoneDetails[]>({
21
21
  ...options,
22
22
  type: "config/core/update",
23
23
  });
24
24
  }
25
25
 
26
- async function GetConfig() {
26
+ async function getConfig() {
27
27
  return await hass.socket.sendMessage<HassConfig>({
28
28
  type: "get_config",
29
29
  });
30
30
  }
31
31
 
32
- async function GetConfigEntries() {
32
+ async function getConfigEntries() {
33
33
  return await hass.socket.sendMessage<ConfigEntry[]>({
34
34
  type: "config_entries/get",
35
35
  });
36
36
  }
37
37
 
38
38
  return {
39
- getConfig: GetConfig,
40
- getConfigEntries: GetConfigEntries,
41
- manifestList: ManifestList,
42
- updateCore: UpdateCore,
39
+ getConfig,
40
+ getConfigEntries,
41
+ manifestList,
42
+ updateCore,
43
43
  };
44
44
  }
@@ -73,6 +73,7 @@ export function WebsocketAPI({
73
73
  // #MARK: setConnectionState
74
74
  function setConnectionState(state: ConnectionState) {
75
75
  hass.socket.connectionState = state;
76
+ hass.diagnostics.websocket?.set_connection_state.publish({ state });
76
77
  }
77
78
 
78
79
  // #MARK: handleUnknownConnectionState
@@ -87,6 +88,7 @@ export function WebsocketAPI({
87
88
  // send a ping message to force a pong
88
89
  logger.trace({ name }, `emitting ping`);
89
90
  lastPingAttempt = now;
91
+ hass.diagnostics.websocket?.send_ping.publish({});
90
92
 
91
93
  // emit a ping, do not wait for reply (inline)
92
94
  hass.socket.sendMessage({ type: "ping" }, false);
@@ -109,6 +111,7 @@ export function WebsocketAPI({
109
111
  logger.warn({ name }, "failed to receive expected {pong}");
110
112
  return;
111
113
  }
114
+ hass.diagnostics.websocket?.failed_ping.publish({});
112
115
  // 🪦 oof, get rid of the current connection and try again
113
116
  await hass.socket.teardown();
114
117
  logger.warn({ name }, "hass stopped replying {unknown} => {offline}");
@@ -215,11 +218,13 @@ export function WebsocketAPI({
215
218
 
216
219
  // #MARK: fireEvent
217
220
  async function fireEvent(event_type: string, event_data: object = {}) {
218
- return await hass.socket.sendMessage({
221
+ const result = await hass.socket.sendMessage({
219
222
  event_data,
220
223
  event_type,
221
224
  type: "fire_event",
222
225
  });
226
+ hass.diagnostics.websocket?.fire_event.publish({ event_data, event_type, result });
227
+ return result;
223
228
  }
224
229
 
225
230
  // #MARK: sendMessage
@@ -245,6 +250,7 @@ export function WebsocketAPI({
245
250
  if (data.type !== "auth") {
246
251
  data.id = id;
247
252
  }
253
+ hass.diagnostics.websocket?.send_message.publish({ data, waitForResponse });
248
254
  const json = JSON.stringify(data);
249
255
  const sentAt = new Date();
250
256
 
@@ -275,6 +281,7 @@ export function WebsocketAPI({
275
281
  //
276
282
  // discard the promise so whatever flow is depending on this can get garbage collected
277
283
  waitingCallback.delete(id);
284
+ hass.diagnostics.websocket?.missed_reply.publish({ data, sentAt });
278
285
  logger.warn(
279
286
  {
280
287
  message: data,
@@ -398,10 +405,11 @@ export function WebsocketAPI({
398
405
  */
399
406
  async function onMessage(message: SocketMessageDTO) {
400
407
  const id = Number(message.id);
408
+ setImmediate(() => hass.diagnostics.websocket?.message_received.publish({ message }));
401
409
  switch (message.type) {
402
410
  case "auth_required": {
403
411
  logger.trace({ name: onMessage }, `sending authentication`);
404
- hass.socket.sendMessage({ access_token: config.hass.TOKEN, type: "auth" }, false);
412
+ void hass.socket.sendMessage({ access_token: config.hass.TOKEN, type: "auth" }, false);
405
413
  return;
406
414
  }
407
415
 
@@ -4,6 +4,7 @@ import {
4
4
  EARLY_ON_READY,
5
5
  HassZoneService,
6
6
  ManifestItem,
7
+ perf,
7
8
  ZONE_REGISTRY_UPDATED,
8
9
  ZoneDetails,
9
10
  ZoneOptions,
@@ -29,10 +30,12 @@ export function Zone({
29
30
  context,
30
31
  event_type: "zone_registry_updated",
31
32
  async exec() {
33
+ const ms = perf();
32
34
  await debounce(ZONE_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
33
35
  hass.zone.current = await hass.zone.list();
34
36
  logger.debug(`zone registry updated`);
35
37
  event.emit(ZONE_REGISTRY_UPDATED);
38
+ hass.diagnostics.zone?.registry_update.publish({ ms: ms() });
36
39
  },
37
40
  });
38
41
  });