Skip to content

7. Values, Types, Collections, and Interfaces

FlowScript types describe what may cross a visible connection. A string maps to a compatible string pin. An Incident gives a Struct pin a visible field contract. An array carries its collection shape as part of the wire.

The governing rule is simple:

Reject known incompatibilities during authoring. When reality violates an unknown assumption, fail at the first node that discovers it and make that node easy to find.

External contracts drift. Flow-Like uses the facts it has and makes the remaining uncertainty visible at runtime.

Release check: The scalar and container spellings, interface grammar, schema projection, connection diagnostics, and Make/Break behavior match the current parser, renderer, reconciler, pin-matching code, and catalog tests. Verify the complete examples and diagnostic navigation in the publication release. The final section records current schema-projection limits.

A Flow-Like data pin has a base data type. FlowScript gives the current built-in types these canonical spellings:

FlowScriptKind of valueTypical examples
stringTextnames, messages, URLs, identifiers
intWhole numberretry counts, status codes, quantities
floatDecimal numberscores, prices, measurements
boolBooleanenabled flags and decisions
DateDate or timestampobservation and scheduling times
PathFlow-Like storage pathfiles and managed object-store locations
bytesBinary dataencoded files, hashes, protocol payloads
StructStructured JSON-like valueAPI responses, records, configuration objects
anyGeneric data valuea boundary whose concrete data type is not yet known

Execution pins are typed control connections. They answer “what runs next?” rather than “what data is this?”

The first four types have direct literal forms:

const service = "orders"
const retries = 3
const confidence = 0.95
const productionStopped = true

Date, Path, and bytes often come from catalog nodes or typed boundaries. A quoted timestamp remains a string; use an explicit conversion before date operations.

bytes represents one raw byte buffer on a Normal pin. bytes[] would be an array of buffers, not the usual representation of one file’s contents.

Struct means an object-shaped value and may omit field information. That suits open boundaries such as webhook payloads, but gives Flow-Like less information than a named interface.

any is the textual spelling of a Generic pin. Its base type may specialize when connected to a concrete type. Generic array utilities, selectors, and external boundaries sometimes need this.

Use any where uncertainty is real. A concrete type improves catalog search, rejects incompatible wires, and exposes fields. An any value often needs inspection or conversion at runtime.

A schema-aware Struct can use Make Struct (Schema) and Break Struct with visible field pins. An open Struct relies on generic Get Field and Set Field operations with string keys, which are harder to inspect and govern.

Flow-Like records both the base type and value shape:

  • Normal means one value.
  • Array means an ordered collection.
  • Map means values addressed by string keys.
  • Set means a collection of unique values.

FlowScript renders those shapes in familiar syntax. Top-level Board variables may intentionally have no persisted default, as in this type-focused example:

const report: string
const reports: string[]
const incidentsById: Map<string, Struct>
const affectedServices: Set<string>

Maps use string keys and one value type. Sets have one element type. Ordinary declaration and function-boundary types currently wrap one base type in one collection shape.

Shape affects compatibility. string, string[], Struct, and Struct[] are distinct contracts. A Generic pin may specialize its base type, but connection planning still protects its declared scalar or collection shape.

A node producing Incident[] needs a selector or iterator before a consumer expecting one Incident. This example uses For Each:

Inspect Incidents event passing a typed incidents array into For Each, with each Struct value feeding Get Field and Print Info.
The collection wire ends at For Each; only its per-item Value output crosses into the scalar Incident path.

Studio rejects a direct connection because Incident[] and Incident have different shapes. A selector or iterator must choose an element first.

Array literals exist, but top-level defaults use compact canonical JSON, and an unannotated array default only establishes any[] today:

const names: any[] = ["api","billing"]
const typedNames: string[] = ["api","billing"]

The first declaration promises only an array. The second keeps the element type visible. Empty arrays also need an annotation when their element type matters.

An object literal creates a Struct. Struct fields may have different types; Map values share one type under arbitrary string keys. The distinction decides between named field pins and key lookup.

FlowScript has array and JSON object literals. Maps and sets are normally built through catalog nodes, so construction remains visible on the Board.

FlowScript infers a top-level variable type only from an unambiguous literal:

