Core Concepts
/
Validation & Data Import
Validation & Data Import
Constraints compile into a state machine — the same gates validate a form keystroke and an imported spreadsheet row, and rastack import loads CSV, Excel and PDF data through them.
The generator's constraints — requiredness, datatype, maxLength, enum membership, unique, foreign keys — are not a bag of ad-hoc checks. They compile into a deterministic finite state machine (rastack/validate): each constraint becomes one gate, and a value must pass every gate, in a fixed order, to reach the accepting state.
the constraint state machine
1
pristine → present → typed → bounded → member → unique → resolved → valid
2
└─────────┴───── any gate fails ────┴─────────┘
Compiling builds the constraints: a gate is only emitted when its constraint exists, so an unconstrained optional string's machine is just pristine → present → typed → valid. Running walks the gates with a raw value, coercing as it goes ("12" → 12, "yes" → true), and halts at the first failing guard — reporting the state reached and the exact constraint that rejected the value.
always — also coerces the value
the field has a maxLength
the schema carries an enum
no duplicate in the dataset
the field is a foreign key
› Good to know
unique and resolved are dataset-level gates: they only engage when the run is given a dataset context (a seen-set, a relation resolver). A form validating a single value passes through them untouched — mirroring the server, where uniqueness and FK existence are checked against the table, not the payload.
One gate is record-level rather than per-field: when a resource declares state transitions, a change to the state field must follow a declared edge. The gate runs everywhere the per-field machine runs — the form hooks (useForm), rastack import, and the engine itself — and rejects an illegal move with the edge that was missing (status: no transition from "scheduled" to "departed"). The per-field pristine → … → valid gates are unchanged; the transition gate sits alongside them, on the record. Every form field carries its machine. getFormField(name) compiles it from the same write JSON Schema the form already validates against, and runs it against the current value on every render:
1
const
form
=
useForm
(
Airport
,
{
formValues
:
{
code
:
""
}
});
2
const
field
=
form
.
getFormField
(
"code"
);
4
field
.
constraints
;
// ["required", "string", "maxLength ≤ 3", "unique"]
5
field
.
validation
?.
state
;
// "valid" | "invalid" for the current value
6
field
.
validation
?.
path
;
// ["pristine", "present", "typed", "invalid"]
7
field
.
validation
?.
failed
;
// { gate: "bounded", constraint: "maxLength ≤ 3", … }
So a form can render exactly which gate the value stops at — with the same semantics the API enforces on write, because both compile from the same constraints. The existing validation path is unchanged; the machine is purely additive.
rastack import loads CSV, Excel (XLSX) and PDF files into the shape of a compiled resource and runs every row through the resource's machine — with the full dataset context, so in-file duplicates and dangling foreign keys are rejected with row-located errors.
1
# validate a CSV against a resource's constraints
2
rastack
import
airports.csv
--resource
airports.airport
4
# resolve foreign keys against a related file's ids
5
rastack
import
terminals.xlsx
--resource
airports.terminal
\
6
--related
airports.airport=airports.csv
8
# write the coerced valid records for seeding or POSTing
9
rastack
import
bookings.pdf
--resource
airports.booking
--out
clean.json
Source columns map onto fields by normalised name — "Airport Code", airport_code and airportCode all match — and every failure names its row, field and gate:
4
✗ row 2 code: "LHRX" is longer than 3 characters [failed at "bounded" — maxLength ≤ 3]
5
✗ row 3 code: duplicate value "LHR" [failed at "unique" — unique]
6
✗ row 4 code: code is required [failed at "present" — required]
8
✗ 1/4 row(s) valid, 3 rejected
The exit status is non-zero when any row fails (so CI can gate on it) unless --allow-invalid is passed; --report report.json writes the full validation report.
the standard CSV parser (quoted fields supported)
a dependency-free reader: shared + inline strings, numbers, cached formula results from the first worksheet
a best-effort table extractor: decodes the content streams and clusters positioned text into rows
! Warning
PDFs carry no table structure — only positioned text — so the PDF reader targets machine-generated tabular reports. Merged cells, wrapped cell text and scanned pages won't survive; export those sources to CSV or XLSX instead. Every extracted value still runs through the machine, so a misread cell is rejected with a located error, never silently imported.
✓ Tip
The machine compiles from the manifest, so run rastack compile first (or point --schema at a schema.rastack.json). Command details on the CLI Reference page. Build with AI
Every page is available as clean Markdown for LLMs and coding agents. Point one at the files below to build on top of React API Stack.
React API Stack · alpha · built to scale to millions