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.
7.1 Scalars and any
Section titled “7.1 Scalars and any”A Flow-Like data pin has a base data type. FlowScript gives the current built-in types these canonical spellings:
| FlowScript | Kind of value | Typical examples |
|---|---|---|
string | Text | names, messages, URLs, identifiers |
int | Whole number | retry counts, status codes, quantities |
float | Decimal number | scores, prices, measurements |
bool | Boolean | enabled flags and decisions |
Date | Date or timestamp | observation and scheduling times |
Path | Flow-Like storage path | files and managed object-store locations |
bytes | Binary data | encoded files, hashes, protocol payloads |
Struct | Structured JSON-like value | API responses, records, configuration objects |
any | Generic data value | a 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 = 3const confidence = 0.95const productionStopped = trueDate, 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.
7.2 Value shapes
Section titled “7.2 Value shapes”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: stringconst 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:
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.
7.3 Inference and its boundaries
Section titled “7.3 Inference and its boundaries”FlowScript infers a top-level variable type only from an unambiguous literal:
| Literal | Inferred declaration type |
|---|---|
"hold" | string |
3 | int |
3.0 | float |
true or false | bool |
{"system":"orders"} | Struct |
[1,2] | any[] |
null | none; add an annotation |
The renderer removes a scalar annotation when the literal already proves the same type:
const retries: int = 3becomes the canonical form:
const retries = 3It keeps an annotation that adds information. These declarations have different types:
const threshold = 1const thresholdAsFloat: float = 1The 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 = nulland requires an explicit type. A top-level Board variable may omit its persisted default:
const owner: stringThis 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.
7.4 Interfaces as visible schemas
Section titled “7.4 Interfaces as visible schemas”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, andobservedAtare required fields.sourcemay be absent, and when present may contain a string ornull.affectedSystemsis an array of another named structured type.labelsis a string-keyed map whose values are strings.severitydeclares four literal alternatives in its schema rather than an unrestricted string.- the external ticket key is quoted because its hyphens prevent identifier syntax;
externalPayloadadmits 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.
7.5 Struct fields and schema propagation
Section titled “7.5 Struct fields and schema propagation”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:
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.reportlet 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.
7.6 Edges and preserved metadata
Section titled “7.6 Edges and preserved metadata”Type checks use known facts. The current behavior is:
| What Flow-Like knows | Current behavior |
|---|---|
Known source and target contracts have different base types, such as int and string | Reject the new connection and request an explicit conversion |
| Scalar versus collection shape | Reject where the declared shapes contradict one another |
| Two incompatible concrete schema contracts | Reject when the pins require those contracts to be enforced |
| A direct literal has the wrong input type | Studio 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 shape | Permit specialization or schema adoption where that node contract allows it |
| A typed Make/Break field disappears while still wired | Preserve the wire and report the stale field on the node |
| An external value violates a fact the Board did not know | Fail 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:
- parse and render the FlowScript;
- apply it with the release’s catalog and inspect the resulting Board pins and schemas; and
- 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.