Lox Nexus

@lox-nexus/plugin-sdk/wire

The host/worker protocol: the messages between Nexus and a plugin worker, validated with zod on both sides, and the shapes they carry (catalog, panel, HTTP request and response). Most plugins only need the types; the messages are for tools and hosts.

Interfaces

PluginWorkerData

workerData the host passes to every plugin worker, so a module can tell it was started by Nexus

Properties

loxNexus

loxNexus: number

Type Aliases

HostFeature

HostFeature = typeof HOST_FEATURES[keyof typeof HOST_FEATURES]


SignalSpec

SignalSpec = z.infer<typeof SignalSpecSchema>


HostMessage

HostMessage = z.infer<typeof HostMessageSchema>


ActionAnswer

ActionAnswer = z.infer<typeof ActionAnswerSchema>


JsonSchema

JsonSchema = Record<string, unknown>

A JSON Schema document, as produced by zod's toJSONSchema.


CapabilityDescription

CapabilityDescription = z.infer<typeof CapabilityDescriptionSchema>


ActionDescription

ActionDescription = z.infer<typeof ActionDescriptionSchema>


PluginDescription

PluginDescription = z.infer<typeof PluginDescriptionSchema>


PluginHealth

PluginHealth = z.infer<typeof PluginHealthSchema>


LogLevel

LogLevel = z.infer<typeof LogLevelSchema>


WorkerMessage

WorkerMessage = z.infer<typeof WorkerMessageSchema>

Variables

HOST_FEATURES

const HOST_FEATURES: object

What a host understands beyond wire version 1, sent in init (hostFeatures). Plugins don't check these: the runtime does, and falls back to what older hosts understand. A host that sends none is one from before them.

Type Declaration

panelV2

readonly panelV2: "panel.v2" = "panel.v2"

tiles, notices, cells with tones and times, controls on key-value items and switch or instant buttons

panelPush

readonly panelPush: "panel.push" = "panel.push"

the panel.changed message (Plugin.panelChanged)

actionResult

readonly actionResult: "action.result" = "action.result"

action answers with a panel part

actionProgress

readonly actionProgress: "action.progress" = "action.progress"

the action.progress message (Action.progress)


WIRE_VERSION

const WIRE_VERSION: 1 = 1

bumped on any incompatible change; a worker announces its version in hello


SignalSpecSchema

const SignalSpecSchema: ZodObject<{ id: ZodString; kind: ZodEnum<{ sensor: "sensor"; actuator: "actuator"; }>; capability: ZodString; params: ZodUnknown; label: ZodOptional<ZodString>; }, $strip>

A configured sensor or actuator, as the host knows it.


HttpRequestSchema

const HttpRequestSchema: ZodObject<{ method: ZodString; path: ZodString; query: ZodRecord<ZodString, ZodString>; headers: ZodRecord<ZodString, ZodString>; body: ZodString; }, $strip>

An HTTP request to the plugin's webhook routes; path is below /hook/.


HostMessageSchema

const HostMessageSchema: ZodDiscriminatedUnion<[ZodObject<{ type: ZodLiteral<"init">; instanceId: ZodString; slug: ZodOptional<ZodString>; config: ZodUnknown; store: ZodRecord<ZodString, ZodUnknown>; timeZone: ZodOptional<ZodString>; logLevel: ZodOptional<ZodEnum<{ error: "error"; warn: "warn"; info: "info"; debug: "debug"; }>>; configVersion: ZodOptional<ZodInt>; hostFeatures: ZodOptional<ZodArray<ZodString>>; }, $strip>, ZodObject<{ type: ZodLiteral<"describe">; requestId: ZodInt; }, $strip>, ZodObject<{ type: ZodLiteral<"signal.add">; requestId: ZodInt; signal: ZodObject<{ id: ZodString; kind: ZodEnum<{ sensor: "sensor"; actuator: "actuator"; }>; capability: ZodString; params: ZodUnknown; label: ZodOptional<ZodString>; }, $strip>; }, $strip>], "type">

