Skip to content
D
Documentation

Ports

reference
9 min readUpdated

Import these from @grafloria/engine.

On their own pages

Functions

arePortDataTypesCompatible

Free-function facade over the singleton — what PortModel/validators import.

ts
function arePortDataTypesCompatible(from: string | undefined, to: string | undefined): boolean

buildDynamicPortCommands

The plan, as undoable commands. Empty array when nothing is due — so a caller can drive this on every link change without polluting the undo stack.

ts
function buildDynamicPortCommands(node: NodeModel, links?: LinkModel[]): Command[]

buildDynamicPortCommandsForDiagram

Every node in the diagram that owes the allocator work.

ts
function buildDynamicPortCommandsForDiagram(diagram: DiagramModel): Command[]

canConnectPortsWithRules

Boolean facade for call sites that don't care WHY.

ts
function canConnectPortsWithRules(
  source: PortModel,
  target: PortModel,
  context: ConnectionRuleContext = {}
): boolean

ensureSideAnchorPort

The hidden port a side-anchor handle names — created once per node and handle, then re-used (a re-applied spec must not grow a second one). Placed as a FRACTION of the node box, so it stays on its side when the node resizes. Returns null when handle is not a side anchor.

ts
function ensureSideAnchorPort(node: NodeModel, handle: string): string | null

evaluatePortConnection

May a link be created FROM source TO target? Directional — swapping the arguments is a different question and may well get a different answer.

ts
function evaluatePortConnection(
  source: PortModel,
  target: PortModel,
  context: ConnectionRuleContext = {}
): ConnectionVerdict

findPortGroup

Find the group a port belongs to: the node's own definitions first (most specific), then the registry for the node's type.

ts
function findPortGroup(
  port: PortModel,
  node: NodeModel | undefined
): PortGroupDefinition | undefined

getNodePortGroups

Per-node group definitions, stored in node metadata so they serialize with the diagram (a node can carry a one-off group without registering a node type).

ts
function getNodePortGroups(node: NodeModel | undefined): Record<string, PortGroupDefinition>

isSideAnchorPort

A port a side-anchor handle created (<node>__right@36).

ts
function isSideAnchorPort(portId: string | undefined): boolean

parseSideAnchor

'right@36' → { side: 'right', at: { px: 36 } }; 'left@50%' → { at: { pct: 50 } }; anything else → null.

ts
function parseSideAnchor(handle: string): { side: AnchorSide; at: { px?: number; pct?: number } } | null

planDynamicPorts

What must change so every dynamic group on node offers exactly spare free ports? Idempotent: run it on a settled node and it returns an empty plan.

A port is "free" when it carries no links. Only ports the allocator itself spawned (dynamic: true) are ever REMOVED — an authored port the user simply hasn't wired yet is not surplus, it is the design.

ts
function planDynamicPorts(node: NodeModel, links?: LinkModel[]): DynamicPortPlan

portTypeColor

Free-function facade over the singleton — what the renderer imports.

ts
function portTypeColor(name: string | undefined): string | undefined

resolvePortConfig

Fold a port's group into its own fields. THE resolution seam — the renderer, the layout engine and the connection validator all read the result of this function and never the raw port fields, so group inheritance can never silently apply in one place and not another.

ts
function resolvePortConfig(port: PortModel, node?: NodeModel): ResolvedPortConfig

setNodePortGroups

ts
function setNodePortGroups(node: NodeModel, groups: Record<string, PortGroupDefinition>): void

sideAnchorPortId

The id of the port a side-anchor handle names on nodeId.

ts
function sideAnchorPortId(nodeId: string, handle: string): string

Classes

PortGroupRegistry

Groups registered per node TYPE. A node type declares its port groups once ("the and-gate type has an in group and an out group"), and every node of that type inherits them.

ts
class PortGroupRegistry

