Evaluate a flow-based-programming graph into a
blocks-schema UI document. Where
json-schema-to-blocks and
meta-to-blocks lower a static description, a flow
computes the document: props come from upstream nodes, children can be
generated, and re-evaluating on new data yields a new plain document for the
renderer to diff. No expression language lives in the document itself. Pure
functions, no React.
pnpm add flow-to-blocks blocks-schemaBuilt with makage and published from dist, so deep imports resolve without
an exports map:
import { flowToDocument } from 'flow-to-blocks';
import { uiNodeDefinitions } from 'flow-to-blocks/definitions';import { flowToDocument } from 'flow-to-blocks';
const document = await flowToDocument(graph, {
inputs: { schema: taskInputSchema },
props: { locale: 'en' }
});graph is an @fbp/types graph.
Element nodes are contributed by this package, so a graph author composes them
with ordinary data nodes:
graphInput(schema) ──▶ ui:FromJsonSchema ──▶ ui:Form ──▶ graphOutput
The evaluator is lazy: only the nodes the requested output depends on run, so a graph describing many screens costs one screen to render.
| Node | Purpose |
|---|---|
ui:Page, ui:Form, ui:Grid, ui:Section, ui:Tabs, … |
Containers; children is a multi input, one slot per edge |
ui:Input, ui:Select, ui:Textarea, ui:DatePicker, … |
Field nodes, with the field props blocks-schema defines |
ui:DataTable, ui:Markdown, ui:Chart, ui:Button, … |
Screen-level blocks |
ui:Node |
Any node type, including one a host registry adds |
ui:Document |
Wrap a page in the document envelope, naming it and attaching metadata |
ui:FromJsonSchema |
Lower a JSON Schema arriving on a wire into field nodes |
Container and field lists are generated from the node types blocks-schema
exports, so the palette cannot drift from the document format.
Every element node accepts a props input, so any prop can be computed rather
than authored — that is what makes the document dynamic:
demo:rows ──▶ (props) ui:DataTable ──▶ graphOutput
bindings and actions are accepted as objects or JSON strings, since a graph
editor's prop field hands over text.
Data nodes come from the caller, so a flow can read whatever the host can read:
await flowToDocument(graph, { definitions: [...defaultDataDefinitions, myRowsDef] });defaultDataDefinitions is the evaluator's coreDefinitions +
mathDefinitions (literals, json:select, flow:branch, arithmetic); passing
definitions replaces it, and element nodes are always included.
The graph's graphOutput may carry either an element or a whole document —
flowToDocument wraps the former and passes the latter through, applying
documentId/meta overrides. A flow whose output is neither raises rather than
producing a half-valid document, and duplicate node keys (the symptom of one
flow node feeding two parents) are rejected by key.
flowToDocument(graph, options?): Promise<UIDocument>
flowToNode(graph, options?): Promise<UINode>
evaluateFlow(graph, options?): Promise<unknown>
uiNodeDefinitions: NodeDefinitionWithImpl[]
defaultDataDefinitions: NodeDefinitionWithImpl[]
buildElement({ type, inputs, props }): UINode
isElement(value): value is UINode
assertUniqueKeys(page): voidoptions: definitions, outputNode, outputPort, inputs, props,
documentId, meta.
MIT — see the repository LICENSE.