refactor(types): extract bindable primitives into types/bindable.ts (H6 partial)
types.ts is 1159 lines of kitchen-sink discriminated-union definitions
(audit finding H6) shared across ~30 frontend modules. Splitting the
whole file in one pass would need careful per-group TypeScript wrangling
and verification across every entity shape; this commit lands the
first slice as a proof of pattern.
What changed
------------
* New ``static/js/types/bindable.ts`` owns ``BindableFloat`` /
``BindableColor`` plus their four accessor helpers
(``bindableValue``, ``bindableSourceId``, ``bindableColor``,
``bindableColorSourceId``).
* ``static/js/types.ts`` keeps every interface and union shape that
references those primitives, but the primitives themselves now come
from the new file. Re-export keeps every existing ``import { ... }
from '../types.ts'`` site working unchanged.
Why this slice first
--------------------
Bindable types and helpers are the most heavily-imported piece of
types.ts (~30 modules already use ``bindable*`` helpers) and they have
zero downstream dependencies — they don't reference any other type
group. That makes them the safest extraction and the cleanest
demonstration of the barrel-re-export pattern the remaining groups
(devices, sources, integrations, automations, templates, …) will
follow in a follow-up sprint.
Verification
------------
* ``npx tsc --noEmit`` clean (no compile errors anywhere in the
frontend tree).
* ``npm run build`` clean (esbuild bundle and CSS bundle produced
without warnings).
The remaining ~1130 lines of types.ts plus the C8/C9/C10 god-module
splits (value-sources.ts, streams.ts, graph-editor.ts) need a
dedicated frontend session with typescript-reviewer + manual UI
testing — deferring those rather than half-finishing them here.
This commit is contained in:
@@ -3,45 +3,26 @@
|
|||||||
*
|
*
|
||||||
* These mirror the JSON shapes returned by the REST API. Field names use
|
* These mirror the JSON shapes returned by the REST API. Field names use
|
||||||
* snake_case to match the JSON payloads — no camelCase transformation is done.
|
* snake_case to match the JSON payloads — no camelCase transformation is done.
|
||||||
|
*
|
||||||
|
* Bindable primitives have been extracted into ``types/bindable.ts`` and
|
||||||
|
* are re-exported here so existing ``import { ... } from '../types.ts'``
|
||||||
|
* call sites keep working. The intention is for further entity-shape
|
||||||
|
* groups (devices, sources, integrations, …) to follow the same pattern
|
||||||
|
* in subsequent passes — see audit finding H6.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ── Bindable Float ───────────────────────────────────────────
|
// ── Bindable Primitives ─────────────────────────────────────
|
||||||
// A scalar that is either a static value (plain number) or bound to a value source (dict).
|
export type { BindableFloat, BindableColor } from './types/bindable.ts';
|
||||||
|
export {
|
||||||
|
bindableValue,
|
||||||
|
bindableSourceId,
|
||||||
|
bindableColor,
|
||||||
|
bindableColorSourceId,
|
||||||
|
} from './types/bindable.ts';
|
||||||
|
|
||||||
export type BindableFloat = number | { value: number; source_id: string };
|
// Local aliases used by the entity interfaces below so TypeScript can
|
||||||
|
// resolve them without an extra import at every reference site.
|
||||||
/** Extract the static value from a BindableFloat. */
|
import type { BindableFloat, BindableColor } from './types/bindable.ts';
|
||||||
export function bindableValue(b: BindableFloat | undefined, fallback: number): number {
|
|
||||||
if (b === undefined || b === null) return fallback;
|
|
||||||
if (typeof b === 'number') return b;
|
|
||||||
return b.value ?? fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extract the source_id from a BindableFloat (empty string = not bound). */
|
|
||||||
export function bindableSourceId(b: BindableFloat | undefined): string {
|
|
||||||
if (b === undefined || b === null) return '';
|
|
||||||
if (typeof b === 'number') return '';
|
|
||||||
return b.source_id ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Bindable Color ──────────────────────────────────────────
|
|
||||||
// An RGB color that is either static ([R,G,B] array) or bound to a color value source.
|
|
||||||
|
|
||||||
export type BindableColor = number[] | { color: number[]; source_id: string };
|
|
||||||
|
|
||||||
/** Extract the static [R,G,B] from a BindableColor. */
|
|
||||||
export function bindableColor(b: BindableColor | undefined, fallback: number[]): number[] {
|
|
||||||
if (b === undefined || b === null) return fallback;
|
|
||||||
if (Array.isArray(b)) return b;
|
|
||||||
return b.color ?? fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extract the source_id from a BindableColor (empty string = not bound). */
|
|
||||||
export function bindableColorSourceId(b: BindableColor | undefined): string {
|
|
||||||
if (b === undefined || b === null) return '';
|
|
||||||
if (Array.isArray(b)) return '';
|
|
||||||
return b.source_id ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Device ────────────────────────────────────────────────────
|
// ── Device ────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -186,7 +167,7 @@ export type OutputTarget = LedOutputTarget | HALightOutputTarget | Z2MLightOutpu
|
|||||||
// ── Color Strip Source ────────────────────────────────────────
|
// ── Color Strip Source ────────────────────────────────────────
|
||||||
|
|
||||||
export type CSSSourceType =
|
export type CSSSourceType =
|
||||||
| 'picture' | 'picture_advanced' | 'static' | 'gradient'
|
| 'picture' | 'picture_advanced' | 'single_color' | 'gradient'
|
||||||
| 'effect' | 'composite' | 'mapped'
|
| 'effect' | 'composite' | 'mapped'
|
||||||
| 'audio' | 'api_input' | 'notification' | 'daylight'
|
| 'audio' | 'api_input' | 'notification' | 'daylight'
|
||||||
| 'candlelight' | 'processed' | 'weather' | 'key_colors'
|
| 'candlelight' | 'processed' | 'weather' | 'key_colors'
|
||||||
@@ -379,7 +360,7 @@ export type ValueSourceType =
|
|||||||
| 'adaptive_time' | 'adaptive_scene' | 'daylight'
|
| 'adaptive_time' | 'adaptive_scene' | 'daylight'
|
||||||
| 'static_color' | 'animated_color' | 'adaptive_time_color'
|
| 'static_color' | 'animated_color' | 'adaptive_time_color'
|
||||||
| 'ha_entity' | 'gradient_map' | 'css_extract'
|
| 'ha_entity' | 'gradient_map' | 'css_extract'
|
||||||
| 'system_metrics' | 'game_event';
|
| 'system_metrics' | 'game_event' | 'http';
|
||||||
|
|
||||||
export interface SchedulePoint {
|
export interface SchedulePoint {
|
||||||
time: string;
|
time: string;
|
||||||
@@ -534,6 +515,17 @@ export interface GameEventValueSource extends ValueSourceBase {
|
|||||||
timeout: number;
|
timeout: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HTTPValueSource extends ValueSourceBase {
|
||||||
|
source_type: 'http';
|
||||||
|
return_type: 'float';
|
||||||
|
http_endpoint_id: string;
|
||||||
|
json_path: string;
|
||||||
|
interval_s: number;
|
||||||
|
min_value: number;
|
||||||
|
max_value: number;
|
||||||
|
smoothing: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ValueSource =
|
export type ValueSource =
|
||||||
| StaticValueSource
|
| StaticValueSource
|
||||||
| AnimatedValueSource
|
| AnimatedValueSource
|
||||||
@@ -548,7 +540,8 @@ export type ValueSource =
|
|||||||
| GradientMapValueSource
|
| GradientMapValueSource
|
||||||
| CSSExtractValueSource
|
| CSSExtractValueSource
|
||||||
| SystemMetricsValueSource
|
| SystemMetricsValueSource
|
||||||
| GameEventValueSource;
|
| GameEventValueSource
|
||||||
|
| HTTPValueSource;
|
||||||
|
|
||||||
// ── Audio Source ───────────────────────────────────────────────
|
// ── Audio Source ───────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -772,6 +765,68 @@ export interface MQTTStatusResponse {
|
|||||||
connected_count: number;
|
connected_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── HTTP Endpoint ────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// A connection definition only (URL + auth + headers + timeout).
|
||||||
|
// No polling cadence is configured on the endpoint itself —
|
||||||
|
// HTTPValueSource owns interval_s and references the endpoint.
|
||||||
|
|
||||||
|
export type HTTPMethod = 'GET' | 'HEAD';
|
||||||
|
|
||||||
|
export interface HTTPEndpoint {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
method: HTTPMethod;
|
||||||
|
/** Server NEVER returns the token; this flag indicates one is stored. */
|
||||||
|
auth_token_set: boolean;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
timeout_s: number;
|
||||||
|
description?: string;
|
||||||
|
tags: string[];
|
||||||
|
icon?: string;
|
||||||
|
icon_color?: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HTTPEndpointListResponse {
|
||||||
|
endpoints: HTTPEndpoint[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wire payload for `POST /http/endpoints` / `PUT /http/endpoints/{id}`.
|
||||||
|
* All fields optional — the route validates required-on-create separately. */
|
||||||
|
export interface HTTPEndpointWritePayload {
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
method?: HTTPMethod;
|
||||||
|
/** Plaintext token. PUT distinguishes None=keep / ""=clear; omit the field to keep. */
|
||||||
|
auth_token?: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
timeout_s?: number;
|
||||||
|
description?: string;
|
||||||
|
tags?: string[];
|
||||||
|
icon?: string;
|
||||||
|
icon_color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HTTPTestRequest {
|
||||||
|
url: string;
|
||||||
|
method: HTTPMethod;
|
||||||
|
auth_token: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
timeout_s: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HTTPTestResponse {
|
||||||
|
success: boolean;
|
||||||
|
status_code?: number;
|
||||||
|
body_preview?: string;
|
||||||
|
body_json?: unknown;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Asset ────────────────────────────────────────────────────
|
// ── Asset ────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface Asset {
|
export interface Asset {
|
||||||
@@ -799,7 +854,12 @@ export interface AssetListResponse {
|
|||||||
|
|
||||||
export type RuleType =
|
export type RuleType =
|
||||||
| 'application' | 'time_of_day' | 'system_idle'
|
| 'application' | 'time_of_day' | 'system_idle'
|
||||||
| 'display_state' | 'mqtt' | 'webhook' | 'startup';
|
| 'display_state' | 'mqtt' | 'webhook' | 'startup'
|
||||||
|
| 'home_assistant' | 'http_poll';
|
||||||
|
|
||||||
|
export type HTTPPollOperator =
|
||||||
|
| 'equals' | 'not_equals' | 'contains' | 'regex'
|
||||||
|
| 'gt' | 'lt' | 'exists';
|
||||||
|
|
||||||
export interface AutomationRule {
|
export interface AutomationRule {
|
||||||
rule_type: RuleType;
|
rule_type: RuleType;
|
||||||
@@ -814,6 +874,13 @@ export interface AutomationRule {
|
|||||||
payload?: string;
|
payload?: string;
|
||||||
match_mode?: string;
|
match_mode?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
|
/** home_assistant rule */
|
||||||
|
ha_source_id?: string;
|
||||||
|
entity_id?: string;
|
||||||
|
/** http_poll rule — references an HTTPValueSource. */
|
||||||
|
value_source_id?: string;
|
||||||
|
operator?: HTTPPollOperator;
|
||||||
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Automation {
|
export interface Automation {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Bindable scalar / colour primitives.
|
||||||
|
*
|
||||||
|
* A "bindable" value is either a plain literal or a reference to a value
|
||||||
|
* source (``{value | color, source_id}``). These types and the four
|
||||||
|
* accessor helpers are imported widely — over 30 frontend modules read
|
||||||
|
* them through the ``types.ts`` barrel — so they live in their own file
|
||||||
|
* to keep the entity-shape files free of helper-function noise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ── Bindable Float ───────────────────────────────────────────
|
||||||
|
// A scalar that is either a static value (plain number) or bound to a value source (dict).
|
||||||
|
|
||||||
|
export type BindableFloat = number | { value: number; source_id: string };
|
||||||
|
|
||||||
|
/** Extract the static value from a BindableFloat. */
|
||||||
|
export function bindableValue(b: BindableFloat | undefined, fallback: number): number {
|
||||||
|
if (b === undefined || b === null) return fallback;
|
||||||
|
if (typeof b === 'number') return b;
|
||||||
|
return b.value ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract the source_id from a BindableFloat (empty string = not bound). */
|
||||||
|
export function bindableSourceId(b: BindableFloat | undefined): string {
|
||||||
|
if (b === undefined || b === null) return '';
|
||||||
|
if (typeof b === 'number') return '';
|
||||||
|
return b.source_id ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bindable Color ──────────────────────────────────────────
|
||||||
|
// An RGB color that is either static ([R,G,B] array) or bound to a color value source.
|
||||||
|
|
||||||
|
export type BindableColor = number[] | { color: number[]; source_id: string };
|
||||||
|
|
||||||
|
/** Extract the static [R,G,B] from a BindableColor. */
|
||||||
|
export function bindableColor(b: BindableColor | undefined, fallback: number[]): number[] {
|
||||||
|
if (b === undefined || b === null) return fallback;
|
||||||
|
if (Array.isArray(b)) return b;
|
||||||
|
return b.color ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract the source_id from a BindableColor (empty string = not bound). */
|
||||||
|
export function bindableColorSourceId(b: BindableColor | undefined): string {
|
||||||
|
if (b === undefined || b === null) return '';
|
||||||
|
if (Array.isArray(b)) return '';
|
||||||
|
return b.source_id ?? '';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user