FTS Structures and Types: Subject Data Shape
This chapter is about what the utility input and morphism certificate are made of: five built-in types, optionality, named states and nesting, as well as the honest boundary of what the compiler checks automatically and what it does not.
Minimal example
категория «Счета»
структура «Строка счёта»
номер является строкой
сумма является деньгами
«срок оплаты» является датой
просрочен является признаком
комментарий иногда является строкой
Structure describes only observable fields. It is not a class and does not
contain behavior — no methods, no constructor, no default value,
except иногда является, giving a field the right to be absent.
What the compiler does
Each field in canonical JSON is a pair { name, type }. type is either
one of the five built-in canonical names (Строка, Число, Дата,
Деньги, Признак), or a named state name, or a name of another
structure for nesting. Optionality is not a separate flag, but a suffix
of the type string itself: Строка | undefined. Further, it is this string that is read
by executeUtility (runtime input check), generateTypeScript (mapping to
TS) and any third-party form adapter — all three use the same field text,
not the parser tokens.
Built-in types
| FTS | Canonical Type | JavaScript |
|---|---|---|
является строкой |
Строка |
string |
является числом |
Число |
number |
является датой |
Дата |
ISO string on the boundary |
является деньгами |
Деньги |
finite number in current runtime |
является признаком |
Признак |
boolean |
иногда является … |
T | undefined |
field may be absent |
Дата at runtime check level is just a string: executeUtility
distinguishes it from Строка only in the canonical model, not at the moment
of execution, so "вчера" will pass the type check just as easily as
"2026-08-03". Parsing and validation of calendar date is the application’s task.
Деньги is also a regular finite number (Number.isFinite, without -0); for
currency precision, the production application should choose minor units
or a decimal adapter in advance, FTS does not magically fix IEEE 754.
There is also a less obvious boundary: if after “is” there is a word that
is not in this table and does not start with “state”, the compiler will not
reject it. The line валюта является Валюта compiles without errors — “Currency”
becomes a tag type, even if a structure with such a name is declared nowhere.
Checks that an arbitrary field type refers to an existing structure are absent in
validate; this distinguishes such a field from вложен объект,
where the name is simultaneously a type and (by convention, not by compiler enforcement)
a reference to another model structure.
State is not a nested structure
объект Заказ
«готов к отгрузке» является состоянием «Готов к отгрузке»
The canonical type of such a field is not a wrapper like Состояние, but literally the name
of the state itself: "type": "Готов к отгрузке". Thus, the domain of the morphism
«Готовый заказ можно отгрузить» (line если «Готов к отгрузке») coincides
with the type of the field directly, without an intermediate stage. This is precisely why replacing
the state with a structure does not work: the structure has no concept of “valid transition”, and the morphism cannot compare itself to a list of fields — they speak different languages of the canonical model, even though they live in the same JSON.
If an object really has composite data — not one state value, but several fields together — use nesting:
объект Заказ
вложен объект «Адрес доставки»
The type of such a field is the name of a nested structure (“Shipping Address”). The compiler
does not automatically expand the fields of a nested structure into the parent: the adapter
that builds a form or a table will have to find the structure with this name in document.structures itself, if its fields are needed, not just the name.
What does the structure turn into
compile(source) returns an array structures. The adapter can turn
fields into UI without re-enumeration:
const controls = document.structures
.find(({ name }) => name === "Строка счёта")
.fields.map((field) => ({
name: field.name,
required: !field.type.includes("undefined"),
control: field.type === "Признак" ? "checkbox" : "text",
}))
This does not mean that FTS knows the entire form design. Layout, hints, async validation and availability remain the responsibility of the UI component. FTS eliminates duplication of names, types and requiredness.
How types get into TypeScript
generateTypeScript reads the structure bound to the utility as принимает,
and for each field calls the same mapping function as the runtime check:
Строка/Дата → string, Число/Деньги → number, Признак →
boolean, the suffix | undefined is preserved as is. If the field type is a name
of a state or an arbitrary label like «Currency» from the example above, the generator
does not crash but also does not guess: it honestly outputs unknown. This is expected —
utilities in FTS accept flat scalar structures, not objects with
states; a state field in a structure used by a utility almost
always indicates that the model mixes two different tasks (computation and output) in
one object.
Practice in the sandbox
Find the «готов к отгрузке» field in the Заказ JSON structure. Its type
must literally match the «то» string in the «Готовый заказ можно отгрузить» morphism.
Open FtsInput0 in the generated code. Count how many fields became
number, and how many — boolean (the correct answer: three and one). Note
that вес and расстояние are different units of measurement, but
the compiler sees both simply as Число.
Replace «state» with «state» in one place (remove the last
letter). Obtain the diagnostics FTS_NATURAL_FIELD and return the text back.
Common Fallacies
FTS_NATURAL_FIELD — field line does not match the form «имя» является типом or «имя» иногда является типом. The missing word “is” —
a common cause: сумма деньгами instead of сумма является деньгами gives
exactly this diagnostic with the line number. Fix — return the keyword
является/иногда является between the field name and the type.
FTS_UTILITY_PROPERTY — property utility violated on a specific
example. The property does not fix the result automatically: if the rule adds
200% instead of the declared maximum of 20% in the postcondition, testUtilities will mark
the example as failed with this code in the error message, and a direct call
executeUtility will throw it as diagnostics. Fix — correct either
the rule, or (if the rule is correct, but the limit is outdated) the property text itself;
both require a deliberate decision, not number tweaking.
Checklist
- I know five built-in types and that
ДатаandДеньгиare checked at runtime more leniently than their names suggest. - I can explain the difference between a state field and a field — an embedded object.
- I understand that a field type with an arbitrary word after «is» is not checked for the existence of the corresponding structure.
- I can predict what each field of a structure will become in the generated TypeScript.