flow: structured dtypes, and the widgets that read them
A series, record or list message declares its shape instead of riding DType.JSON, so a widget binds a shape rather than some JSON and a wrong binding is refused before anything runs. A list declares its item type, which is what keeps list[float] expressible for a pipeline. On top of that: an agenda over a list, a notification over a record, and a dashboard alert channel that publishes engine faults as one — so a panel can show what went wrong without a flow wiring it by hand. Also: only None means a node published nothing, a falsy value of the wrong shape is now the named error it always should have been; and the gauge's readout says its size is viewBox geometry rather than type scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -298,7 +298,7 @@ export const ChannelSchema = {
|
||||
},
|
||||
kind: {
|
||||
type: 'string',
|
||||
enum: ['ntfy', 'smtp', 'webhook'],
|
||||
enum: ['ntfy', 'smtp', 'webhook', 'dashboard'],
|
||||
title: 'Kind'
|
||||
},
|
||||
enabled: {
|
||||
@@ -320,10 +320,15 @@ export const ChannelSchema = {
|
||||
|
||||
export const DTypeSchema = {
|
||||
type: 'string',
|
||||
enum: ['float', 'int', 'str', 'bool', 'json'],
|
||||
enum: ['float', 'int', 'str', 'bool', 'json', 'series', 'record', 'list'],
|
||||
title: 'DType',
|
||||
description: `Serializable payload types.
|
||||
|
||||
The scalars carry what a single reading can say. The three structured ones
|
||||
are declared shapes rather than "some JSON": a widget or a downstream node
|
||||
knows what it is getting before anything runs, which is what lets the
|
||||
dashboard picker offer a message and refuse a wrong binding.
|
||||
|
||||
Binary payloads (tensors, images) will arrive later as explicitly declared
|
||||
codec fields; until then everything on the wire is JSON.`
|
||||
} as const;
|
||||
@@ -1151,6 +1156,16 @@ export const MessageSpecSchema = {
|
||||
'$ref': '#/components/schemas/DType',
|
||||
default: 'float'
|
||||
},
|
||||
item: {
|
||||
anyOf: [
|
||||
{
|
||||
'$ref': '#/components/schemas/DType'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
]
|
||||
},
|
||||
interval: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
@@ -1172,6 +1187,10 @@ export const MessageSpecSchema = {
|
||||
:param port: The identifier the node function sees. Defaults to the last
|
||||
segment of \`\`name\`\`, so unqualified flows read naturally.
|
||||
:param dtype: Payload type, validated on every message that passes through.
|
||||
:param item: The type of each item of a \`\`list\`\` port, ignored otherwise.
|
||||
Unset means \`\`record\`\`, which is what the agenda and forecast widgets
|
||||
read; \`\`float\`\` is the numeric list a pipeline passes around. A list of
|
||||
lists, or of series, is refused — one declared level is the point.
|
||||
:param interval: Deliver at most every this many seconds; 0 is every time.
|
||||
On an output it holds back publishing, on an input it holds back waking
|
||||
the node. The value is never lost — state keeps the latest — only the
|
||||
@@ -2497,7 +2516,7 @@ export const WidgetDefSchema = {
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['stat', 'gauge', 'chart', 'markdown', 'button', 'switch', 'slider', 'input', 'dropdown'],
|
||||
enum: ['stat', 'gauge', 'chart', 'markdown', 'agenda', 'notification', 'button', 'switch', 'slider', 'input', 'dropdown'],
|
||||
title: 'Type'
|
||||
},
|
||||
title: {
|
||||
@@ -2525,7 +2544,14 @@ export const WidgetDefSchema = {
|
||||
|
||||
\`\`config\`\` is per type — a chart names its series, a button names the
|
||||
message it publishes — and is validated against the type below rather than
|
||||
by a schema per class, because the whole set is small and closed.`
|
||||
by a schema per class, because the whole set is small and closed.
|
||||
|
||||
A chart comes in two kinds. The default reads what the engine kept for a
|
||||
message. One with \`\`source: "query"\`\` asks instead, and its config is
|
||||
\`\`{source, request, request_dtype: "record", message, dtype: "series",
|
||||
refresh_s, range_s}\`\`: it publishes \`\`{range_s, interval_s}\`\` to
|
||||
\`\`request\`\` exactly as a slider publishes a value, and draws the \`\`series\`\`
|
||||
a flow answers with on \`\`message\`\`.`
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__dashboards__PublishRequestSchema = {
|
||||
|
||||
@@ -120,14 +120,14 @@ export type BrainNode = {
|
||||
*/
|
||||
export type Channel = {
|
||||
name: string;
|
||||
kind: 'ntfy' | 'smtp' | 'webhook';
|
||||
kind: 'ntfy' | 'smtp' | 'webhook' | 'dashboard';
|
||||
enabled?: boolean;
|
||||
config?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type kind = 'ntfy' | 'smtp' | 'webhook';
|
||||
export type kind = 'ntfy' | 'smtp' | 'webhook' | 'dashboard';
|
||||
|
||||
/**
|
||||
* A dashboard as stored, and as the API hands it over.
|
||||
@@ -185,10 +185,15 @@ export type DeadLetter = {
|
||||
/**
|
||||
* Serializable payload types.
|
||||
*
|
||||
* The scalars carry what a single reading can say. The three structured ones
|
||||
* are declared shapes rather than "some JSON": a widget or a downstream node
|
||||
* knows what it is getting before anything runs, which is what lets the
|
||||
* dashboard picker offer a message and refuse a wrong binding.
|
||||
*
|
||||
* Binary payloads (tensors, images) will arrive later as explicitly declared
|
||||
* codec fields; until then everything on the wire is JSON.
|
||||
*/
|
||||
export type DType = 'float' | 'int' | 'str' | 'bool' | 'json';
|
||||
export type DType = 'float' | 'int' | 'str' | 'bool' | 'json' | 'series' | 'record' | 'list';
|
||||
|
||||
/**
|
||||
* Something wired into this flow that is not a node in it.
|
||||
@@ -390,6 +395,10 @@ export type MessagePoints = {
|
||||
* :param port: The identifier the node function sees. Defaults to the last
|
||||
* segment of ``name``, so unqualified flows read naturally.
|
||||
* :param dtype: Payload type, validated on every message that passes through.
|
||||
* :param item: The type of each item of a ``list`` port, ignored otherwise.
|
||||
* Unset means ``record``, which is what the agenda and forecast widgets
|
||||
* read; ``float`` is the numeric list a pipeline passes around. A list of
|
||||
* lists, or of series, is refused — one declared level is the point.
|
||||
* :param interval: Deliver at most every this many seconds; 0 is every time.
|
||||
* On an output it holds back publishing, on an input it holds back waking
|
||||
* the node. The value is never lost — state keeps the latest — only the
|
||||
@@ -403,6 +412,7 @@ export type MessageSpec = {
|
||||
name?: string;
|
||||
port?: string;
|
||||
dtype?: DType;
|
||||
item?: (DType | null);
|
||||
interval?: number;
|
||||
trigger?: boolean;
|
||||
};
|
||||
@@ -764,10 +774,17 @@ export type ValidationResult = {
|
||||
* ``config`` is per type — a chart names its series, a button names the
|
||||
* message it publishes — and is validated against the type below rather than
|
||||
* by a schema per class, because the whole set is small and closed.
|
||||
*
|
||||
* A chart comes in two kinds. The default reads what the engine kept for a
|
||||
* message. One with ``source: "query"`` asks instead, and its config is
|
||||
* ``{source, request, request_dtype: "record", message, dtype: "series",
|
||||
* refresh_s, range_s}``: it publishes ``{range_s, interval_s}`` to
|
||||
* ``request`` exactly as a slider publishes a value, and draws the ``series``
|
||||
* a flow answers with on ``message``.
|
||||
*/
|
||||
export type WidgetDef = {
|
||||
id: string;
|
||||
type: 'stat' | 'gauge' | 'chart' | 'markdown' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
|
||||
type: 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
|
||||
title?: string;
|
||||
layout?: {
|
||||
[key: string]: Placement;
|
||||
@@ -777,7 +794,7 @@ export type WidgetDef = {
|
||||
};
|
||||
};
|
||||
|
||||
export type type = 'stat' | 'gauge' | 'chart' | 'markdown' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
|
||||
export type type = 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
|
||||
|
||||
export type AlertsReadAlertsConfigResponse = (AlertsConfig);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user