From dd7d6e5fc0513acf7b6a265b767d49a9090c80ba Mon Sep 17 00:00:00 2001 From: lelandks Date: Sun, 4 Oct 2026 17:30:00 -0400 Subject: [PATCH 1/2] docs: document macro nodes Updated documentation for custom nodes, including details on macro nodes and their configuration. --- website/content/docs/5-custom-nodes.mdx | 102 ++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/website/content/docs/5-custom-nodes.mdx b/website/content/docs/5-custom-nodes.mdx index 3c0a50c6..87ae0a0f 100644 --- a/website/content/docs/5-custom-nodes.mdx +++ b/website/content/docs/5-custom-nodes.mdx @@ -47,7 +47,7 @@ The first argument of the `run` function is an object containing the values of t Flyde use of RxJS is _very_ simplistic and should be treated as a mere implementation detail. You don't need to know RxJS to create custom nodes. Neither should you rely on a full RxJS API being available in your custom nodes. ::: -The `run` function also accepts a third argument, which is an object containing advanced utilities to help author more complex custom nodes. One of these utilities is the `state` object, which gives the node access to state that will be available to it while the flow is running. The `state` object is `Map`. +The `run` function also accepts a third argument, which is an object containing advanced utilities to help author more complex custom nodes. One of these utilities is the `state` object, which gives the node access to state that will be available to it while the flow is running. The `state` object is `Map<string, any>`. For example, the following custom node will emit the average of all the numbers it received since the flow started: @@ -84,18 +84,106 @@ It will appear on the "Current project" section of the "nodes library -> view al ## Macro Nodes -:::info -Macro node docs are still in progress. Please refer to the [flyde/stdlib](https://github.com/flydelabs/flyde/tree/main/stdlib) for examples. -::: +Macro nodes are code nodes with **edit-time configuration**. Use them when an instance's configuration should change its pins, display text, or runtime behavior. + +In the current API, define a macro node as a `CodeNode<Config>` with `mode: "advanced"`. + +### Basic structure + +```ts +import { CodeNode } from "@flyde/core"; -Each macro node is expected to expose an already bundled React component that can render and edit the node's configuration. -Each component is expected to be bundled separately, and export a window variable named `__MacroNode__ID`, which is the ID of the macro node. +interface FanOutConfig { + count: number; +} -Until this is properly documented, and potentially abstracted, you may follow the stdlib's bundle config to see how it is done there - https://github.com/flydelabs/flyde/blob/main/stdlib/package.json#L52. +export const FanOut: CodeNode<FanOutConfig> = { + id: "FanOut", + mode: "advanced", + defaultConfig: { + count: 2, + }, + inputs: { + value: { description: "Value to fan out" }, + }, + outputs: (config) => + Object.fromEntries( + Array.from({ length: config.count }, (_, index) => [ + `item${index + 1}`, + { description: `Output ${index + 1}` }, + ]) + ), + + displayName: (config) => `Fan out to ${config.count} outputs`, + + run: ({ value }, outputs, { context }) => { + for (let index = 0; index < context.config.count; index++) { + outputs[`item${index + 1}`].next(value); + } + }, + + editorConfig: { + type: "structured", + fields: [ + { + type: "number", + configKey: "count", + label: "Output count", + typeData: { min: 1 }, + }, + ], + }, +}; +``` + +The important pieces are: + +- `defaultConfig` is the starting configuration for a new node instance. +- `inputs` and `outputs` may be objects or functions of the current configuration. Functions let the node's shape change when configuration changes. +- `displayName`, `description`, `reactiveInputs`, `completionOutputs`, and `overrideNodeBodyHtml` can also depend on configuration where supported. +- During execution, Flyde exposes the resolved instance configuration on `context.config`. + +If a configured value should also be able to become a runtime input, use `ConfigurableValue` together with helpers such as `configurableValue`, `extractInputsFromValue`, and `replaceInputsInValue`. The [Inline Value node](https://github.com/flydelabs/flyde/blob/main/nodes/src/Values/InlineValue.flyde.ts) is a good example. + +### Configuration editors + +For most macro nodes, use a structured editor: + +```ts +editorConfig: { + type: "structured", + fields: [ + { + type: "number", + configKey: "count", + label: "Output count", + }, + ], +} +``` + +Structured fields can use the supported editor types such as `string`, `number`, `boolean`, `json`, `select`, `longtext`, and `secret`. Fields can also define descriptions, numeric limits, conditions, and other type-specific options. + +If `editorConfig` is omitted from an advanced node, Flyde generates a structured editor from `defaultConfig`. + +For a fully custom editor, use: + +```ts +editorConfig: { + type: "custom", + editorComponentBundlePath: "../../dist/ui/MyNode.js", +} +``` +A custom editor bundle is a React component bundle. The bundle path is relative to the package root, and the component is expected to be exposed on `window` as `__MacroNode__{id}`. +### Examples in the standard nodes +- [Inline Value](https://github.com/flydelabs/flyde/blob/main/nodes/src/Values/InlineValue.flyde.ts) derives runtime inputs from configured values. +- [Merge to List](https://github.com/flydelabs/flyde/blob/main/nodes/src/Lists/ListFrom.flyde.ts) changes its input pins based on configuration. +- [Note](https://github.com/flydelabs/flyde/blob/main/nodes/src/Note/Note.flyde.ts) uses a fully custom editor component. +These examples are useful references for dynamic pins, configuration-aware runtime behavior, and custom configuration UIs. From 8cc5611d0e4e805a279c322485bc52e8a244d106 Mon Sep 17 00:00:00 2001 From: lelandks Date: Sun, 4 Oct 2026 16:04:45 -0600 Subject: [PATCH 2/2] docs: fix literal HTML escapes in custom node examples --- website/content/docs/5-custom-nodes.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/content/docs/5-custom-nodes.mdx b/website/content/docs/5-custom-nodes.mdx index 87ae0a0f..43e10579 100644 --- a/website/content/docs/5-custom-nodes.mdx +++ b/website/content/docs/5-custom-nodes.mdx @@ -47,7 +47,7 @@ The first argument of the `run` function is an object containing the values of t Flyde use of RxJS is _very_ simplistic and should be treated as a mere implementation detail. You don't need to know RxJS to create custom nodes. Neither should you rely on a full RxJS API being available in your custom nodes. ::: -The `run` function also accepts a third argument, which is an object containing advanced utilities to help author more complex custom nodes. One of these utilities is the `state` object, which gives the node access to state that will be available to it while the flow is running. The `state` object is `Map<string, any>`. +The `run` function also accepts a third argument, which is an object containing advanced utilities to help author more complex custom nodes. One of these utilities is the `state` object, which gives the node access to state that will be available to it while the flow is running. The `state` object is `Map`. For example, the following custom node will emit the average of all the numbers it received since the flow started: @@ -86,7 +86,7 @@ It will appear on the "Current project" section of the "nodes library -> view al Macro nodes are code nodes with **edit-time configuration**. Use them when an instance's configuration should change its pins, display text, or runtime behavior. -In the current API, define a macro node as a `CodeNode<Config>` with `mode: "advanced"`. +In the current API, define a macro node as a `CodeNode` with `mode: "advanced"`. ### Basic structure @@ -97,7 +97,7 @@ interface FanOutConfig { count: number; } -export const FanOut: CodeNode<FanOutConfig> = { +export const FanOut: CodeNode = { id: "FanOut", mode: "advanced", @@ -120,7 +120,7 @@ export const FanOut: CodeNode<FanOutConfig> = { displayName: (config) => `Fan out to ${config.count} outputs`, run: ({ value }, outputs, { context }) => { - for (let index = 0; index < context.config.count; index++) { + for (let index = 0; index < context.config.count; index++) { outputs[`item${index + 1}`].next(value); } },