# Ports

Import these from `@grafloria/engine`.

## On their own pages

- [`PortLayoutArgs`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-engine-ports-portlayoutargs)

## 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `sourceNode?` | `NodeModel` |  |  |
| `targetNode?` | `NodeModel` |  |  |
| `links?` | `LinkModel[]` |  | The diagram's links — needed for the duplicate-link rule. |
| `rejectDuplicatesByDefault?` | `boolean` |  | Reject 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ok` | `boolean` |  |  |
| `reason?` | `ConnectionRejectionReason` |  |  |
| `message?` | `string` |  | Human-readable, safe to surface in a tooltip / live region. |

### `DynamicPortPlan`

```ts
interface DynamicPortPlan
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `add` | `PortModel[]` |  | Ports to create, in order. |
| `remove` | `string[]` |  | 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `enabled` | `boolean` |  |  |
| `spare?` | `number` |  | How many UNCONNECTED ports the group must always offer. Default 1. |
| `max?` | `number` |  | Hard cap on total ports in the group. 0 (default) = uncapped. |
| `idPrefix?` | `string` |  | Id prefix for spawned ports. Default `<groupId>-`. |

### `PortDataTypeDefinition`

```ts
interface PortDataTypeDefinition
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | The 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?` | `string` |  | Affordance: 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `isConnectableStart?` | `boolean` |  | May a link START here? Default true. |
| `isConnectableEnd?` | `boolean` |  | May a link END here? Default true. |
| `fromMaxLinks?` | `number \| null` |  | Cap on OUTGOING links. null/undefined = unlimited. |
| `toMaxLinks?` | `number \| null` |  | Cap on INCOMING links. null/undefined = unlimited. |
| `maxConnections?` | `number \| null` |  | Cap on links in EITHER direction (the legacy knob). null = unlimited. |
| `allowSelfLink?` | `boolean` |  | Allow a link whose source node IS its target node. Default false. |
| `allowDuplicateLinks?` | `boolean` |  | Allow 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  |  |
| `side?` | `PortEdge` |  | Default 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` |  |  |
| `layout?` | `PortLabelLayout` |  | Default 'outside'. |
| `offset?` | `number` |  | Gap from the glyph EDGE (not its centre), in px. Default 6. |
| `angle?` | `number` |  | Extra rotation applied to the label, in degrees. Default 0. |
| `keepUpright?` | `boolean` |  | Auto-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?` | `number` |  | Wrap width for the shared text-block engine. |
| `className?` | `string` |  |  |
| `noNudge?` | `boolean` |  | Opt 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `strategy` | `PortLayoutStrategyName` |  |  |
| `args?` | `PortLayoutArgs` |  |  |

### `PortShapeSpec`

```ts
interface PortShapeSpec
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `shape` | `PortGlyphShape` |  |  |
| `size?` | `number` |  | Full 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?` | `number` |  | Non-square glyphs: override one axis. Falls back to `size`. |
| `height?` | `number` |  |  |
| `path?` | `string` |  | shape:'path' only — SVG path data, centred on (0,0) in a `size` box. |
| `rotation?` | `number` |  | Rotate the glyph about its own centre, in degrees. |

### `PortSpot`

```ts
interface PortSpot
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `spot` | `PortSpotName` |  |  |
| `direction?` | `PortEdge` |  | The 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?` | `number` |  | Push 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `enabled` | `boolean` |  |  |
| `spacing?` | `number` |  | Gap between adjacent lanes, in px. Default 10. |
| `max?` | `number` |  | Cap the number of distinct lanes; links beyond the cap reuse the outermost lane. 0 (default) = uncapped. |

### `ResolvedPortConfig`

```ts
interface ResolvedPortConfig
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `PortEdge` |  |  |
| `layout?` | `PortLayoutSpec` |  |  |
| `shape?` | `PortShapeSpec` |  |  |
| `style` | `Record<string, unknown>` |  |  |
| `label?` | `PortLabelSpec` |  |  |
| `visibility?` | `PortVisibilityMode` |  |  |
| `gating` | `ResolvedPortGating` |  |  |
| `dataType?` | `string` |  |  |
| `fromSpot?` | `PortSpot` |  |  |
| `toSpot?` | `PortSpot` |  |  |
| `spread?` | `PortSpreadSpec` |  |  |
| `dynamic?` | `DynamicPortSpec` |  |  |
| `groupId?` | `string` |  | The 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**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `isConnectableStart` | `boolean` |  |  |
| `isConnectableEnd` | `boolean` |  |  |
| `allowSelfLink` | `boolean` |  |  |
| `allowDuplicateLinks` | `boolean` |  |  |
| `fromMaxLinks` | `number \| null` |  |  |
| `toMaxLinks` | `number \| null` |  |  |
| `maxConnections` | `number \| null` |  |  |
| `allowedTypes` | `string[]` |  |  |

## 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';
```