Methods

  • register(nodeType: string, group: PortGroupDefinition): void
  • registerAll(nodeType: string, groups: PortGroupDefinition[]): void
  • get(nodeType: string, groupId: string): PortGroupDefinition | undefined
  • getAll(nodeType: string): PortGroupDefinition[]
  • unregister(nodeType: string, groupId?: string): void
  • clear(): void

PortTypeRegistry

ts
class PortTypeRegistry

Methods

  • register(definition: PortDataTypeDefinition): void
  • registerAll(definitions: PortDataTypeDefinition[]): void
  • get(name: string): PortDataTypeDefinition | undefined
  • has(name: string): boolean
  • unregister(name: string): void
  • clear(): void
  • isCompatible(from: string | undefined, to: string | undefined): boolean — May a link carry from into to?

Compatibility is DIRECTIONAL on purpose: int → float is a widening that a host may well allow while float → int is a lossy one it may not.

  • colorFor(name: string | undefined): string | undefined — The glyph colour for a data type, if one was registered.

Constants

ANY_PORT_TYPE

The wildcard: a port typed * (or declared compatible with *) fits anything.

ts
const ANY_PORT_TYPE: "*"

DEFAULT_PORT_GATING

ts
const DEFAULT_PORT_GATING: ResolvedPortGating

DEFAULT_PORT_LABEL_OFFSET

ts
const DEFAULT_PORT_LABEL_OFFSET: 6

DEFAULT_PORT_SPREAD_SPACING

ts
const DEFAULT_PORT_SPREAD_SPACING: 10

PORT_GROUPS_METADATA_KEY

Node metadata key holding per-node group definitions.

ts
const PORT_GROUPS_METADATA_KEY: "portGroups"

portGroupRegistry

The process-wide registry. Hosts register node-type groups at bootstrap.

ts
const portGroupRegistry: PortGroupRegistry

portTypeRegistry

The process-wide registry. Hosts register their data types at bootstrap.

ts
const portTypeRegistry: PortTypeRegistry

Interfaces

ConnectionRuleContext

ts
interface ConnectionRuleContext

Properties

NameTypeDefaultDescription
sourceNode?NodeModel
targetNode?NodeModel
links?LinkModel[]The diagram's links — needed for the duplicate-link rule.
rejectDuplicatesByDefault?booleanReject a second link between the same ordered port pair EVEN when the ports allow duplicates. Proximity-connect passes this (auto-linking a duplicate on a drag-near is never what the user meant); the explicit connection drag does not, preserving its historical permissiveness.
validators?Array<(source: PortModel, target: PortModel) => boolean>Extra host rules (connection groups, ACLs…). Run last.

ConnectionVerdict

ts
interface ConnectionVerdict

Properties

NameTypeDefaultDescription
okboolean
reason?ConnectionRejectionReason
message?stringHuman-readable, safe to surface in a tooltip / live region.

DynamicPortPlan

ts
interface DynamicPortPlan

Properties

NameTypeDefaultDescription
addPortModel[]Ports to create, in order.
removestring[]Ids of surplus free ports to retire.

DynamicPortSpec

Dynamic auto-ports: keep a group topped up with free ports so the user always has somewhere to drop the next link — the node-editor pattern (Blender / Unreal / n8n).

ts
interface DynamicPortSpec

Properties

NameTypeDefaultDescription
enabledboolean
spare?numberHow many UNCONNECTED ports the group must always offer. Default 1.
max?numberHard cap on total ports in the group. 0 (default) = uncapped.
idPrefix?stringId prefix for spawned ports. Default <groupId>-.

PortDataTypeDefinition

ts
interface PortDataTypeDefinition

Properties

NameTypeDefaultDescription
namestringThe type's own name.
compatibleWith?string[]Types this one may ALSO connect to (beyond an exact name match). '*' means "compatible with everything" — the escape hatch for an any port.
color?stringAffordance: the glyph colour for ports of this type.

PortGatingSpec

Directional connectability.

