GestaltBI la forma all’origine del significato

editor

@gestaltbi/editor

A process graph, read as a graph: typed nodes, laid out, validated, and written back intact.

Renders nothing, like the rest of GestaltBI. It works out the picture — what each node is for, where it sits, what is wrong with it — and the host draws it: as SVG for a read-only view, or on a node canvas for an editable one.

Its job is to make an existing processing.json openable. Nothing has to be migrated, nothing is lost on the way back out. The canvas it exists for is the Studio, which is built on exactly this API.

npm install @gestaltbi/editor on npm →

The shape of it

import { readGraph, writeGraph } from '@gestaltbi/editor';

const graph = readGraph(processingJson);

graph.nodes     // placed at { depth, lane }, with a kind, a summary and an arity
graph.edges     // { from, to, socket }
graph.problems  // cycles, bad arity, requires that name nothing

// ...and back, without losing anything it did not model
const config = writeGraph(graph.nodes, processingJson);

Every node arrives with somewhere to be, so a host renders without solving layout — multiply by whatever spacing suits the surface and draw.

Positions are derived, never stored

Depth is the longest path from the raw frame, so a node always sits to the right of everything it reads. Lanes are ordered by the average position of each node's inputs, which keeps edges short without a full crossing-minimisation pass.

The point is what it means for a config nobody wrote in an editor. A processing.json written by hand, or generated by a build script — as the Kickstarter bundle is — opens as a readable picture straight away. Nothing has to be migrated to gain coordinates.

Nodes say what they are for

The op key tells you which class runs. kind tells you what it does to the data, which is what somebody reading a pipeline for the first time actually needs.

kindopsreads as
prepareformat, clearMake the text into dates and numbers
narrowglobalfilter, localfilterWhich rows are in play
deriveenhance, diffcalc, recognizeNew columns from existing ones
summariseaggregate, pivot, cohortFewer rows, rolled up
combinejoin, unionTwo branches back into one
relatecorrelateHow columns move together
placegeocode, geojsonifyRows onto a map
checkassertClaims the data must support
settings—Options for a view, not a stage

inputs carries the op's arity, so a canvas knows how many sockets to draw and a validator can catch a graph wired more ways than the op reads. A host with its own ops passes its own arities:

readGraph(config, { arity: { myop: 2 } });

A third of a config is not a stage

Entries named conf_* declare <code>"op": "noop"</code>, which is not a registered op. They never run; they exist so views can read their options — which measure a chart opens on, which columns a table shows, which currency a story uses.

configentriesreal stagessettings carriers
bundled sample513318
Everpix18135
Kickstarter291811

They are left off the canvas by default, because a node that cannot run is noise among nodes that do — and put back untouched when the graph is written. Ask for them with includeSettings to show them somewhere they make sense, beside the view they configure.

Problems are reported, not thrown

A config with a cycle in it is exactly the config somebody needs to look at. Refusing to draw it hands them a stack trace instead of a picture of their mistake, so readGraph always returns a graph and lists what is wrong with it.

graph.problems
// [{ severity: 'error', code: 'cycle', node: 'a',
//    message: '"a" is part of a cycle: a -> b -> a' }]
codemeans
cycleA node reads, eventually, from itself. Laid out anyway, with the loop broken
missing-requireA require names a process that is not in the config
too-many-inputsMore inputs wired than the op reads
unknown-opAn op this host has not declared &mdash; a warning, not an error

The Studio

The node canvas this package exists for. Open a config repo by its org/repo slug, or a processing.json from disk, and the graph draws itself — then add stages from a palette grouped by what they do to the data, wire them by dragging between sockets, and copy the result back out.

It is a host, not a fork: every decision about what the graph means comes from this package, which is why the editor and the read-only view inside GestaltBI cannot disagree about a config. Sockets come from each op's declared arity; problems are listed as you work; the document is the source and the canvas redraws from it.

Opening a config that already exists

An editor that quietly drops what it did not understand is one nobody can safely open a real config with. writeGraph carries through the top-level keys, every option exactly as read, the settings carriers that were never on the canvas, and the original key order — so a diff shows what you changed and nothing else.

Reference

Extracted from src/index.ts when this page was built, so it cannot drift from what the package actually exports.

Functions

layout

fn
function layout( nodes: GraphNode[], edges: GraphEdge[], problems: Problem[], ):

Place the nodes, left to right, without being told where anything goes.

Positions are derived rather than stored, which is the point: a processing.json written by hand, or generated by a build script, opens as a readable picture straight away. Nothing has to be migrated, and two people editing the same config never fight over coordinates.

