Core data model: tree nodes, entries, and schema variants
At a glance
- A mapping is a tree of
TreeNodes: 6 leafEntryTypes plus the one container type,Map. - Nodes are always built via
createUnregisteredItem/createUnregisteredMap— never by hand. SchemaVariantis the enum that picks the file type — it drives bothnode-logicandfile-viewer.- A node's
keymust avoid thereservedNamesset, or generated code silently shadows a runtime value.
packages/types has no runtime logic of its own to speak of — its job is to define the shapes that every other package (node-logic, node-inputs, plugins, schema-serialization, file-viewer) agrees on. This page walks through the central pieces: the tree that represents a mapping, and the schema metadata that wraps it.
Tree nodes: what a mapping is made of
A template's mapping is a tree. Every node in it is one of the EntryType variants: Boolean, Simple, Graph, Internal, Cell, Code, or Map. Map is the odd one out — it's a container (it has children), while the other six are leaf values. That split is captured directly in the types: EntryItemType is EntryType minus Map, and is used anywhere a field should only accept a leaf, not a container (see allowedTypes below).
Every leaf node shares a common shape, BaseTreeNode — an id/key/name, a Zod validation schema, a computed value, a parentId, and a dependencies record (dependsOn / requiredBy, both Set<string>) used to track which other nodes this one relies on. The six leaf variants ( TreeCodeItem, TreeGraphItem, TreeBooleanItem, TreeSimpleItem, TreeCellItem, TreeInternalItem) each intersect BaseTreeNode with a type tag and whatever extra field that variant needs — TreeCellItem adds a spreadsheet column, TreeGraphItem adds a graphId, and so on. Their union is TreeItem.
TreeMap, the container variant, is different: instead of a single value it has children, a forwardMap/reverseMap pair of Record<string, string> for looking up children by key in either direction, and an outputType (one of 'unwrap', 'group', 'array', 'forEach', 'data', 'code') that determines how its children get combined into output. formerOutputType sits alongside it, presumably to detect a change from the previous value — the source doesn't show anything reading it.
Open question
Is formerOutputType on TreeMap read anywhere, or is it write-only bookkeeping for now? Nothing found in packages/ reads it — if you know the answer, this is the line to fix.
TreeNode is the full union, TreeItem | TreeMap — this is the type most other packages import when they mean "a node in the tree" (plugins' framework, for instance, operates on TreeNode).
Building tree nodes
You don't construct a TreeItem or TreeMap by hand — the id and most of BaseTreeNode get filled in at registration time. Before that, a node is an UnregisteredTreeItem<T>: the same shape with id and the rest of BaseTreeNode's keys stripped off.
Two factories build these unregistered nodes:
createUnregisteredMaptakes aCreateMapItemand returns a bareTreeMap(minusid) with emptyforwardMap/reverseMapandvalidation: z.any().createUnregisteredItemtakes anEntryItemTypeplus aCreateEntryItem, builds the commonBaseTreeNodefields, then usests-pattern's exhaustivematchon the type to fill in the type-specific fields (aBooleanitem gets itsvaluecast to boolean, aCellitem getscolumn: undefined, and so on). Thematch(...).exhaustive()means adding a newEntryTypewithout updating this function is a compile error, not a silent gap.
For the "is this node a...?" side, the type guards (isTreeItem, isTreeMap, isTreeGraphItem, isTreeSimpleItem, isCodeOutputMap) all check the type (or, for isCodeOutputMap, type plus outputType === 'code') rather than using instanceof, since these are plain data objects, not classes.
Schema variants and schema metadata
SchemaVariant — Pdf, Table (value 'xls/csv'), Xml, Text, Email, Json — is the one enum almost every package touches. It picks which node-logic rule set applies to a node, and which file-viewer organism renders the source file.
A schema itself (the persisted, top-level record a template is built from) is described by SchemaListItem — id, version, domain/type/variant, a free-form definition, and a SchemaStatus ('draft' | 'active' | 'archived'; only 'active' schemas are usable). CreateSchemaInput and UpdateSchemaInput are the create/patch variants of the same shape.
SchemaSource (File/Email/Http) and EdiType (POST/GET/TokenBased) describe where an inbound file comes from when it's not a manual upload. ediOptionInputs maps each EdiType to the credential fields the UI should render for it (user/password, plus an internal-flagged OAuth field for GET, plus an endpoint field for TokenBased) — each field's schemaVariable is the runtime variable name (e.g. $ediUser) the value gets injected under.
Reserved names
Templates compile down to expressions evaluated against a runtime scope that has certain identifiers pre-injected (data, db, RegExp, emailMessage, ...). reservedNames is the full set of those identifiers.
Silent failure mode
A node's key must not collide with a name in reservedNames. Nothing enforces this at the type level — a colliding key doesn't error, it silently shadows the runtime value instead of referencing it, producing a template that looks fine until it runs.
reservedWrapVariables is a smaller, separate map for identifiers that need to be rewritten to a function call rather than simply reserved (currently just $xlsColumn). ReservedNodeType is a related but distinct enum — node types ($data, $declareVariable, #iterator, #repeatItems, #parseInt) reserved for the tree structure itself, not the runtime variable scope.
The graph-facing wrapper
Everything above describes a node's data. packages/types/src/graph describes how a node is presented and connected in the graph view:
Port/Ports— a node's input and output connection points, each with aPortDirectionand an optionalPortValueKind(string/number/boolean/object/array/any).GraphOperation— what a graph-editing gesture does:Replace,Insert,Add, orDependency.OptionType/OptionEntry<T>— how one configuration option's value is sourced: user-entered, a reference to a sibling/parent entry, a graph, code, or a boolean; wrapped with anOptionVisibilityflag for whether it shows in the node's Quick View.NodeData<T>— the container actually stored on a graph node:ui(Quick View open/closed),graph(dependencies+ports), andlogic.options(aNodeOptionsWrapper<T>, i.e. oneOptionEntryper key of the node's logic-block options — see Logic & structure blocks).
The Input enum
One more enum lives in packages/types: Input. It's the registry of form-input kinds a node's configuration panel can use. types only defines the enum; the components it maps to live in node-inputs — see Input widget registry.