Every field is optional and every default reproduces the old behaviour: isConnectableStart/End = true, from/toMaxLinks = null (unlimited), allowSelfLink = false, allowDuplicateLinks = true.

ts
interface PortGatingSpec

Properties

NameTypeDefaultDescription
isConnectableStart?booleanMay a link START here? Default true.
isConnectableEnd?booleanMay a link END here? Default true.
fromMaxLinks?number | nullCap on OUTGOING links. null/undefined = unlimited.
toMaxLinks?number | nullCap on INCOMING links. null/undefined = unlimited.
maxConnections?number | nullCap on links in EITHER direction (the legacy knob). null = unlimited.
allowSelfLink?booleanAllow a link whose source node IS its target node. Default false.
allowDuplicateLinks?booleanAllow a SECOND link between the same ordered pair of ports. Default true.
allowedTypes?string[]Restrict which port data-types / system-types may attach. Empty = no restriction.

PortGroupDefinition

A named, reusable bundle of port config on a node type. Ports name their group and override ONLY what differs — replacing the old top/right/bottom/left-only PortsConfig, in which "eight typed inputs down the left edge, each with a label" could not be said at all.

ts
interface PortGroupDefinition

Properties

NameTypeDefaultDescription
idstring
side?PortEdgeDefault side for members that don't declare one.
layout?PortLayoutSpec
shape?PortShapeSpec
style?Record<string, unknown>Raw SVG presentation attributes merged onto the glyph (fill, stroke, …).
label?Partial<PortLabelSpec>Label defaults. Members supply/override text.
visibility?PortVisibilityMode
gating?PortGatingSpec
type?'input' | 'output' | 'bi'Default port direction (input/output/bi) for members.
dataType?string
fromSpot?PortSpot
toSpot?PortSpot
spread?PortSpreadSpec
dynamic?DynamicPortSpec

PortLabelSpec

ts
interface PortLabelSpec

Properties

NameTypeDefaultDescription
textstring
layout?PortLabelLayoutDefault 'outside'.
offset?numberGap from the glyph EDGE (not its centre), in px. Default 6.
angle?numberExtra rotation applied to the label, in degrees. Default 0.
keepUpright?booleanAuto-flip a label whose total rotation would leave it upside-down (|angle| > 90°) by adding 180°, so text always reads left-to-right. Default true.
fontSize?number
fontFamily?string
fontWeight?number | string
color?string
maxWidth?numberWrap width for the shared text-block engine.
className?string
noNudge?booleanOpt OUT of collision-aware nudging (see port-label.ts). Default false — i.e. crowded labels ARE nudged apart by default.

PortLayoutSpec

ts
interface PortLayoutSpec

Properties

NameTypeDefaultDescription
strategyPortLayoutStrategyName
args?PortLayoutArgs

PortShapeSpec

ts
interface PortShapeSpec

Properties

NameTypeDefaultDescription
shapePortGlyphShape
size?numberFull width AND height of the glyph box, in px. For a circle this is the DIAMETER (so size: 12 === the legacy portDefaultRadius: 6). Omit to inherit InteractionConfig.portDefaultRadius * 2.
width?numberNon-square glyphs: override one axis. Falls back to size.
height?number
path?stringshape:'path' only — SVG path data, centred on (0,0) in a size box.
rotation?numberRotate the glyph about its own centre, in degrees.

PortSpot

ts
interface PortSpot

Properties

NameTypeDefaultDescription
spotPortSpotName
direction?PortEdgeThe direction a link LEAVES (fromSpot) or APPROACHES (toSpot) the port. Defaults to the port's side — i.e. the outward normal — which is what the orthogonal router has always been handed.
distance?numberPush the attachment point this many px further along direction.

PortSpreadSpec

Spread N links landing on ONE port along that port's edge instead of piling them all on the centre point.

(Byte-stability.)

ts
interface PortSpreadSpec

Properties

NameTypeDefaultDescription
enabledboolean
spacing?numberGap between adjacent lanes, in px. Default 10.
max?numberCap the number of distinct lanes; links beyond the cap reuse the outermost lane. 0 (default) = uncapped.

