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
@@ -1,3 +1,4 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { useNavigate } from "@tanstack/react-router"
|
||||
import { useEffect } from "react"
|
||||
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
import { libraryQueryOptions } from "./queries"
|
||||
|
||||
/**
|
||||
* ⌘K: add a node, jump to another flow, or run the current one, without
|
||||
@@ -21,6 +23,7 @@ export function CommandPalette({
|
||||
nodeTypes,
|
||||
flows,
|
||||
onAddNode,
|
||||
onAddSharedNode,
|
||||
onRun,
|
||||
}: {
|
||||
open: boolean
|
||||
@@ -28,9 +31,14 @@ export function CommandPalette({
|
||||
nodeTypes: NodeTypeInfo[]
|
||||
flows: FlowSummary[]
|
||||
onAddNode: (type: string) => void
|
||||
onAddSharedNode: (libName: string) => void
|
||||
onRun: () => void
|
||||
}) {
|
||||
const navigate = useNavigate()
|
||||
const { data: libraryData } = useQuery({
|
||||
...libraryQueryOptions(),
|
||||
enabled: open,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (event: KeyboardEvent) => {
|
||||
@@ -48,6 +56,8 @@ export function CommandPalette({
|
||||
// dialog outright is deterministic; the palette does not need to fade out.
|
||||
if (!open) return null
|
||||
|
||||
const library = libraryData ?? []
|
||||
|
||||
const close = (action: () => void) => {
|
||||
onOpenChange(false)
|
||||
action()
|
||||
@@ -81,6 +91,28 @@ export function CommandPalette({
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
{library.length > 0 ? (
|
||||
<CommandGroup heading="Reusable nodes">
|
||||
{library.map((entry) => (
|
||||
<CommandItem
|
||||
key={entry.name}
|
||||
value={`shared ${entry.name}`}
|
||||
onSelect={() => close(() => onAddSharedNode(entry.name))}
|
||||
>
|
||||
<span className="flex flex-col">
|
||||
<span className="font-mono">{entry.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(entry.used_by ?? []).length === 1
|
||||
? "Used once"
|
||||
: `Used ${(entry.used_by ?? []).length} times`}
|
||||
, code shared with every flow using it
|
||||
</span>
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
) : null}
|
||||
|
||||
{flows.length > 0 ? (
|
||||
<CommandGroup heading="Flows">
|
||||
{flows.map((flow) => (
|
||||
|
||||
Reference in New Issue
Block a user