@digital-alchemy/hass 24.9.4 → 24.10.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 +1 -1
- package/dist/extensions/call-proxy.extension.d.ts +1 -1
- package/dist/extensions/call-proxy.extension.js +4 -1
- package/dist/extensions/call-proxy.extension.js.map +1 -1
- package/dist/extensions/entity.extension.js +3 -0
- package/dist/extensions/entity.extension.js.map +1 -1
- package/dist/extensions/websocket-api.extension.js +5 -11
- package/dist/extensions/websocket-api.extension.js.map +1 -1
- package/dist/helpers/notify.helper.d.ts +2 -2
- package/package.json +16 -14
- package/scripts/mock-assistant.sh +5 -0
- package/scripts/run-e2e.sh +7 -0
- package/scripts/test.sh +2 -0
- package/src/dynamic.ts +4254 -0
- package/src/extensions/area.extension.ts +118 -0
- package/src/extensions/backup.extension.ts +63 -0
- package/src/extensions/call-proxy.extension.ts +122 -0
- package/src/extensions/config.extension.ts +119 -0
- package/src/extensions/conversation.extension.ts +46 -0
- package/src/extensions/device.extension.ts +56 -0
- package/src/extensions/entity.extension.ts +347 -0
- package/src/extensions/events.extension.ts +25 -0
- package/src/extensions/fetch-api.extension.ts +269 -0
- package/src/extensions/floor.extension.ts +76 -0
- package/src/extensions/id-by.extension.ts +157 -0
- package/src/extensions/index.ts +16 -0
- package/src/extensions/internal.extension.ts +145 -0
- package/src/extensions/label.extension.ts +83 -0
- package/src/extensions/reference.extension.ts +330 -0
- package/src/extensions/registry.extension.ts +44 -0
- package/src/extensions/websocket-api.extension.ts +551 -0
- package/src/extensions/zone.extension.ts +69 -0
- package/src/hass.module.ts +217 -0
- package/src/helpers/backup.helper.ts +11 -0
- package/src/helpers/constants.helper.ts +30 -0
- package/src/helpers/device.helper.ts +25 -0
- package/src/helpers/entity-state.helper.ts +171 -0
- package/src/helpers/features.helper.ts +580 -0
- package/src/helpers/fetch/calendar.ts +54 -0
- package/src/helpers/fetch/configuration.ts +75 -0
- package/src/helpers/fetch/index.ts +5 -0
- package/src/helpers/fetch/server-log.ts +28 -0
- package/src/helpers/fetch/service-list.ts +64 -0
- package/src/helpers/fetch/weather-forecasts.ts +86 -0
- package/src/helpers/fetch.helper.ts +328 -0
- package/src/helpers/id-by.helper.ts +53 -0
- package/src/helpers/index.ts +13 -0
- package/src/helpers/interfaces.helper.ts +340 -0
- package/src/helpers/manifest.helper.ts +0 -0
- package/src/helpers/notify.helper.ts +302 -0
- package/src/helpers/registry.ts +281 -0
- package/src/helpers/utility.helper.ts +147 -0
- package/src/helpers/websocket.helper.ts +117 -0
- package/src/index.ts +5 -0
- package/src/mock_assistant/extensions/area.extension.ts +62 -0
- package/src/mock_assistant/extensions/config.extension.ts +33 -0
- package/src/mock_assistant/extensions/device.extension.ts +44 -0
- package/src/mock_assistant/extensions/entity-registry.extension.ts +41 -0
- package/src/mock_assistant/extensions/entity.extension.ts +114 -0
- package/src/mock_assistant/extensions/events.extension.ts +37 -0
- package/src/mock_assistant/extensions/fetch.extension.ts +3 -0
- package/src/mock_assistant/extensions/fixtures.extension.ts +79 -0
- package/src/mock_assistant/extensions/floor.extension.ts +64 -0
- package/src/mock_assistant/extensions/index.ts +12 -0
- package/src/mock_assistant/extensions/label.extension.ts +64 -0
- package/src/mock_assistant/extensions/services.extension.ts +25 -0
- package/src/mock_assistant/extensions/websocket-api.extension.ts +84 -0
- package/src/mock_assistant/extensions/zone.extension.ts +65 -0
- package/src/mock_assistant/helpers/fixtures.ts +22 -0
- package/src/mock_assistant/helpers/index.ts +1 -0
- package/src/mock_assistant/index.ts +3 -0
- package/src/mock_assistant/main.ts +46 -0
- package/src/mock_assistant/mock-assistant.module.ts +90 -0
- package/src/quickboot.module.ts +23 -0
- package/src/testing/area.spec.ts +189 -0
- package/src/testing/backup.spec.ts +157 -0
- package/src/testing/config.spec.ts +188 -0
- package/src/testing/device.spec.ts +89 -0
- package/src/testing/entity.spec.ts +171 -0
- package/src/testing/events.spec.ts +78 -0
- package/src/testing/fetch-api.spec.ts +410 -0
- package/src/testing/fixtures.spec.ts +158 -0
- package/src/testing/floor.spec.ts +186 -0
- package/src/testing/id-by.spec.ts +140 -0
- package/src/testing/label.spec.ts +186 -0
- package/src/testing/ref-by.spec.ts +300 -0
- package/src/testing/websocket.spec.ts +63 -0
- package/src/testing/workflow.spec.ts +195 -0
- package/src/testing/zone.spec.ts +109 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// {
|
|
2
|
+
// "0": {
|
|
3
|
+
// "name": "homeassistant.util.yaml.loader",
|
|
4
|
+
// "message": [
|
|
5
|
+
// "mapping values are not allowed here\n in \"/config/configuration.yaml\", line 71, column 8"
|
|
6
|
+
// ],
|
|
7
|
+
// "level": "ERROR",
|
|
8
|
+
// "source": [
|
|
9
|
+
// "util/yaml/loader.py",
|
|
10
|
+
// 127
|
|
11
|
+
// ],
|
|
12
|
+
// "timestamp": 1638118416.470104,
|
|
13
|
+
// "exception": "",
|
|
14
|
+
// "count": 2,
|
|
15
|
+
// "first_occurred": 1638118343.795454
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
export interface HomeAssistantServerLogItem {
|
|
20
|
+
count: number;
|
|
21
|
+
exception: string;
|
|
22
|
+
first_occurred: number;
|
|
23
|
+
level: "ERROR" | "WARNING";
|
|
24
|
+
message: string[];
|
|
25
|
+
name: string;
|
|
26
|
+
source: [string, number];
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { TPlatformId } from "../../dynamic";
|
|
2
|
+
import { ALL_DOMAINS } from "../utility.helper";
|
|
3
|
+
|
|
4
|
+
export interface ServiceListSelectorTarget {
|
|
5
|
+
domain?: ALL_DOMAINS;
|
|
6
|
+
integration?: TPlatformId;
|
|
7
|
+
multiple?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ServiceListSelector {
|
|
10
|
+
boolean?: null;
|
|
11
|
+
entity?: ServiceListSelectorTarget;
|
|
12
|
+
number?: {
|
|
13
|
+
max: number;
|
|
14
|
+
min: number;
|
|
15
|
+
mode?: string;
|
|
16
|
+
step?: number;
|
|
17
|
+
unit_of_measurement: string;
|
|
18
|
+
};
|
|
19
|
+
object?: null;
|
|
20
|
+
select?: {
|
|
21
|
+
options: Record<"label" | "value", string>[] | string[];
|
|
22
|
+
};
|
|
23
|
+
text?: null;
|
|
24
|
+
time?: null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ServiceListFieldDescription {
|
|
28
|
+
advanced?: boolean;
|
|
29
|
+
default?: unknown;
|
|
30
|
+
description?: string;
|
|
31
|
+
example?: string | number;
|
|
32
|
+
name?: string;
|
|
33
|
+
required?: boolean;
|
|
34
|
+
selector?: ServiceListSelector;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type ServiceListEntityTarget = {
|
|
38
|
+
domain?: ALL_DOMAINS[];
|
|
39
|
+
integration?: TPlatformId;
|
|
40
|
+
supported_features?: number[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface ServiceListServiceTarget {
|
|
44
|
+
device?: { integration?: string };
|
|
45
|
+
entity?: ServiceListEntityTarget[];
|
|
46
|
+
integration?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ServiceListField {
|
|
50
|
+
description?: string;
|
|
51
|
+
fields: Record<string, ServiceListFieldDescription>;
|
|
52
|
+
name?: string;
|
|
53
|
+
target?: ServiceListServiceTarget;
|
|
54
|
+
response?: ResponseOptional;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ResponseOptional {
|
|
58
|
+
optional?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface HassServiceDTO {
|
|
62
|
+
domain: ALL_DOMAINS;
|
|
63
|
+
services: Record<string /* via .name */, ServiceListField>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Comes from https://www.home-assistant.io/integrations/weather/#action-weatherget_forecasts
|
|
2
|
+
|
|
3
|
+
export type WeatherCondition =
|
|
4
|
+
| "clear-night"
|
|
5
|
+
| "cloudy"
|
|
6
|
+
| "fog"
|
|
7
|
+
| "hail"
|
|
8
|
+
| "lightning"
|
|
9
|
+
| "lightning-rainy"
|
|
10
|
+
| "partlycloudy"
|
|
11
|
+
| "pouring"
|
|
12
|
+
| "rainy"
|
|
13
|
+
| "snowy"
|
|
14
|
+
| "snowy-rainy"
|
|
15
|
+
| "sunny"
|
|
16
|
+
| "windy"
|
|
17
|
+
| "windy-variant"
|
|
18
|
+
| "exceptional";
|
|
19
|
+
|
|
20
|
+
export interface WeatherGetForecasts {
|
|
21
|
+
/**
|
|
22
|
+
* Time of the forecasted conditions.
|
|
23
|
+
* Format is YYYY-MM-DDTHH:mm:ss+zz:zz
|
|
24
|
+
*/
|
|
25
|
+
datetime: string;
|
|
26
|
+
/**
|
|
27
|
+
* Only set for `twice_daily` forecasts
|
|
28
|
+
*/
|
|
29
|
+
is_daytime: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The apparent (feels-like) temperature in the unit indicated by the `temperature_unite` state attribute.
|
|
32
|
+
*/
|
|
33
|
+
apparent_temperature: number;
|
|
34
|
+
/**
|
|
35
|
+
* The cloud coverage in %.
|
|
36
|
+
*/
|
|
37
|
+
cloud_coverage: number;
|
|
38
|
+
/**
|
|
39
|
+
* The weather condition
|
|
40
|
+
*/
|
|
41
|
+
condition: WeatherCondition;
|
|
42
|
+
/**
|
|
43
|
+
* The dew point temperature in the unit indicated by the `temperature_unite` state attribute.
|
|
44
|
+
*/
|
|
45
|
+
dew_point: number;
|
|
46
|
+
/**
|
|
47
|
+
* The relative humidity in %.
|
|
48
|
+
*/
|
|
49
|
+
humidity: number;
|
|
50
|
+
/**
|
|
51
|
+
* The probability of precipitation in %;
|
|
52
|
+
*/
|
|
53
|
+
precipitation_probability: number;
|
|
54
|
+
/**
|
|
55
|
+
* The precipitation amount in the unit indicated by the `precipitation_unit` state attribute.
|
|
56
|
+
*/
|
|
57
|
+
precipitation: number;
|
|
58
|
+
/**
|
|
59
|
+
* The air pressure in the unit indicated by the `pressure_unit` state attribute.
|
|
60
|
+
*/
|
|
61
|
+
pressure: number;
|
|
62
|
+
/**
|
|
63
|
+
* The temperature in the unit indicated by the `temperature_unite` state attribute. If `templow` is also provided this is the higher temperature.
|
|
64
|
+
*/
|
|
65
|
+
temperature: number;
|
|
66
|
+
/**
|
|
67
|
+
* The lower temperature in the unit indicated by the `temperature_unite` state attribute.
|
|
68
|
+
*/
|
|
69
|
+
templow: number;
|
|
70
|
+
/**
|
|
71
|
+
* The UV index.
|
|
72
|
+
*/
|
|
73
|
+
uv_index: number;
|
|
74
|
+
/**
|
|
75
|
+
* The wind bearing in azimuth angle (degrees) or 1-3 letter cardinal direction
|
|
76
|
+
*/
|
|
77
|
+
wind_bearing: number | string;
|
|
78
|
+
/**
|
|
79
|
+
* The wind gust speed in the unit indicated by the `wind_speed_unit` state attribute.
|
|
80
|
+
*/
|
|
81
|
+
wind_gust_speed: number;
|
|
82
|
+
/**
|
|
83
|
+
* The wind speed in the unit indicated by the `wind_speed_unit` state attribute.
|
|
84
|
+
*/
|
|
85
|
+
wind_speed: number;
|
|
86
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { is, TContext } from "@digital-alchemy/core";
|
|
2
|
+
import { MergeExclusive } from "type-fest";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Defines the types of parameters that can be used in fetch requests.
|
|
6
|
+
*/
|
|
7
|
+
export type FetchParameterTypes = string | boolean | Date | number | Array<string | Date | number>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enumerates HTTP methods used in fetch requests.
|
|
11
|
+
*/
|
|
12
|
+
export enum HTTP_METHODS {
|
|
13
|
+
get = "get",
|
|
14
|
+
delete = "delete",
|
|
15
|
+
put = "put",
|
|
16
|
+
patch = "patch",
|
|
17
|
+
post = "post",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type NoBodyMethods = "get" | "delete";
|
|
21
|
+
type BodyMethods = "put" | "patch" | "post";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents a fetch request with additional properties and body content.
|
|
25
|
+
*/
|
|
26
|
+
export type FetchWith<
|
|
27
|
+
EXTRA extends Record<never, string> = Record<never, string>,
|
|
28
|
+
BODY extends TFetchBody = undefined,
|
|
29
|
+
> = Partial<FetchArguments<BODY>> & EXTRA;
|
|
30
|
+
|
|
31
|
+
export type FetchProcessTypes = boolean | "text" | "json" | "raw" | undefined;
|
|
32
|
+
|
|
33
|
+
type BaseFetchArguments = {
|
|
34
|
+
/**
|
|
35
|
+
* Headers to append
|
|
36
|
+
*/
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Query params to send
|
|
40
|
+
*/
|
|
41
|
+
params?: Record<string, FetchParameterTypes>;
|
|
42
|
+
/**
|
|
43
|
+
* Built in post-processing
|
|
44
|
+
*
|
|
45
|
+
* - true / "json" = attempt to decode as json
|
|
46
|
+
* - false / "raw" = return the node-fetch response object without processing
|
|
47
|
+
* - "text" = return result as text, no additional processing
|
|
48
|
+
*
|
|
49
|
+
* ? boolean values are deprecated
|
|
50
|
+
*/
|
|
51
|
+
process?: FetchProcessTypes;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type MaybeHttpError = {
|
|
55
|
+
error: string;
|
|
56
|
+
message: string;
|
|
57
|
+
statusCode: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type BaseFetchUrl = {
|
|
61
|
+
/**
|
|
62
|
+
* URL to send request to
|
|
63
|
+
*/
|
|
64
|
+
url: string;
|
|
65
|
+
} & MergeExclusive<
|
|
66
|
+
{
|
|
67
|
+
/**
|
|
68
|
+
* Frequently filled in by wrapper services
|
|
69
|
+
*/
|
|
70
|
+
baseUrl?: string;
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
/**
|
|
74
|
+
* URL is the full path (includes http://...)
|
|
75
|
+
*
|
|
76
|
+
* Ignores baseUrl if set
|
|
77
|
+
*/
|
|
78
|
+
rawUrl?: boolean;
|
|
79
|
+
}
|
|
80
|
+
>;
|
|
81
|
+
|
|
82
|
+
type BaseFetchBody<BODY extends TFetchBody = undefined> = MergeExclusive<
|
|
83
|
+
{
|
|
84
|
+
/**
|
|
85
|
+
* POSTDATA
|
|
86
|
+
*/
|
|
87
|
+
body?: BODY;
|
|
88
|
+
/**
|
|
89
|
+
* HTTP method
|
|
90
|
+
*/
|
|
91
|
+
method: BodyMethods;
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
/**
|
|
95
|
+
* HTTP method
|
|
96
|
+
*/
|
|
97
|
+
method?: NoBodyMethods;
|
|
98
|
+
}
|
|
99
|
+
>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Defines the structure and types for arguments passed to fetch requests.
|
|
103
|
+
*/
|
|
104
|
+
export type FetchArguments<BODY extends TFetchBody = undefined> = BaseFetchUrl &
|
|
105
|
+
BaseFetchArguments &
|
|
106
|
+
BaseFetchBody<BODY>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Represents a subset of FetchArguments for specific use cases.
|
|
110
|
+
*/
|
|
111
|
+
export type FilteredFetchArguments<BODY extends TFetchBody = undefined> = BaseFetchBody<BODY> &
|
|
112
|
+
Pick<BaseFetchUrl, "url"> &
|
|
113
|
+
Pick<BaseFetchArguments, "process" | "params">;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Same thing as FetchWith, but the function doesn't need any args
|
|
117
|
+
*
|
|
118
|
+
* This is a work around, for some reason the default value approach isn't work as I had hoped
|
|
119
|
+
*/
|
|
120
|
+
export type BaseFetch = Partial<FetchArguments>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Defines the types of values that can be used in filter operations.
|
|
124
|
+
*/
|
|
125
|
+
export type FilterValueType = string | boolean | number | Date | RegExp | Record<string, string>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Enumerates the types of operations available for data filtering.
|
|
129
|
+
*/
|
|
130
|
+
export enum FILTER_OPERATIONS {
|
|
131
|
+
// "elemMatch" functionality in mongo
|
|
132
|
+
// eslint-disable-next-line unicorn/prevent-abbreviations
|
|
133
|
+
elem = "elem",
|
|
134
|
+
regex = "regex",
|
|
135
|
+
in = "in",
|
|
136
|
+
nin = "nin",
|
|
137
|
+
lt = "lt",
|
|
138
|
+
lte = "lte",
|
|
139
|
+
gt = "gt",
|
|
140
|
+
gte = "gte",
|
|
141
|
+
exists = "exists",
|
|
142
|
+
empty = "empty",
|
|
143
|
+
ne = "ne",
|
|
144
|
+
eq = "eq",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ComparisonDTO {
|
|
148
|
+
operation?: FILTER_OPERATIONS | `${FILTER_OPERATIONS}`;
|
|
149
|
+
value?: FilterValueType | FilterValueType[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface Filter<FIELDS = string> extends ComparisonDTO {
|
|
153
|
+
empty?: boolean;
|
|
154
|
+
exists?: boolean;
|
|
155
|
+
field?: FIELDS;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ResultControl {
|
|
159
|
+
filters?: Set<Filter>;
|
|
160
|
+
limit?: number;
|
|
161
|
+
select?: string[];
|
|
162
|
+
skip?: number;
|
|
163
|
+
sort?: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function controlToQuery(value: Readonly<ResultControl>): Record<string, string> {
|
|
167
|
+
const out = new Map<string, string>();
|
|
168
|
+
if (value?.limit) {
|
|
169
|
+
out.set("limit", value.limit.toString());
|
|
170
|
+
}
|
|
171
|
+
if (value?.skip) {
|
|
172
|
+
out.set("skip", value.skip.toString());
|
|
173
|
+
}
|
|
174
|
+
if (value?.sort) {
|
|
175
|
+
out.set("sort", value.sort.join(","));
|
|
176
|
+
}
|
|
177
|
+
if (value?.select) {
|
|
178
|
+
out.set("select", value.select.join(","));
|
|
179
|
+
}
|
|
180
|
+
value?.filters?.forEach(f => {
|
|
181
|
+
let field = f.field;
|
|
182
|
+
if (f.operation && f.operation !== FILTER_OPERATIONS.eq) {
|
|
183
|
+
field = `${field}__${f.operation}`;
|
|
184
|
+
}
|
|
185
|
+
let value = f.value;
|
|
186
|
+
if (is.array(value)) {
|
|
187
|
+
value = value.join(",");
|
|
188
|
+
}
|
|
189
|
+
if (value instanceof Date) {
|
|
190
|
+
value = value.toISOString();
|
|
191
|
+
}
|
|
192
|
+
if (value === null) {
|
|
193
|
+
value = "null";
|
|
194
|
+
}
|
|
195
|
+
if (field) {
|
|
196
|
+
out.set(field, String(value) ?? "");
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return Object.fromEntries(out.entries());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildFilter(key: string, value: FilterValueType | FilterValueType[]): Filter {
|
|
203
|
+
const [name, operation] = key.split("__") as [string, FILTER_OPERATIONS];
|
|
204
|
+
switch (operation) {
|
|
205
|
+
case "in":
|
|
206
|
+
case "nin":
|
|
207
|
+
if (!is.array(value)) {
|
|
208
|
+
value = is.string(value) ? value.split(",") : [value];
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
field: name,
|
|
212
|
+
operation,
|
|
213
|
+
value: value,
|
|
214
|
+
};
|
|
215
|
+
case "elem":
|
|
216
|
+
return {
|
|
217
|
+
field: name,
|
|
218
|
+
operation,
|
|
219
|
+
value: is.string(value) ? JSON.parse(value) : value,
|
|
220
|
+
};
|
|
221
|
+
default:
|
|
222
|
+
return {
|
|
223
|
+
field: name,
|
|
224
|
+
operation,
|
|
225
|
+
value,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function queryToControl(value: Readonly<Record<string, string>>): ResultControl {
|
|
231
|
+
const filters = new Set<Filter<string>>();
|
|
232
|
+
const out: ResultControl = { filters };
|
|
233
|
+
const parameters = new Map<string, string>(Object.entries(value));
|
|
234
|
+
parameters.forEach((value, key) => {
|
|
235
|
+
const [name, operation] = key.split("__") as [string, FILTER_OPERATIONS];
|
|
236
|
+
switch (key) {
|
|
237
|
+
case "select":
|
|
238
|
+
out.select = value.split(",");
|
|
239
|
+
return;
|
|
240
|
+
case "sort":
|
|
241
|
+
out.sort = value.split(",");
|
|
242
|
+
return;
|
|
243
|
+
case "limit":
|
|
244
|
+
out.limit = Number(value);
|
|
245
|
+
return;
|
|
246
|
+
case "skip":
|
|
247
|
+
out.skip = Number(value);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
switch (operation) {
|
|
251
|
+
case "in":
|
|
252
|
+
case "nin":
|
|
253
|
+
filters.add({
|
|
254
|
+
field: name,
|
|
255
|
+
operation,
|
|
256
|
+
value: value.split(","),
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
case "elem":
|
|
260
|
+
filters.add({
|
|
261
|
+
field: name,
|
|
262
|
+
operation,
|
|
263
|
+
value: JSON.parse(value),
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
default:
|
|
267
|
+
filters.add({
|
|
268
|
+
field: name,
|
|
269
|
+
operation,
|
|
270
|
+
value,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Properties that alter the way that fetcher works.
|
|
279
|
+
*/
|
|
280
|
+
export type FetcherOptions = {
|
|
281
|
+
/**
|
|
282
|
+
* typically domain names with scheme, added to the front of urls if the individual request doesn't override
|
|
283
|
+
*/
|
|
284
|
+
baseUrl?: string;
|
|
285
|
+
/**
|
|
286
|
+
* merged into every request
|
|
287
|
+
*/
|
|
288
|
+
headers?: Record<string, string>;
|
|
289
|
+
/**
|
|
290
|
+
* Alter the context attached to the log statements emitted from the fetcher
|
|
291
|
+
*/
|
|
292
|
+
context?: TContext;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export type DownloadOptions<BODY extends TFetchBody = undefined> = Partial<FetchArguments<BODY>> & {
|
|
296
|
+
destination: string;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export function fetchCast(item: FetchParameterTypes): string {
|
|
300
|
+
if (is.array(item)) {
|
|
301
|
+
return item.map(i => fetchCast(i)).join(",");
|
|
302
|
+
}
|
|
303
|
+
if (item instanceof Date) {
|
|
304
|
+
return item.toISOString();
|
|
305
|
+
}
|
|
306
|
+
if (is.number(item)) {
|
|
307
|
+
return item.toString();
|
|
308
|
+
}
|
|
309
|
+
if (is.boolean(item)) {
|
|
310
|
+
return item ? "true" : "false";
|
|
311
|
+
}
|
|
312
|
+
return item;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export type TFetchBody = object | undefined;
|
|
316
|
+
|
|
317
|
+
export function buildFilterString(
|
|
318
|
+
fetchWith: FetchWith<{
|
|
319
|
+
filters?: Readonly<ResultControl>;
|
|
320
|
+
params?: Record<string, FetchParameterTypes>;
|
|
321
|
+
}>,
|
|
322
|
+
): string {
|
|
323
|
+
return new URLSearchParams({
|
|
324
|
+
...Object.fromEntries(
|
|
325
|
+
Object.entries(fetchWith.params ?? {}).map(([label, value]) => [label, fetchCast(value)]),
|
|
326
|
+
),
|
|
327
|
+
}).toString();
|
|
328
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TAreaId,
|
|
3
|
+
TDeviceId,
|
|
4
|
+
TFloorId,
|
|
5
|
+
TLabelId,
|
|
6
|
+
TPlatformId,
|
|
7
|
+
TRawEntityIds,
|
|
8
|
+
TUniqueId,
|
|
9
|
+
TUniqueIDMapping,
|
|
10
|
+
} from "../dynamic";
|
|
11
|
+
import {
|
|
12
|
+
ALL_DOMAINS,
|
|
13
|
+
ANY_ENTITY,
|
|
14
|
+
PICK_ENTITY,
|
|
15
|
+
PICK_FROM_AREA,
|
|
16
|
+
PICK_FROM_DEVICE,
|
|
17
|
+
PICK_FROM_FLOOR,
|
|
18
|
+
PICK_FROM_LABEL,
|
|
19
|
+
PICK_FROM_PLATFORM,
|
|
20
|
+
} from "./utility.helper";
|
|
21
|
+
|
|
22
|
+
export type IDByInterface = {
|
|
23
|
+
area: <AREA extends TAreaId, DOMAIN extends ALL_DOMAINS>(
|
|
24
|
+
area: AREA,
|
|
25
|
+
...domains: DOMAIN[]
|
|
26
|
+
) => PICK_FROM_AREA<AREA, DOMAIN>[];
|
|
27
|
+
device: <DEVICE extends TDeviceId, DOMAIN extends ALL_DOMAINS>(
|
|
28
|
+
device: DEVICE,
|
|
29
|
+
...domains: DOMAIN[]
|
|
30
|
+
) => PICK_FROM_DEVICE<DEVICE, DOMAIN>[];
|
|
31
|
+
domain: <DOMAIN extends ALL_DOMAINS>(domain: DOMAIN) => PICK_ENTITY<DOMAIN>[];
|
|
32
|
+
floor: <FLOOR extends TFloorId, DOMAIN extends ALL_DOMAINS>(
|
|
33
|
+
floor: FLOOR,
|
|
34
|
+
...domains: DOMAIN[]
|
|
35
|
+
) => PICK_FROM_FLOOR<FLOOR, DOMAIN>[];
|
|
36
|
+
label: <LABEL extends TLabelId, DOMAIN extends ALL_DOMAINS>(
|
|
37
|
+
label: LABEL,
|
|
38
|
+
...domains: DOMAIN[]
|
|
39
|
+
) => PICK_FROM_LABEL<LABEL, DOMAIN>[];
|
|
40
|
+
platform: <PLATFORM extends TPlatformId, DOMAIN extends ALL_DOMAINS>(
|
|
41
|
+
platform: PLATFORM,
|
|
42
|
+
...domains: DOMAIN[]
|
|
43
|
+
) => PICK_FROM_PLATFORM<PLATFORM, DOMAIN>[];
|
|
44
|
+
unique_id: <
|
|
45
|
+
UNIQUE_ID extends TUniqueId,
|
|
46
|
+
ENTITY_ID extends Extract<TUniqueIDMapping[UNIQUE_ID], ANY_ENTITY> = Extract<
|
|
47
|
+
TUniqueIDMapping[UNIQUE_ID],
|
|
48
|
+
TRawEntityIds
|
|
49
|
+
>,
|
|
50
|
+
>(
|
|
51
|
+
unique_id: UNIQUE_ID,
|
|
52
|
+
) => ENTITY_ID;
|
|
53
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./backup.helper";
|
|
2
|
+
export * from "./constants.helper";
|
|
3
|
+
export * from "./device.helper";
|
|
4
|
+
export * from "./entity-state.helper";
|
|
5
|
+
export * from "./features.helper";
|
|
6
|
+
export * from "./fetch";
|
|
7
|
+
export * from "./fetch.helper";
|
|
8
|
+
export * from "./id-by.helper";
|
|
9
|
+
export * from "./interfaces.helper";
|
|
10
|
+
export * from "./notify.helper";
|
|
11
|
+
export * from "./registry";
|
|
12
|
+
export * from "./utility.helper";
|
|
13
|
+
export * from "./websocket.helper";
|