Messages from the host to a plugin worker. Requests carry a requestId and are answered with one response; the others are notifications.


ToneSchema

const ToneSchema: ZodEnum<{ error: "error"; ok: "ok"; warn: "warn"; info: "info"; muted: "muted"; }>

How a value reads at a glance: "ok" green, "warn" amber, "error" red, "info" blue, "muted" grey. The UI shows it with a dot and the colour, never by colour alone.


CellSchema

const CellSchema: ZodUnion<readonly [ZodString, ZodObject<{ text: ZodOptional<ZodString>; tone: ZodOptional<ZodEnum<{ error: "error"; ok: "ok"; warn: "warn"; info: "info"; muted: "muted"; }>>; until: ZodOptional<ZodNumber>; since: ZodOptional<ZodNumber>; at: ZodOptional<ZodNumber>; }, $strip>]>

A panel value: plain text, or text with a tone, and a time the browser counts down to (until, "in 12:30"), up from (since, "for 3 min", how long a state lasts) or shows as a moment (at, "12 s ago", e.g. the last poll) while the page is open, with the exact time on hover; times are epoch ms. A host without the "panel.v2" feature gets the text (a time as an absolute one) instead.


RowActionSchema

const RowActionSchema: ZodObject<{ action: ZodString; label: ZodOptional<ZodString>; input: ZodOptional<ZodRecord<ZodString, ZodUnknown>>; instant: ZodOptional<ZodBoolean>; switch: ZodOptional<ZodObject<{ checked: ZodBoolean; }, $strip>>; }, $strip>

