> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twine.se/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate

export const NodeMode = ({mode = 'data'}) => {
  const ENGINES = {
    data: [{
      label: 'Data Engine',
      href: '/data-engine'
    }],
    flow: [{
      label: 'Flow Engine',
      href: '/flow-engine'
    }],
    any: [{
      label: 'Data Engine',
      href: '/data-engine'
    }, {
      label: 'Flow Engine',
      href: '/flow-engine'
    }]
  };
  const engines = ENGINES[mode] || ENGINES.data;
  return <>
      <style>{`
        .node-mode {
          display: flex;
          flex-wrap: wrap;
          align-items: center;
          gap: 8px;
          margin: 0 0 20px;
        }
        .node-mode-label {
          font-size: 11px;
          font-weight: 600;
          text-transform: uppercase;
          letter-spacing: 0.05em;
          color: var(--colors-content-secondary, #6b7280);
        }
        .node-mode-pill {
          display: inline-flex;
          align-items: center;
          padding: 3px 10px;
          border-radius: 999px;
          font-size: 12px;
          font-weight: 600;
          text-decoration: none;
          color: inherit;
          background: rgb(var(--primary-light) / .15);
          border: 1px solid rgb(var(--primary-light) / .5);
        }
        .node-mode-pill:hover {
          background: rgb(var(--primary-light) / .25);
        }
      `}</style>
      <div className="node-mode">
        <span className="node-mode-label">Available in</span>
        {engines.map(engine => <a key={engine.label} className="node-mode-pill" href={engine.href}>
            {engine.label}
          </a>)}
      </div>
    </>;
};

export const NodePorts = ({nodeName, inputs = [], outputs = []}) => {
  if (inputs.length === 0 && outputs.length === 0) return null;
  const formatTypes = types => {
    if (!types || types.length === 0) return 'Any';
    if (Array.isArray(types)) return types.join(', ');
    return types;
  };
  const Card = ({port, side}) => <div className={`node-port-card node-port-card-${side}`}>
      <div className="node-port-head">
        <span className="node-port-name">{port.name}</span>
        <span className="node-port-id">{port.id}</span>
      </div>
      {port.doc && <div className="node-port-doc">{port.doc}</div>}
      <div className="node-port-tags">
        <span className="node-port-tag node-port-tag-types">{formatTypes(port.types)}</span>
        {port.required && <span className="node-port-tag node-port-tag-required">required</span>}
        {port.arity === 'many' && <span className="node-port-tag">many</span>}
        {port.defaultConnection && <span className="node-port-tag node-port-tag-default">default</span>}
      </div>
    </div>;
  return <>
      <style>{`
        .node-diagram {
          display: grid;
          grid-template-columns: 1fr auto 1fr;
          gap: 32px;
          align-items: stretch;
          margin: 24px 0 32px;
        }
        .node-diagram-col {
          display: flex;
          flex-direction: column;
          gap: 12px;
          min-width: 0;
        }
        .node-diagram-col-label {
          font-size: 11px;
          font-weight: 600;
          text-transform: uppercase;
          letter-spacing: 0.05em;
          color: var(--colors-content-secondary, #6b7280);
          margin-bottom: 4px;
        }
        .node-diagram-col-center {
          display: flex;
          align-items: stretch;
          justify-content: center;
          position: relative;
          /* Push the center node down so it aligns with the cards, not the column labels */
          padding-top: 27px;
        }
        .node-diagram-node {
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 18px 14px;
          border-radius: 10px;
          background: rgb(var(--primary-light) / .15);
          border: 1.5px solid rgb(var(--primary-light) / .55);
          font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          font-size: 15px;
          font-weight: 700;
          color: inherit;
          box-shadow: 0 1px 3px rgb(0 0 0 / .04);
          writing-mode: vertical-rl;
          transform: rotate(180deg);
          letter-spacing: 0.05em;
        }
        .node-port-card {
          position: relative;
          border: 1px solid rgb(var(--primary-light) / .25);
          border-radius: 8px;
          padding: 10px 12px;
          background: rgb(var(--primary-light) / .04);
          font-size: 12px;
          line-height: 1.4;
        }
        .node-port-card::after {
          content: '';
          position: absolute;
          top: 50%;
          width: 32px;
          border-top: 1px dashed rgb(var(--primary-light) / .7);
        }
        .node-port-card-left::after {
          right: -32px;
        }
        .node-port-card-right::after {
          left: -32px;
        }
        .node-port-head {
          display: flex;
          align-items: baseline;
          justify-content: space-between;
          gap: 8px;
          margin-bottom: 4px;
        }
        .node-port-name {
          font-weight: 600;
          font-size: 13px;
          color: inherit;
        }
        .node-port-id {
          font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          font-size: 10.5px;
          color: var(--colors-content-secondary, #6b7280);
        }
        .node-port-doc {
          color: #4b5563;
          margin-bottom: 6px;
        }
        .dark .node-port-doc,
        html.dark .node-port-doc,
        :is(.dark, [data-theme="dark"]) .node-port-doc {
          color: #d1d5db;
        }
        .node-port-tags {
          display: flex;
          flex-wrap: wrap;
          gap: 4px;
        }
        .node-port-tag {
          display: inline-flex;
          align-items: center;
          padding: 2px 6px;
          border-radius: 4px;
          font-size: 10px;
          font-weight: 600;
          background: rgb(var(--primary-light) / .15);
          color: var(--colors-content-secondary, #6b7280);
          text-transform: lowercase;
          letter-spacing: 0.02em;
        }
        .node-port-tag-types {
          font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          text-transform: none;
          background: rgb(var(--primary-light) / .25);
          color: inherit;
        }
        .node-port-tag-required {
          background: var(--colors-warning-light, #fef3c7);
          color: var(--colors-warning, #b45309);
        }
        .node-port-tag-default {
          background: var(--colors-accent-light, #ede9fe);
          color: var(--colors-accent, #7c3aed);
        }
        @media (max-width: 720px) {
          .node-diagram {
            grid-template-columns: 1fr;
            gap: 16px;
          }
          .node-diagram-col-center {
            order: -1;
          }
          .node-port-card::before,
          .node-port-card::after {
            display: none !important;
          }
        }
      `}</style>
      <div className="node-diagram">
        <div className="node-diagram-col node-diagram-col-left">
          {inputs.length > 0 && <div className="node-diagram-col-label">Inputs</div>}
          {inputs.map(p => <Card key={p.id} port={p} side="left" />)}
        </div>
        <div className="node-diagram-col-center">
          <div className="node-diagram-node">{nodeName}</div>
        </div>
        <div className="node-diagram-col node-diagram-col-right">
          {outputs.length > 0 && <div className="node-diagram-col-label">Outputs</div>}
          {outputs.map(p => <Card key={p.id} port={p} side="right" />)}
        </div>
      </div>
    </>;
};