Depth is the longest path from the raw frame, so a node always sits to the right of everything it reads. Lanes are then ordered by the average position of each node's inputs, which is the standard cheap way to keep edges short and mostly untangled.

readGraph

fn
function readGraph(config: ProcessLike | null | undefined, o: ReadOptions = {}): Graph

Turn a process config into a graph that can be drawn.

Nodes come back placed, so a host can render without solving layout, and problems come back listed rather than thrown: a config with a cycle in it is exactly the config somebody needs to look at, and refusing to draw it leaves them with a stack trace instead of a picture of their mistake.

writeGraph

fn
function writeGraph(nodes: GraphNode[], original?: ProcessLike | null): ProcessLike

Write a graph back into a process config.

Everything the editor did not model is carried through untouched: the top-level type, version and name keys, any settings carriers that were filtered out of the canvas, and every option on every node exactly as it was read. An editor that silently drops the parts it did not understand is an editor nobody can safely open an existing config with.

Pass the config it was read from as original. Without it the settings carriers cannot be restored, because they were never on the canvas.

Interfaces

Graph

interface
interface Graph

A whole config, ready to draw: the nodes placed, the edges between them, and everything wrong with it.

nodes: GraphNode[]
edges: GraphEdge[]
problems: Problem[]
depth: number

Number of stages, so a host can size a canvas without walking the nodes.

lanes: number

GraphEdge

interface
interface GraphEdge

One dataflow edge, and which of the target's sockets it feeds.

from: string
to: string
socket: number

Which input socket of to this feeds.

GraphNode

interface
interface GraphNode

One process, placed and described.

id: string

The process name, which is its key in processing.json.

op?: string
kind: NodeKind
summary: string

Plain-language summary. A host with translations should prefer its own.

requires: string[]

Processes this one reads, in the order it named them.

inputs: number

How many inputs the op reads. Sockets to draw.

options?: any
settings: boolean

True for an entry that carries settings rather than running.

Around a third of every shipped config: conf_* entries declaring an op that is not registered. They belong beside the view they configure, not on the canvas, and a host is expected to filter them out.

leaf: boolean

Nothing requires this, so it is something a view subscribes to.

depth: number

Layout: how far downstream, counted in stages from the raw frame.

lane: number

Layout: position within the depth, chosen to keep edges short.

OpInfo

interface
interface OpInfo

What the built-in ops are, grouped by what they do to the data.

Kept here rather than in @gestaltbi/stream because it is presentation: the engine needs to know that aggregate is a class, not that it belongs beside pivot under a heading a person would recognise. A host registering its own ops passes its own entries alongside these.

kind: NodeKind
inputs: number

How many processes it reads.

summary: string

One line, in the reader's terms.

Problem

interface
interface Problem

Something about the graph a person should be told.

severity: 'error' | 'warning'

error means it will not run. warning means it will, surprisingly.

code: 'cycle' | 'missing-require' | 'unknown-op' | 'too-many-inputs' | 'orphan'
node: string

The process it is about.

message: string

ProcessLike

interface
interface ProcessLike

The processing.json shape, as far as this package needs to know.

process: Record<string, { op?: string; require?: string[]; options?: any }>

ReadOptions

interface
interface ReadOptions

What this host knows that the package cannot assume.

arity?: Record<string, number>

How many inputs each op reads, keyed by op name.

Defaults to the built-in ops. Pass your own when the host registers extras, so a custom two-input op is drawn with two sockets instead of being reported as over-wired.

settingsOps?: string[]

Ops that exist but carry settings rather than running. Default: noop.

includeSettings?: boolean

Include settings carriers as nodes. Off by default: they are not stages.

Types

NodeKind

type
type NodeKind = | 'prepare' | 'narrow' | 'derive' | 'summarise' | 'combine' | 'relate' | 'place' | 'check' | 'settings' | 'unknown'

What a node is for, in the reader's terms rather than the engine's.

The op key says which class runs; this says what it does to the data, which is what somebody looking at a pipeline for the first time needs. Nodes of a kind can share a colour and an icon without a host knowing sixteen op names.

Constants

BUILTIN_ARITY

const
const BUILTIN_ARITY: Record<string, number>

Arity for every built-in op, in the shape {@link ReadOptions.arity} wants.

BUILTIN_OPS

const
const BUILTIN_OPS: Record<string, OpInfo>

Every op @gestaltbi/stream registers, described for a reader.

SETTINGS_OPS

const
const SETTINGS_OPS

Ops that exist to carry settings rather than to run.