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
|
||||
* 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 ───────────────────────────────────────────
|
||||
// A scalar that is either a static value (plain number) or bound to a value source (dict).
|
||||
// ── Bindable Primitives ─────────────────────────────────────
|
||||
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 };
|
||||
|
||||
/** 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 ?? '';
|
||||
}
|
||||
// Local aliases used by the entity interfaces below so TypeScript can
|
||||
// resolve them without an extra import at every reference site.
|
||||
import type { BindableFloat, BindableColor } from './types/bindable.ts';
|
||||
|
||||
// ── Device ────────────────────────────────────────────────────
|
||||
|
||||
@@ -186,7 +167,7 @@ export type OutputTarget = LedOutputTarget | HALightOutputTarget | Z2MLightOutpu
|
||||
// ── Color Strip Source ────────────────────────────────────────
|
||||
|
||||
export type CSSSourceType =
|
||||
| 'picture' | 'picture_advanced' | 'static' | 'gradient'
|
||||
| 'picture' | 'picture_advanced' | 'single_color' | 'gradient'
|
||||
| 'effect' | 'composite' | 'mapped'
|
||||
| 'audio' | 'api_input' | 'notification' | 'daylight'
|
||||
| 'candlelight' | 'processed' | 'weather' | 'key_colors'
|
||||
@@ -379,7 +360,7 @@ export type ValueSourceType =
|
||||
| 'adaptive_time' | 'adaptive_scene' | 'daylight'
|
||||
| 'static_color' | 'animated_color' | 'adaptive_time_color'
|
||||
| 'ha_entity' | 'gradient_map' | 'css_extract'
|
||||
| 'system_metrics' | 'game_event';
|
||||
| 'system_metrics' | 'game_event' | 'http';
|
||||
|
||||
export interface SchedulePoint {
|
||||
time: string;
|
||||
@@ -534,6 +515,17 @@ export interface GameEventValueSource extends ValueSourceBase {
|
||||
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 =
|
||||
| StaticValueSource
|
||||
| AnimatedValueSource
|
||||
@@ -548,7 +540,8 @@ export type ValueSource =
|
||||
| GradientMapValueSource
|
||||
| CSSExtractValueSource
|
||||
| SystemMetricsValueSource
|
||||
| GameEventValueSource;
|
||||
| GameEventValueSource
|
||||
| HTTPValueSource;
|
||||
|
||||
// ── Audio Source ───────────────────────────────────────────────
|
||||
|
||||
@@ -772,6 +765,68 @@ export interface MQTTStatusResponse {
|
||||
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 ────────────────────────────────────────────────────
|
||||
|
||||
export interface Asset {
|
||||
@@ -799,7 +854,12 @@ export interface AssetListResponse {
|
||||
|
||||
export type RuleType =
|
||||
| '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 {
|
||||
rule_type: RuleType;
|
||||
@@ -814,6 +874,13 @@ export interface AutomationRule {
|
||||
payload?: string;
|
||||
match_mode?: 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 {
|
||||
|
||||
@@ -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