<NodeMode mode="data" />

Checks every value in the upstream stream against a named validator, and
optionally sends valid and invalid values down different paths.

Where Converter changes a value, Validate judges it. Each validator is a small
named module that answers whether a value is acceptable and, when it is not,
why.

### Inputs

* **In** — the values to check.

### Outputs

* **Valid?** — one boolean per value, with its date and ID preserved. Wire it
  into Switch's *check* port, into Assert, or into a Constant lookup.
* **Valid** — optional branch, taken when every value passed. Receives the
  input unchanged.
* **Not valid** — optional branch, taken when any value failed. Receives the
  input unchanged.
* **Reason** — optional. The validator's explanation for each value that
  failed; empty when nothing did. Wire it into an Anomaly node's parameter to
  tell the customer what was wrong.

Each branch you wire needs its own End node at the far side, the same way
Switch's branches do. Leave both branches unwired to use the node as a plain
test, and carry on from *Valid?*.

### Configuration

* **Validator** — pick one of the registered validators.
* **Options** — typed form rendered from the validator's declared params.
  Validators that declare none show no options form.
* **Treat an empty value as** — *Valid* (the default) ignores a value that is
  missing or blank, so a cleared field is not reported as bad data. *Not valid*
  fails it with the reason "Value is empty".

### Tips

* The branch decision covers the whole timeline: one bad value in a field's
  history sends the entire input down *Not valid*. *Valid?* still reports each
  value separately, so use Switch on that port when you need per-value routing.

## Ports

<NodePorts
  nodeName="Validate"
  inputs={[
{ id: 'default_in', name: 'In', doc: 'Default input port', types: 'Any', required: true, defaultConnection: true },
]}
  outputs={[
{ id: 'default_out', name: 'Valid?', doc: 'One boolean per checked value, with its date and ID preserved.', types: 'Boolean', required: true, defaultConnection: true },
{ id: 'invalid', name: 'Not valid', doc: 'Branch taken when any value failed, receiving the input unchanged. Its subgraph runs once and must end at an End node.', types: 'Any' },
{ id: 'reason', name: 'Reason', doc: 'The validator\'s explanation for each value that failed. Empty when nothing failed.', types: 'String' },
{ id: 'valid', name: 'Valid', doc: 'Branch taken when every value passed, receiving the input unchanged. Its subgraph runs once and must end at an End node.', types: 'Any' },
]}
/>