LiteralInferred declaration type
"hold"string
3int
3.0float
true or falsebool
{"system":"orders"}Struct
[1,2]any[]
nullnone; add an annotation

The renderer removes a scalar annotation when the literal already proves the same type:

const retries: int = 3

becomes the canonical form:

const retries = 3

It keeps an annotation that adds information. These declarations have different types:

const threshold = 1
const thresholdAsFloat: float = 1

The first is an integer; the second is a float with a whole-number default. Structured and array defaults keep annotations so their intended shapes remain visible.

null carries no future value type, so FlowScript rejects this declaration:

const owner = null

and requires an explicit type. A top-level Board variable may omit its persisted default:

const owner: string

This declares an unset default. It does not make null valid on every string pin. Interface fields can spell nullability as string | null; ordinary variable and boundary types currently use the simpler base-plus-container form.

Top-level inference stops at literals. This call cannot initialize persisted configuration:

const normalized = normalizeReport()

A top-level Board variable stores configuration. Node calls belong inside functions and events, where catalog declarations and pins supply output types.

Studio checks candidate wires, and FlowScript reconciliation resolves calls against the live catalog. A known int to string mismatch, collection-shape conflict, or enforced-schema conflict blocks the new edge with a conversion diagnostic. Apply does not yet prove every direct literal against its target contract.

Execution catches unknown changes. If an external service starts returning a numeric customer ID without updating its contract, the first typed node unable to consume it fails. That may be a conversion or a later consumer after an open field lookup produced null. Logs remain attributed to the node that discovered the problem.

A named interface turns an anonymous Struct into a readable field contract. Here is a more complete incident record:

interface AffectedSystem {
name: string;
region?: string | null = null;
}
interface IncidentRecord {
id: string;
severity: "critical" | "high" | "medium" | "low";
report: string;
source?: string | null = null;
affectedSystems?: AffectedSystem[] = [];
labels?: Map<string, string> = {};
observedAt: Date;
"external-ticket-id"?: string | null = null;
externalPayload?: any = null;
}
eventsGeneric inspectIncident(payload: Struct, incident: IncidentRecord) {
let report = incident.report
}

The declaration records each field’s contract:

  • id, severity, report, and observedAt are required fields.
  • source may be absent, and when present may contain a string or null.
  • affectedSystems is an array of another named structured type.
  • labels is a string-keyed map whose values are strings.
  • severity declares four literal alternatives in its schema rather than an unrestricted string.
  • the external ticket key is quoted because its hyphens prevent identifier syntax;
  • externalPayload admits uncertainty exactly where it enters, without weakening the rest of the record.

Optionality, nullability, and defaults remain separate:

source?: string | null = null;

The ? makes the property optional. | null allows null when it exists. = null records a schema default. A field may be required and nullable, or optional and non-null when supplied.

Inside interfaces, the current surface supports named types, nested arrays, string-keyed maps, unions, string-literal alternatives, null, and any. Parentheses disambiguate an array whose element is itself a union:

interface Evidence {
observations?: (string | null)[] = [];
}

Without parentheses, string | null[] means either one string or an array of nulls.

The parser turns interfaces into JSON Schema metadata and carries referenced definitions. An IncidentRecord boundary becomes a Struct pin with that specific schema attached.

IncidentRecord describes fields on a wire. It creates no constructor, prototype, or arbitrary methods. Method syntax still resolves to a catalog node or FlowScript function with a compatible receiver.

Typing a Struct changes what the canvas can show.

An open Struct reveals no keys. Generic Get Field and Set Field nodes therefore need a string such as "report", which may remain plausible after the remote field is renamed.

Given IncidentRecord, the schema can drive field-shaped nodes instead:

Typed incident Struct connected to Break Struct, which exposes Id, Report, and ObservedAt output pins.
Break Struct adopts the IncidentRecord schema and turns its three fields into visible, typed outputs.

Break Struct adopts the producer schema and creates the most specific output pin it can derive for each property. Make Struct (Schema) exposes field inputs from the consumer schema and produces the Struct. Field selection stays visible instead of relying on unchecked string keys.

FlowScript can render that Board logic compactly:

let report = incident.report
let ticketId = incident["external-ticket-id"]