A button for an action, on a table row, a key-value item or a section's heading; it opens the action's dialog with input filled in (its keys must be fields of the action's input schema). instant: runs at once without the dialog (after the action's confirm, if it has one), for actions whose input the button fills in completely. switch: shown as a switch in the state checked; flipping it runs the action with input at once (after the action's confirm, too), so put the new state in input.


PanelPartSchema

const PanelPartSchema: ZodDiscriminatedUnion<[ZodObject<{ type: ZodLiteral<"keyValue">; items: ZodArray<ZodObject<{ label: ZodString; value: ZodUnion<readonly [ZodString, ZodObject<{ text: ...; tone: ...; until: ...; since: ...; at: ...; }, $strip>]>; control: ZodOptional<ZodObject<{ action: ZodString; label: ZodOptional<...>; input: ZodOptional<...>; instant: ZodOptional<...>; switch: ZodOptional<...>; }, $strip>>; catalog: ZodOptional<ZodString>; }, $strip>>; }, $strip>, ZodObject<{ type: ZodLiteral<"table">; columns: ZodArray<ZodString>; rows: ZodArray<ZodArray<ZodUnion<readonly [ZodString, ZodObject<{ text: ZodOptional<...>; tone: ZodOptional<...>; until: ZodOptional<...>; since: ZodOptional<...>; at: ZodOptional<...>; }, $strip>]>>>; rowActions: ZodOptional<ZodArray<ZodArray<ZodObject<{ action: ZodString; label: ZodOptional<ZodString>; input: ZodOptional<ZodRecord<..., ...>>; instant: ZodOptional<ZodBoolean>; switch: ZodOptional<ZodObject<..., ...>>; }, $strip>>>>; rowCatalog: ZodOptional<ZodArray<ZodNullable<ZodString>>>; }, $strip>, ZodObject<{ type: ZodLiteral<"json">; data: ZodUnknown; }, $strip>], "type">

One part of a panel; a section holds one of these.


PanelSchema

const PanelSchema: ZodDiscriminatedUnion<[ZodObject<{ type: ZodLiteral<"keyValue">; items: ZodArray<ZodObject<{ label: ZodString; value: ZodUnion<readonly [ZodString, ZodObject<{ text: ...; tone: ...; until: ...; since: ...; at: ...; }, $strip>]>; control: ZodOptional<ZodObject<{ action: ZodString; label: ZodOptional<...>; input: ZodOptional<...>; instant: ZodOptional<...>; switch: ZodOptional<...>; }, $strip>>; catalog: ZodOptional<ZodString>; }, $strip>>; }, $strip>, ZodObject<{ type: ZodLiteral<"table">; columns: ZodArray<ZodString>; rows: ZodArray<ZodArray<ZodUnion<readonly [ZodString, ZodObject<{ text: ZodOptional<...>; tone: ZodOptional<...>; until: ZodOptional<...>; since: ZodOptional<...>; at: ZodOptional<...>; }, $strip>]>>>; rowActions: ZodOptional<ZodArray<ZodArray<ZodObject<{ action: ZodString; label: ZodOptional<ZodString>; input: ZodOptional<ZodRecord<..., ...>>; instant: ZodOptional<ZodBoolean>; switch: ZodOptional<ZodObject<..., ...>>; }, $strip>>>>; rowCatalog: ZodOptional<ZodArray<ZodNullable<ZodString>>>; }, $strip>, ZodObject<{ type: ZodLiteral<"json">; data: ZodUnknown; }, $strip>], "type">

A declarative status view a plugin shows on its page, returned by Plugin.panel(); plugins ship no UI code:

{ type: "keyValue", items: [{ label: "Firmware", value: "1.34" }, { label: "Inverter", value: { text: "running", tone: "ok" } }] }
{ type: "table", columns: ["Meter", "Power"], rows: [["Grid", "412 W"]], rowActions: [[{ action: "reset", label: "Reset" }]] }
{ type: "tiles", tiles: [{ label: "PV", value: 3200, unit: "W" }, { label: "Battery", value: 82, unit: "%", min: 0, max: 100 }] }
{ type: "notice", tone: "warn", title: "Not paired yet", text: "Scan the code in the Alexa app" }
{ type: "qr", data: "MT:Y.K90…", caption: "Scan in the Alexa app" }
{ type: "json", data: { anything: "JSON-serialisable" } }
{ type: "sections", sections: [{ title: "Inverter", panel: { type: "keyValue", items } }, { title: "Meters", panel: { type: "table", columns, rows } }] }

Values are cells: text, or text with a tone and a live countdown (see Cell). Buttons (see RowAction) go on table rows (rowActions), key-value items (control) and section headings (actions). A row or item that stands for something in the catalog (a client, a device) can point at its catalog node (rowCatalog, an item's catalog) for a "Signals" button that adds its signals directly. Hosts without the "panel.v2" feature get tiles and notices as key-value lists, cells as text and no item controls; the runtime converts them. Answers must be JSON: format BigInt and dates as text. Never put secrets in a panel.


ActionAnswerSchema

const ActionAnswerSchema: ZodUnion<readonly [ZodString, ZodObject<{ message: ZodOptional<ZodString>; panel: ZodOptional<ZodDiscriminatedUnion<[ZodObject<{ type: ZodLiteral<"keyValue">; items: ZodArray<ZodObject<..., ...>>; }, $strip>, ZodObject<{ type: ZodLiteral<"table">; columns: ZodArray<ZodString>; rows: ZodArray<ZodArray<...>>; rowActions: ZodOptional<ZodArray<...>>; rowCatalog: ZodOptional<ZodArray<...>>; }, $strip>, ZodObject<{ type: ZodLiteral<"json">; data: ZodUnknown; }, $strip>], "type">>; }, $strip>]>

What an action answers: a text, or (on hosts with the "action.result" feature) a message with a panel part shown under it, e.g. the QR code of a pairing window. Older hosts get the message.


CapabilityDescriptionSchema

const CapabilityDescriptionSchema: ZodObject<{ id: ZodString; label: ZodString; description: ZodOptional<ZodString>; params: ZodRecord<ZodString, ZodUnknown>; valueType: ZodUnion<readonly [ZodEnum<{ string: "string"; number: "number"; boolean: "boolean"; null: "null"; singleChannel: "singleChannel"; rgbw: "rgbw"; tunableWhite: "tunableWhite"; t5: "t5"; }>, ZodLiteral<"any">]>; unit: ZodCatch<ZodOptional<ZodString>>; decimals: ZodCatch<ZodOptional<ZodInt>>; staleAfterMin: ZodCatch<ZodOptional<ZodInt>>; }, $strip>

A sensor or actuator a plugin offers, ready to be configured with params.


ActionDescriptionSchema

const ActionDescriptionSchema: ZodObject<{ id: ZodString; label: ZodString; description: ZodOptional<ZodString>; input: ZodRecord<ZodString, ZodUnknown>; confirm: ZodOptional<ZodString>; placement: ZodOptional<ZodEnum<{ panel: "panel"; toolbar: "toolbar"; }>>; }, $strip>

An action a user can start from the instance page (static actions).


PluginDescriptionSchema

const PluginDescriptionSchema: ZodObject<{ config: ZodRecord<ZodString, ZodUnknown>; sensors: ZodArray<ZodObject<{ id: ZodString; label: ZodString; description: ZodOptional<ZodString>; params: ZodRecord<ZodString, ZodUnknown>; valueType: ZodUnion<readonly [ZodEnum<{ string: "string"; number: "number"; boolean: "boolean"; null: "null"; singleChannel: "singleChannel"; rgbw: "rgbw"; tunableWhite: "tunableWhite"; t5: "t5"; }>, ZodLiteral<"any">]>; unit: ZodCatch<ZodOptional<ZodString>>; decimals: ZodCatch<ZodOptional<ZodInt>>; staleAfterMin: ZodCatch<ZodOptional<ZodInt>>; }, $strip>>; actuators: ZodArray<ZodObject<{ id: ZodString; label: ZodString; description: ZodOptional<ZodString>; params: ZodRecord<ZodString, ZodUnknown>; valueType: ZodUnion<readonly [ZodEnum<{ string: "string"; number: "number"; boolean: "boolean"; null: "null"; singleChannel: "singleChannel"; rgbw: "rgbw"; tunableWhite: "tunableWhite"; t5: "t5"; }>, ZodLiteral<"any">]>; unit: ZodCatch<ZodOptional<ZodString>>; decimals: ZodCatch<ZodOptional<ZodInt>>; staleAfterMin: ZodCatch<ZodOptional<ZodInt>>; }, $strip>>; watches: ZodOptional<ZodObject<{ label: ZodString; description: ZodOptional<ZodString>; }, $strip>>; actions: ZodOptional<ZodArray<ZodObject<{ id: ZodString; label: ZodString; description: ZodOptional<ZodString>; input: ZodRecord<ZodString, ZodUnknown>; confirm: ZodOptional<ZodString>; placement: ZodOptional<ZodEnum<{ panel: "panel"; toolbar: "toolbar"; }>>; }, $strip>>>; testable: ZodOptional<ZodLiteral<true>>; panelPush: ZodOptional<ZodLiteral<true>>; discoverable: ZodOptional<ZodArray<ZodString>>; }, $strip>

The answer to describe: everything a UI needs to configure a plugin without loading it.


CatalogEntrySchema

const CatalogEntrySchema: ZodObject<{ kind: ZodEnum<{ sensor: "sensor"; actuator: "actuator"; }>; capability: ZodString; params: ZodUnknown; label: ZodString; value: ZodCatch<ZodOptional<ZodType<Value, unknown, $ZodTypeInternals<Value, unknown>>>>; unit: ZodCatch<ZodOptional<ZodString>>; }, $strip>

A ready-to-add sensor or actuator in a catalog: the capability with its params filled in.


CatalogNodeSchema

const CatalogNodeSchema: z.ZodType<CatalogNode>


DiscoveredSchema

const DiscoveredSchema: ZodArray<ZodObject<{ label: ZodString; value: ZodString; detail: ZodOptional<ZodString>; }, $strip>>

What static discover found for a config field, e.g. Home Assistant servers on the network: value is what the field gets when the user picks it, label and detail what the list shows.


HttpResponseSchema

const HttpResponseSchema: ZodObject<{ status: ZodInt; headers: ZodDefault<ZodRecord<ZodString, ZodString>>; body: ZodDefault<ZodString>; }, $strip>

What a webhook handler answers (see PluginHttp.route); body is utf-8 text, headers and body default to empty.


WatchDeclarationSchema

const WatchDeclarationSchema: ZodObject<{ label: ZodString; description: ZodOptional<ZodString>; }, $strip>

What a plugin that watches signals declares (static readonly watches): shown to the user where they choose the signals it receives.


WatchedSignalSchema

const WatchedSignalSchema: ZodObject<{ id: ZodString; topic: ZodString; label: ZodString; valueType: ZodUnion<readonly [ZodEnum<{ string: "string"; number: "number"; boolean: "boolean"; null: "null"; singleChannel: "singleChannel"; rgbw: "rgbw"; tunableWhite: "tunableWhite"; t5: "t5"; }>, ZodLiteral<"any">]>; owner: ZodOptional<ZodObject<{ kind: ZodEnum<{ miniserver: "miniserver"; plugin: "plugin"; }>; id: ZodString; }, $strip>>; unit: ZodOptional<ZodString>; decimals: ZodOptional<ZodInt>; }, $strip>

A signal the user chose for a watching plugin: any signal of Nexus, a Miniserver's or another plugin's.


WatchedValueSchema

const WatchedValueSchema: ZodObject<{ signalId: ZodString; ts: ZodNumber; kind: ZodEnum<{ state: "state"; command: "command"; }>; value: ZodType<Value, unknown, $ZodTypeInternals<Value, unknown>>; forced: ZodOptional<ZodLiteral<true>>; initial: ZodOptional<ZodLiteral<true>>; }, $strip>

One value of a watched signal, as subscribers of the bus saw it.


PluginHealthSchema

const PluginHealthSchema: ZodEnum<{ running: "running"; degraded: "degraded"; }>

The status a plugin reports for itself; the host adds its own lifecycle states around it.


LogLevelSchema

const LogLevelSchema: ZodEnum<{ error: "error"; warn: "warn"; info: "info"; debug: "debug"; }>

log levels in increasing severity; the host decides which ones it keeps


HelloSchema

const HelloSchema: ZodObject<{ type: ZodLiteral<"hello">; version: ZodInt; features: ZodOptional<ZodArray<ZodString>>; }, $strip>

The worker's first message: the wire version it was built with, and the additions to that version it understands, so the host sends them only to workers that do ("log.level"). Frozen across wire versions (ADR 0019): a host reads it before it knows which version's messages follow.


WorkerMessageSchema

const WorkerMessageSchema: ZodDiscriminatedUnion<[ZodObject<{ type: ZodLiteral<"hello">; version: ZodInt; features: ZodOptional<ZodArray<ZodString>>; }, $strip>, ZodObject<{ type: ZodLiteral<"ready">; }, $strip>, ZodObject<{ type: ZodLiteral<"failed">; error: ZodString; }, $strip>], "type">

Messages from a plugin worker to the host. The host validates every one of them: a worker that sends something else is treated as broken.

References

HttpRequest

Re-exports HttpRequest


CatalogEntry

Re-exports CatalogEntry


CatalogNode

Re-exports CatalogNode


Discovered

Re-exports Discovered


HttpResponse

Re-exports HttpResponse


Cell

Re-exports Cell


Panel

Re-exports Panel


PanelPart

Re-exports PanelPart


RowAction

Re-exports RowAction


Tone

Re-exports Tone


WatchDeclaration

Re-exports WatchDeclaration


WatchedSignal

Re-exports WatchedSignal


WatchedValue

Re-exports WatchedValue