Something went wrong. Try again.
The IoT platform Cloudflare didn't build. Telemetry, realtime dashboards, and automations on Workers + D1 + R2 + Durable Objects — in your… nodrix.live
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291// 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 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<string, unknown>;};
// 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[]; // 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<string, { t: number[]; v: number[] }>;
export type PublicState = { variables: Record<string, { value: unknown; received_at: number }>; 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 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<string, unknown> } | { type: 'emit_event'; event: string; payload?: Record<string, unknown> };
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<string, { value: unknown; received_at: number }>; 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<string, { value: unknown; received_at: number }>; 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;};