Identifier-like fields use dot syntax. Other property names use bracketed strings. Numeric or dynamic brackets remain collection indexes, so incidents[0] and incident["external-ticket-id"] have different meanings.

Depending on the live Board, the renderer may describe a Break Struct output or a catalog field-access node. Apply must resolve the expression to a compatible Board operation. Chapter 9 follows that lowering.

Schema propagation keeps field information after the first wire. Get Element and For Each carry a typed array’s item schema, allowing downstream Break Struct nodes to expose fields. Nested Structs receive sub-schemas where derivation is possible. A generic passthrough preserves any concrete schema.

The current Make/Break implementation preserves a wired field pin when a new schema removes that field. The stale pin and connection remain, new fields appear, and the node reports the undeclared connection. The author can then insert a conversion or choose the renamed field without rebuilding a vanished wire.

Interface changes are handled according to the information available at each boundary:

  • a new optional field may require no repair at an open or schema-adopting boundary, although two enforced whole-schema contracts can still compare unequal;
  • a known type or schema contradiction is rejected before a new edge is applied;
  • a removed field that is still wired is retained and reported rather than silently erased; and
  • an unannounced external change can only be discovered at runtime, where the first node unable to consume the changed value exposes it.

The repair should appear where the change first becomes knowable.

Type checks use known facts. The current behavior is:

What Flow-Like knowsCurrent behavior
Known source and target contracts have different base types, such as int and stringReject the new connection and request an explicit conversion
Scalar versus collection shapeReject where the declared shapes contradict one another
Two incompatible concrete schema contractsReject when the pins require those contracts to be enforced
A direct literal has the wrong input typeStudio may warn, but Apply does not yet validate every literal; the consumer may reject it at runtime
One side is genuinely Generic or declares an open Struct shapePermit specialization or schema adoption where that node contract allows it
A typed Make/Break field disappears while still wiredPreserve the wire and report the stale field on the node
An external value violates a fact the Board did not knowFail at runtime with node-attributed error evidence

any marks a missing base-type fact while preserving collection rules. An open Struct says its fields are unfixed; it does not match an unrelated concrete schema. Make and Break boundary pins are special because they adopt a connected Struct schema.

Schema compatibility does not provide full structural subtyping. Two enforced concrete schemas generally require canonical equality, except at schema-adopting boundaries. A three-field record cannot be assumed compatible with a two-field input. The node declares whether its schema is descriptive, enforced, or open.

The Board stores JSON Schema text or a reference to it. FlowScript projects the readable portion: object properties, requiredness, supported defaults, named definitions, arrays, string-keyed maps, supported unions and literals, null, any, and date or date-time formats.

Validation bounds, nested descriptions, pattern properties, and compositions such as oneOf or allOf may exceed that projection. The legacy @schema("…") decorator preserves an object schema that no named interface can represent, though it is hard to read.

When a Board contains a richer live schema, the renderer can show its interface projection while the reconciler retains the exact schema through an unchanged Board-to-source-to-Board round trip. A new standalone text file may not recreate hidden constraints omitted from that projection.

Top-level and function or event references currently support Normal, array, Map<string, T>, and Set<T> shapes around one base type. Interface fields also allow nested arrays, maps, sets, and unions. A Set projects to a JSON Schema array with uniqueItems: true.

Make/Break inference is narrower: an enum-only literal field may become Generic, and a map-shaped property may become an object-shaped Struct. The root interface remains intact, though derived field pins can be less precise. These are release observations.

An unresolved top-level, function, or event type name can currently fall back to a schema-less Struct, so a misspelled interface is not guaranteed to fail. Run publication examples through catalog-aware reconciliation; parser acceptance does not prove nominal type safety.

For publication, any example near these edges should pass three checks against the named release:

  1. parse and render the FlowScript;
  2. apply it with the release’s catalog and inspect the resulting Board pins and schemas; and
  3. render that Board back to FlowScript and compare the meaningful contract.

If a shape fails, report the smallest source, release, expected metadata, and actual result. Keep the limitation tied to that release.

These contracts guide authoring and repair on both the canvas and in FlowScript. Chapter 8 uses them to search and call the node catalog.