ResolvedPortConfig

ts
interface ResolvedPortConfig

Properties

NameTypeDefaultDescription
sidePortEdge
layout?PortLayoutSpec
shape?PortShapeSpec
styleRecord<string, unknown>
label?PortLabelSpec
visibility?PortVisibilityMode
gatingResolvedPortGating
dataType?string
fromSpot?PortSpot
toSpot?PortSpot
spread?PortSpreadSpec
dynamic?DynamicPortSpec
groupId?stringThe group this resolved from, if any.

ResolvedPortGating

Gating with every question ANSWERED — no undefined anywhere, because a validator that has to ask "was this unset or set to false?" is a validator with a bug waiting in it. null is the explicit "unlimited" for the caps.

ts
interface ResolvedPortGating

Properties

NameTypeDefaultDescription
isConnectableStartboolean
isConnectableEndboolean
allowSelfLinkboolean
allowDuplicateLinksboolean
fromMaxLinksnumber | null
toMaxLinksnumber | null
maxConnectionsnumber | null
allowedTypesstring[]

Types

AnchorSide

Also has every member of String, listed on its own entry.

ts
type AnchorSide = 'top' | 'right' | 'bottom' | 'left';

ConnectionRejectionReason

Also has every member of String, listed on its own entry.

ts
type ConnectionRejectionReason =
  | 'self-port'
  | 'self-link'
  | 'not-connectable-start'
  | 'not-connectable-end'
  | 'node-not-connectable'
  | 'direction'
  | 'data-type'
  | 'allowed-types'
  | 'max-connections'
  | 'from-max-links'
  | 'to-max-links'
  | 'duplicate-link'
  | 'connection-group'
  | 'custom';

PortEdge

Also has every member of String, listed on its own entry.

ts
type PortEdge = 'left' | 'right' | 'top' | 'bottom';

PortGlyphShape

Also has every member of String, listed on its own entry.

The port's rendered marker.

path renders a caller-supplied SVG path (PortShapeSpec.path), authored in a box of size centred on the port's anchor point.

ts
type PortGlyphShape = 'circle' | 'square' | 'diamond' | 'triangle' | 'path';

PortLabelLayout

Also has every member of String, listed on its own entry.

Where a port's label sits relative to the glyph.

  • inside — pulled INTO the node body, opposite the port's outward normal.
  • outside — pushed AWAY from the node, along the outward normal (default).
  • orthogonal — offset perpendicular to the outward normal (reads along the edge, so a column of side ports doesn't stack labels on the same line).
  • radial — offset along the ray from the node's centre through the port; the layout that actually works on ellipse / circle nodes, where "outward normal" and "away from centre" are the same thing only at the four cardinal points.
ts
type PortLabelLayout = 'inside' | 'outside' | 'orthogonal' | 'radial';

PortLayoutStrategyName

Also has every member of String, listed on its own entry.

Named port-layout strategies. Every other strategy is an explicit opt-in that overrides it.

ts
type PortLayoutStrategyName =
  | 'shape'
  | 'absolute'
  | 'line'
  | 'sideLinear'
  | 'ellipse'
  | 'ellipseSpread';

PortSpotName

Also has every member of String, listed on its own entry.

A named point on the port's glyph box. default means "whatever the port's side implies" — the outward-facing edge midpoint, which is the historical attachment behaviour (the glyph CENTRE, since the legacy glyph had no box).

ts
type PortSpotName =
  | 'default'
  | 'center'
  | 'top'
  | 'right'
  | 'bottom'
  | 'left'
  | 'topLeft'
  | 'topRight'
  | 'bottomLeft'
  | 'bottomRight';

PortVisibilityMode

Also has every member of String, listed on its own entry.

ts
type PortVisibilityMode = 'always' | 'on-hover' | 'never' | 'hidden';

Was this page helpful?

Ports — Grafloria