// Shared types used across stores, pages, and widgets. import type { AutomationGraph, GraphNode, GraphEdge } from '@nodrix/blocks-shared'; export type { AutomationGraph, GraphNode, GraphEdge }; export type InstanceRole = 'owner' | 'admin' | 'member'; // A project the user has access to. owner/admin see all; members see only the // projects they're assigned to. Everyone with access has full control. export type ProjectRef = { id: string; name: string }; export type User = { id: string; email: string; role: InstanceRole; first_name?: string | null; last_name?: string | null; name?: string | null; last_login_at?: number | null; created_at?: number; updated_at?: number; }; export type Project = { id: string; name: string; created_at: number; description?: string | null; updated_at?: number; archived_at?: number | null; }; // A user row in the instance Users management list, with their assigned projects // (empty for owner/admin, who reach every project implicitly). export type InstanceUser = { id: string; email: string; name: string | null; first_name: string | null; last_name: string | null; role: InstanceRole; last_login_at: number | null; created_at: number; projects: ProjectRef[]; }; // Returned once on creation — a one-time accept link. export type InviteCreated = { id: string; email: string; instance_role: 'admin' | 'member'; url: string; token?: string; expires_at?: number; }; export type InvitePreview = { valid: boolean; email?: string | null; instance_role?: 'admin' | 'member'; inviter_email?: string | null; }; export type Variable = { id: string; key: string; unit?: string | null; created_at: number; updated_at: number; last_seen: number | null; }; export type Device = { id: string; name: string; chip: string | null; firmware_version: string | null; is_default: number; first_seen: number | null; last_seen: number | null; desired_firmware_id: string | null; ota_status: string | null; }; export type Firmware = { id: string; version: string; target: string | null; size: number; sha256: string; notes: string | null; created_at: number; }; export type ProjectToken = { id: string; name?: string | null; created_at: number; last_used_at: number | null; revoked_at: number | null; }; export type ProjectTokenWithSecret = ProjectToken & { token: string }; export type UserToken = { id: string; project_id: string | null; scope: 'read' | 'admin'; created_at: number; last_used_at: number | null; revoked_at: number | null; name?: string | null; expires_at?: number | null; }; export type { WidgetType } from '@nodrix/widgets-shared'; import type { WidgetType as _WT } from '@nodrix/widgets-shared'; export type WidgetInstance = { id: string; x: number; y: number; w: number; h: number; type: _WT; props: Record; }; // Phone (<768px) position override for one widget — positions ONLY (by id), // never type/props, which stay single-source-of-truth on the desktop items. export type MobilePlacement = { id: string; x: number; y: number; w: number; h: number }; export type Layout = { grid: { columns: number }; items: WidgetInstance[]; // Which device the widgets' variable keys belong to. Absent => the default. device?: string | null; // Phone layout override, nested in the same layout JSON (no separate column). // Absent/null => auto-derive the phone layout from the desktop items. mobile?: { items: MobilePlacement[] } | null; // Public-view auto-refresh cadence in seconds. Owner-set, server-clamped, and // delivered via the API (never a URL param) so viewers can't override it. refresh?: number; }; export type DashboardMeta = { id: string; name: string; created_at: number; updated_at: number; description?: string | null; visibility?: 'private' | 'public'; share_token?: string | null; archived_at?: number | null; }; export type Dashboard = DashboardMeta & { layout: Layout; }; // Public (unauthenticated) share endpoints. The layout response mirrors the // fields a viewer needs; the state response mirrors the WS snapshot's data. export type PublicDashboard = { id: string; name: string; description: string | null; project_id: string; layout: Layout; }; // Chart series in the compact columnar shape: grouped by variable, parallel // timestamp/value arrays. Replaces the old flat `{ ts, variable, value }[]` so // the variable name isn't repeated per point. export type CompactSeries = Record; export type PublicState = { variables: Record; series: CompactSeries; }; // Response from the share / unshare admin endpoints. export type ShareState = { id: string; visibility: 'private' | 'public'; share_token: string | null; updated_at: number; }; // ─── New entities (backed by 0002_schema_v2.sql) ───────────────────────────── export type AutomationTriggerType = | 'variable' | 'manual' | 'schedule' | 'sunset_sunrise' | 'event'; // ─── Trigger config shapes (stored in automations.trigger_config) ──────────── export type VariableOperator = '>' | '<' | '>=' | '<=' | '==' | '!=' | 'changed'; export type VariableTriggerConfig = { variable: string; // variable key device?: string | null; // device id; absent/empty = any device operator: VariableOperator; value?: number | string | boolean; // omitted for 'changed' mode?: 'edge' | 'always'; // edge = fire once on entry (default) }; export type ScheduleTriggerConfig = { time: string; // 'HH:MM' 24h days?: number[]; // 0=Sun..6=Sat; empty/absent = every day tz?: string; // IANA tz, default 'UTC' }; export type SolarTriggerConfig = { event: 'sunrise' | 'sunset'; lat: number; lng: number; offset_minutes?: number; // +/- minutes around the solar event }; export type EventTriggerConfig = { event: string; // matched against POST /v1/events { event } }; // ─── Action descriptors (stored in automations.actions, ordered) ───────────── export type Action = | { type: 'set_variable'; variable: string; value: number | string | boolean } | { type: 'call_integration'; integration_id: string; payload?: Record } | { type: 'emit_event'; event: string; payload?: Record }; export type Automation = { id: string; project_id: string; name: string; description: string | null; enabled: boolean; trigger_type: AutomationTriggerType; trigger_config: unknown; // JSON; legacy primary trigger actions: unknown[]; // JSON ordered list (legacy) graph: AutomationGraph | null; // flow graph; source of truth created_at: number; updated_at: number; last_run_at: number | null; last_run_status: 'ok' | 'error' | 'skipped' | null; last_error: string | null; }; // Sourced from the shared packages; re-exported so '../types' imports keep working. // (GraphNode/GraphEdge/AutomationGraph are imported + re-exported at the top.) export type { IntegrationKind, Integration } from '@nodrix/integrations-shared'; export type { IntegrationResult as IntegrationTestResult } from '@nodrix/integrations-shared'; export type AuditLogEntry = { id: number; project_id: string | null; project_name: string | null; // joined from projects on read user_id: string | null; user_email: string | null; // joined from users on read action: string; // e.g. 'device.create', 'automation.run' target_type: string | null; target_id: string | null; metadata: unknown; // JSON created_at: number; }; // ─── Realtime WS ───────────────────────────────────────────────────────────── export type SnapshotMsg = { type: 'snapshot'; dashboard: string; layout: Layout; variables: Record; series: CompactSeries; }; export type UpdateMsg = { type: 'update'; variable: string; value: unknown; ts: number; }; // A whole ingest batch in one frame (shares one `ts`). export type UpdatesMsg = { type: 'updates'; ts: number; points: Array<{ variable: string; value: unknown }>; }; // Reconnect resume: points/state since the client's cursor. No layout — the client // keeps the one from its initial snapshot. export type DeltaMsg = { type: 'delta'; dashboard: string; variables: Record; series: CompactSeries; }; export type WsServerMsg = | SnapshotMsg | UpdateMsg | UpdatesMsg | DeltaMsg | { type: 'error'; reason: string } | { type: 'ack'; req: string; ok: boolean; reason?: string }; export type WsClientMsg = { type: 'control'; req?: string; variable: string; value?: unknown; };