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

# Switch

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" />

Routes the test value into one of several branches based on a check, with an
optional fallback for "none of the above".

Per dated property in the test value, Switch picks the first branch whose
check conforms with the value, or falls through to the fallback. Pair with
Each when downstream branches should iterate inside a list.

### Inputs

* **Test Value** — the dated property (or list of them) to route.

### Outputs

* **Check** — connect one or more nodes whose result decides the branch.
  The matched branch always carries the **original test value** onward (with
  its `valid_from` intact), not the check node's own output.
* **Fallback** (optional) — taken when no Check conforms. If a value matches
  no Check and no Fallback is wired, evaluation fails — a value with nowhere
  to go is a misconfiguration, not a silent drop.
* **Empty** — taken when the test input is an empty timeline. Wire a
  subgraph here to emit a default value when the upstream has no data.
  If unwired, Switch emits an empty timeline directly.
* **Result** — the merged result after routing every dated property.

### How conformity is decided

* *Value* nodes (*Constant*, *Attribute*, *Date Today*, arithmetic, …)
  conform when their output **equals** the test value.
* *Date range* checks (a *Constant* typed `date range`, or a *Date Range*
  node) conform when the test value falls inside the period — bounds
  inclusive, an empty bound meaning open-ended. A range test value conforms
  when the two ranges overlap.
* *Predicate* nodes (*Equals*, *Match*, *Greater Than*, *Less Than*, …)
  conform when their output is `true`.
* An exact value match wins over a passing predicate. More than one match in
  the winning tier is nondeterministic and errors. A check whose subgraph
  errors simply doesn't match.

Date-range checks count as value matches, so overlapping ranges on one Switch
are an error rather than a first-one-wins race. Wire the catch-all to
*Fallback* instead of adding a range that covers everything.

## Ports

<NodePorts
  nodeName="Switch"
  inputs={[
{ id: 'default_in', name: 'In', doc: 'Default input port', types: 'Any', required: true, defaultConnection: true },
{ id: 'test', name: 'Test Value', doc: 'The dated property (or collection of dated properties) to route.', types: 'Any' },
]}
  outputs={[
{ id: 'check', name: 'Check', doc: 'Connects to a Constant, Attribute, Equals, or Match node whose result is compared against the test value.', types: 'Any' },
{ id: 'default_out', name: 'Result', doc: 'The collected result after every dated property has been routed and evaluated.', types: 'Any', required: true },
{ id: 'empty', name: 'Empty', doc: 'Branch taken when the test value is an empty timeline (no DPs). When unwired, Switch emits an empty timeline directly without firing any branch. Useful for emitting a sensible default when an upstream attribute is missing.', types: 'Any' },
{ id: 'fallback', name: 'Fallback', doc: 'Branch taken when no `check` connection conforms with the test value. Optional, but a value that matches no check and has no fallback raises at evaluation time.', types: 'Any' },
]}
/>
