Add the connector contract, reusable nodes and per-port intervals
Connectors are the device-facing node class third parties write, so the surface they build against is versioned and documented: ConnectorNode carries a declared contract version, a polling loop that publishes only what changed and reports health around it, and parameters whose credential fields are marked x-secret so the editor offers the secrets store instead of a text box. They are found through the fluksio.node_types entry point group, with the package's own metadata as the manifest. docs/connectors/ has the contract and the authoring guide; connector-skeleton/ is a working one to copy. The controller no longer knows what any node type is: start, stop and report_health are protocol methods on Node, and the built-ins were migrated to them first, so the hooks a connector implements are the ones the engine has been driving all along. Marking a node reusable moves its source to _lib/ and points the node at it by name. Other flows instantiate it with their own ports and settings, one fix reaches all of them, and a shared source still in use cannot be deleted. Ports gained an interval: an output publishes, and an input wakes its node, at most every n seconds. State keeps the latest value, so only the delivery is skipped, and pressing Run is never throttled. Also fixes autosave sending no version on its first save of a session, which made every flow saved more than once conflict with itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7344eac262
commit
3724b68f23
@@ -343,6 +343,26 @@ export const HistoryPointSchema = {
|
||||
description: 'One numeric value a message carried, and when.'
|
||||
} as const;
|
||||
|
||||
export const LibraryNodeSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
used_by: {
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Used By'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
title: 'LibraryNode',
|
||||
description: 'A node source shared across flows, and who is using it.'
|
||||
} as const;
|
||||
|
||||
export const MessageSchema = {
|
||||
properties: {
|
||||
message: {
|
||||
@@ -398,6 +418,12 @@ export const MessageSpecSchema = {
|
||||
dtype: {
|
||||
'$ref': '#/components/schemas/DType',
|
||||
default: 'float'
|
||||
},
|
||||
interval: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
title: 'Interval',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -408,7 +434,11 @@ export const MessageSpecSchema = {
|
||||
the flow name at load time; empty means the port is unbound.
|
||||
: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 dtype: Payload type, validated on every message that passes through.
|
||||
: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
|
||||
delivery is skipped.`
|
||||
} as const;
|
||||
|
||||
export const MessageValueSchema = {
|
||||
@@ -492,6 +522,17 @@ export const NodeDef_InputSchema = {
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Provides'
|
||||
},
|
||||
source_ref: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Source Ref'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -541,6 +582,17 @@ export const NodeDef_OutputSchema = {
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Provides'
|
||||
},
|
||||
source_ref: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Source Ref'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -583,12 +635,29 @@ export const NodeStatusPublicSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Error'
|
||||
},
|
||||
health: {
|
||||
type: 'string',
|
||||
enum: ['ok', 'degraded', 'down'],
|
||||
title: 'Health',
|
||||
default: 'ok'
|
||||
},
|
||||
health_detail: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Health Detail'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
title: 'NodeStatusPublic',
|
||||
description: 'Whether a node loaded, and why not.'
|
||||
description: 'Whether a node loaded, and how its connection is doing.'
|
||||
} as const;
|
||||
|
||||
export const NodeTypeInfoSchema = {
|
||||
@@ -614,6 +683,17 @@ export const NodeTypeInfoSchema = {
|
||||
type: 'boolean',
|
||||
title: 'Has Source',
|
||||
default: false
|
||||
},
|
||||
plugin: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Plugin'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -733,6 +813,18 @@ export const SecretValueSchema = {
|
||||
title: 'SecretValue'
|
||||
} as const;
|
||||
|
||||
export const ShareRequestSchema = {
|
||||
properties: {
|
||||
lib_name: {
|
||||
type: 'string',
|
||||
title: 'Lib Name'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['lib_name'],
|
||||
title: 'ShareRequest'
|
||||
} as const;
|
||||
|
||||
export const TokenSchema = {
|
||||
properties: {
|
||||
access_token: {
|
||||
|
||||
Reference in New Issue
Block a user