JFaster Language Specification
Version 1.0 draft — 2026-09. This document defines the JFaster modeling language: its compact syntax, its constructs and their meaning. It is independent of any implementation. A JFaster implementation reads a model written in this language and produces a working application from it.
1. Overview
A JFaster model describes a data-centric business application: the entities it manages, their properties and relationships, named queries (subsets), search filters, list views (listings), operations (actions) and rules to prefill records (defaultsets).
The model is the single source of truth. Storage, business services, the API, forms and listings are all derived from it, so they cannot disagree with each other. A field is declared once.
The language has two notations that are used together:
| Notation | Use |
|---|---|
| Compact syntax | One line per entity and one line per property. This is what people and agents write. |
| Embedded XML | Structured constructs (subsets, filters, listings, actions, defaultsets, enumerations) written as XML elements inside the compact source. |
A model can be split into several source files (for example one per module: Sales.txt, HR.txt). Together they form a single model: an entity in one file can reference an entity in another.
1.1 A first model
## Customers and their orders
Customer @Sales order="name"
name d r
email
country Country
Order @Sales order="orderDate desc"
customer Customer d r
orderDate date ds r default="now"
lines OrderLine set ei x childproperty="order"
OrderLine @
order Order
product Product r
quantity integer r
Product @Catalog order="name"
name d r
price double r
Country @Configuration order="name"
name d r
This model defines five entities, their fields and relationships, an order with editable lines, and a menu with three groups. Nothing else is needed for a working application.
2. Lexical structure
A source file is read line by line. Leading and trailing whitespace does not matter, so indentation is not significant: indenting properties under their entity is a readability convention. Empty lines are ignored.
2.1 Kinds of lines
| A line starting with | Is |
|---|---|
## | A comment. The whole line is ignored. |
< | Embedded XML (§6, §4.4). |
#tab | A form tab (§5). |
| an uppercase letter | An entity line (§3). |
| a lowercase letter | A property line (§4). |
Rules:
- Comments occupy a whole line and start with
##. There are no end-of-line comments. - Every line of embedded XML starts with
<. Write one tag per line and never split a tag across lines. Nested elements go on their own lines. - Entity and property lines are told apart by the case of their first letter, not by indentation.
2.2 Tokens
Entity and property lines are split into tokens separated by spaces.
- A double-quoted string is one token, even with spaces inside:
"Grand total". - A token of the form
key="value"is an attribute. - Everything else is a keyword or a name, as described in §3 and §4.
3. Entities
3.1 Entity line
Name [@Menu_Group] [&Entity_Label] [transient] [key="value" ...]
| Token | Meaning |
|---|---|
Name | Entity name, in PascalCase. Always the first token. |
@Menu_Group | The menu group where the entity appears. Underscores become spaces. |
@ alone | The entity has no menu entry (detail rows, operations opened from actions). |
(no @) | The entity appears in the application's default menu. |
&Entity_Label | Display label. Underscores become spaces. Default: the name split into words (PurchaseOrder → "Purchase Order"). |
transient | The entity is an operation, not stored data (§3.2). |
key="value" | Entity attribute (§3.3). |
Menu and label are always written with @ and &, never as menu="..." or label="..." attributes.
Every line after an entity line, up to the next entity line, belongs to that entity: its properties, tabs and embedded XML.
3.2 Persistent and transient entities
- A persistent entity (the default) is stored data. It has records that can be created, listed, searched, edited and removed. Name it with a singular noun:
Invoice,Customer,Enrollment. - A transient entity is an operation: a form that performs something when submitted (approve, import, send, close a period, run a batch process). Its data is not stored. Name it with a verb:
ApproveInvoice,ImportPrices,SendReminder. It is usually opened from an action (§6.4) and prefilled with a defaultset (§6.5).
3.3 Entity attributes
| Attribute | Meaning |
|---|---|
order="field [desc]" | Default order of the entity in listings and selectors. Every entity should declare one. |
defaultlisting="name" | The listing opened from the menu (§6.3). |
noadd="noadd" | Records cannot be created from the listing (they are created by operations). |
noremove="noremove" | Records cannot be removed from the listing. |
stereotype="Async" | For transient entities: the operation runs asynchronously. |
4. Properties
4.1 Property line
name [type] [EntityName] [modifiers...] ["Label"] [key="value" ...] [\]
The first token is the property name, in camelCase. The other tokens can appear in any order:
| Token | Meaning |
|---|---|
| a type keyword (§4.2) | The data type. Default: string. |
| a PascalCase name | A reference to another entity (§4.3). |
d | Primary display. The property describes the record: it is shown in listings and wherever the record is referenced. Every entity should have at least one. |
ds | Secondary display. Shown in listings. |
r | Required. |
set, list, sortedset | The property holds a collection (§4.3). |
ei | The collection's records are edited inline in the parent form (master/detail). |
x | Extendable: rows can be added inline (with ei). |
fixed | Shown as read-only text. |
disabled | Shown as a read-only input. |
readonly | The input cannot be edited. |
hidden | Not shown in the form, but part of the record. |
invisible | Not shown and not exposed at all. |
transient | Not stored (a helper field in the form). |
autosuggest | Free text with suggestions from existing values. |
"Some_label" | The label. Underscores become spaces. Default: the name split into words (firstName → "First Name"). |
key="value" | Property attribute (§4.6). |
\ (last token) | Continuation: the following embedded XML lines belong to this property (§4.5). |
Labels are always written as a quoted token (total double "Grand total"), never as label="...".
A line can declare several properties separated by / (a slash with spaces around it):
firstName d r / lastName d r / email
4.2 Types
| Keyword | Meaning |
|---|---|
| (none) | Short text (string). Never write string. |
text, longtext | Long text. |
integer, long | Whole numbers. |
double, float | Decimal numbers. |
boolean | Yes / no. |
date, datetime | Date; date and time. |
email, phone | Validated text. |
password | Masked text. |
file, image | Uploaded file; uploaded image. |
enum | One of a fixed set of values (§4.4). |
Name as type. When the property name is itself a type keyword, the property has that type:
email r
date "Due_date"
file "Attachment"
declare a required email, a date labeled "Due date" and a file. In that case do not repeat the type (write email r, not email email r). Otherwise, give the property a descriptive name and its type: issueDate date, workEmail email.
Avoid naming a property like a modifier (hidden, fixed, list, set, d, r, x).
4.3 Relationships and collections
| Declaration | Meaning |
|---|---|
country Country | Reference: each record points to one Country. Selected from the existing countries. |
tags Tag set | Many-to-many: each record has any number of Tags. |
emails set | Multi-valued field: any number of strings. |
lines InvoiceLine set ei x childproperty="invoice" | Master/detail: the invoice owns its lines; they are edited inline in the invoice form, rows can be added, and childproperty names the property of InvoiceLine that points back to the invoice. |
Collections: set (no duplicates, no particular order), list (ordered; listindex="field" stores the position), sortedset (kept sorted by sortexpr="expression").
Inline detail or embedded listing. Use set ei x for a few rows that are part of the parent (invoice lines, addresses, schedule items). For many records, or records with their own life (payments, messages, history), use an embedded listing (§6.6).
4.4 Enumerations
An enumeration is declared once, usually at the top of the file, and referenced by name:
<enumset name="Priority">
<enum value="low">Low</enum>
<enum value="high">High</enum>
</enumset>
Task @Tasks order="title"
title d r
priority enum enumset="Priority" default="low"
labels set enum enumset="Label"
value is what is stored; the element text is what users see.
4.5 Continuation (\)
A property line that ends with \ is followed by embedded XML that belongs to the property: its own actions, an inline enumeration or a dynamic value (§6.7). The property ends at the next property, entity or tab line.
customer Customer d \
<action name="viewCustomer" type="openentity" label="View" />
unitPrice double disabled \
<dynamicvalue expr="product.price">
<param name="product" entity="Product" value="product" />
</dynamicvalue>
quantity integer r
4.6 Property attributes
| Attribute | Meaning |
|---|---|
default="value" | Initial value of new records: a literal, now, or a server expression (§7.2) such as User:find(userId). |
subset="name" | Only records of that subset (§6.1) can be selected. |
subsetparams="field,..." | Fields that feed the subset parameters. When they change, the choices are recomputed: cascading selectors. |
displayif="cond" | The field is shown only when the condition holds (§7.1). |
visibleif="cond" | Like displayif, but the field keeps its space when hidden. |
disabledif="cond", readonlyif="cond" | Conditional read-only. |
calculated="expr" | Value computed from other fields of the form (§7.1). |
formula="sql" | Read-only value computed by the database for each record (§7.3). |
pattern, minlength, maxlength, placeholder, tooltip | Input format, validation and help. |
5. Tabs
Long forms are divided into tabs. A #tab line starts a tab that lasts until the next #tab line or the end of the entity.
Employee @HR order="lastName"
#tab Personal
firstName d r / lastName d r
birthDate date
#tab "Job_details"
department Department r
startDate date
#tab Word uses the word as label; #tab "Two_words" uses the quoted label with underscores as spaces.
6. Behavior
These constructs are written as embedded XML inside an entity.
6.1 Subsets: named queries
A subset is a named query over the entity's records, optionally with parameters. It is defined once and reused by selectors, listing tabs, embedded listings and filters.
Province @Configuration order="name"
<subset name="forCountry" order="name">
<param name="country" entity="Country" />
<condition field="country" value="country" />
</subset>
name d r
country Country r
Address @
country Country r
province Province subset="forCountry" subsetparams="country"
| Element | Meaning |
|---|---|
<param name="p" entity="E"/> or type="t" | A parameter. Add collection="set" for a set of records. |
<condition field="f" .../> | Compares a field. Operators: value (equals), gt, ge, lt, le, like, isnull="isnull", notnull="notnull", in, contains. The operand is a parameter name, a number, true/false, a string in single quotes (value="'open'") or a server expression such as User:find(userId) (§7.2). A bare word is a parameter name, not a string. |
<restriction expr="..."> with <param value="p"/> children | Any condition as a query predicate (§7.3); me is the record, ? binds each <param> in order. |
<and>, <or> | Grouping. Conditions at the same level are combined with AND. |
Write the parameters first, then the conditions, then the restrictions. Give every subset an order. Express queries in the model rather than in code.
6.2 Filters
A filter is a search field on the listing.
<filter name="status" property="status" display="primary" />
<filter name="search" label="Search" display="primary">
<or>
<condition field="name" like="concat('%', search, '%')" />
<condition field="code" like="concat('%', search, '%')" />
</or>
</filter>
A filter either searches one property (property="...") or defines its own conditions, where the filter's name is the value typed by the user. display="primary" shows it by default; display="secondary" shows it among the advanced filters.
6.3 Listings and tabs
A listing is a view of the entity's records. Tabs split it by subset, each with its own actions:
<listing name="main">
<tab name="open" label="Open" subset="open" displayactions="assign,close" />
<tab name="closed" label="Closed" subset="closed" displayactions="reopen" />
</listing>
An entity can have several listings (per role or context). displayfilters, displayactions and displayproperties choose what a listing or tab shows.
6.4 Actions
An action is a button that starts something from a form or a listing.
<action name="approve" label="Approve" type="relatedentity" entity="ApproveExpense" location="local" defaultset="forExpense" />
type | Does |
|---|---|
relatedentity | Opens the form of another entity, usually a transient operation, prefilled by defaultset. |
openentity | Opens the referenced record (used as a property action, §4.5). |
javascript | Calls a client-side function named in function. |
location | The action appears |
|---|---|
form | In the record's form. |
local | On each row of the listing. |
listing | On the listing, and applies to the selected rows (batch). |
main | As the default action when a row is opened. |
6.5 Defaultsets
A defaultset prefills a new record from a context: a parameter goes in, fields come out.
ApproveExpense @ transient
<defaultset name="forExpense">
<param name="expense" entity="Expense" />
<default name="expense" value="expense" />
<default name="approver" value="User:find(userId)" />
</defaultset>
expense Expense fixed
approver User fixed
notes text
For batch actions the parameter is a collection of the selected records:
<param name="expenses" entity="Expense" collection="set" />
6.6 Embedded listings
An embedded listing shows the related records of another entity inside a form.
Company @CRM order="name"
name d r
<embeddedlisting name="employees" entity="Person" subset="forCompany" subsetparams="." defaultset="newForCompany" defaultsetparams="." />
subsetparams="." passes the current record. The defaultset prefills records created from the embedded listing.
6.7 Dynamic values
A dynamic value computes a field from related data whenever its parameters change:
unitPrice double disabled \
<dynamicvalue expr="product.price">
<param name="product" entity="Product" value="product" />
</dynamicvalue>
Use it when the value comes from other records (product.price). Use calculated (§7.1) when it only depends on fields of the same form.
7. Expressions
A model uses three expression languages. Each attribute uses exactly one of them.
| Language | Evaluated on | Used by |
|---|---|---|
| Form expressions (XPath 1.0) | The record being edited, as the user types | calculated, displayif, visibleif, disabledif, readonlyif, subsetparams, defaultsetparams |
| Server expressions | Stored objects | default, <default value>, <dynamicvalue expr>, condition operands |
| Query expressions (SQL / HQL) | The database | formula, <restriction expr>, sortexpr |
7.1 Form expressions
- A field is referenced by name:
status. Paths go through references:customer.active. .is the current record.- Strings go in single quotes:
status = 'approved'. - Comparison:
=,!=,<,>,<=,>=. Logic:and,or,not(...). - Arithmetic:
+,-,*,div,mod. Division isdiv, because/separates path steps. - Aggregates over detail rows:
sum(lines[*].amount).
discount double displayif="customer.vip = 'true'"
subtotal double calculated="sum(lines[*].amount)"
tax double calculated="subtotal * (taxRate div 100)"
In production models, equality with a string literal is by far the most common condition, and sum(collection[*].field) accounts for one in four calculated fields.
7.2 Server expressions
Navigation through references (invoice.customer.email), finders (User:find(userId), Status:findOneBy('code', 'OPEN')), now and userId, the id of the logged-in user. userId refers to the model's own User entity: every model that uses it declares a User entity. Method calls on values (list.size()) are not part of the language.
7.3 Query expressions
formula is a SQL expression evaluated for each record, for example (SELECT COUNT(*) FROM person WHERE person.company = id). A <restriction> expression is a query predicate where me is the current record and each ? is bound to a <param>.
8. Meaning of a model
A JFaster implementation derives the whole application from the model:
- Each persistent entity gets storage, a form to create and edit records, a listing with its filters, tabs and actions, and an API to read and write records.
- Each transient entity gets a form and an operation that runs when the form is submitted.
- Subsets are available wherever records are chosen, listed or queried, including the API.
- Menus group the entities by their
@group. - Business rules that the model cannot express (complex validations, integrations, calculations) are written as code in the implementation's extension points. They complement the model; they never duplicate what the model declares.
9. Style rules
- Every entity has
order="..."and at least onedproperty. - Persistent entities are singular nouns; transient entities are verbs. Name relationship entities by their meaning (
Enrollment, notStudentCourse). - Don't prefix entity names with the owner (
Category, notProductCategory) unless there is ambiguity. - Comments are
##lines. XML is one tag per line. - Labels are quoted tokens, menus are
@Group, entity labels are&Label. - Queries go in subsets, not in code. Subsets declare an
order. - Operations are transient entities opened by actions and prefilled by defaultsets.
- Prefer inline detail (
set ei x) for a few owned rows and embedded listings for many or independent records.
10. Usage in production
The language has been used for about 20 years to build business systems. The figures below come from 196 production models across about 75 projects.
| Tier | Constructs |
|---|---|
| Core (thousands of uses) | property, condition, param, enum, filter, subset, action, tab, default, listing, defaultset, restriction |
| Frequent (hundreds) | embeddedlisting, dynamicvalue, wizard steps, panels |
| Specialized (under 100) | crosstab reports, suggestions, action groups, display conditions |
| Feature | Uses |
|---|---|
displayif / disabledif | 2,354 / 662 |
Collections set / list / sortedset | 1,891 / 237 / 226 |
Master/detail (ei) | 934 |
Actions javascript / relatedentity / openentity | 1,633 / 812 / 367 |
| Embedded listings | 793 |
| Dynamic values | 651 |
Production systems written in the language reach 100 to 500 entities and hundreds of actions.
Appendix A. Complete example
<enumset name="ExpenseStatus">
<enum value="draft">Draft</enum>
<enum value="submitted">Submitted</enum>
<enum value="approved">Approved</enum>
</enumset>
## People
Employee @Staff order="lastName"
firstName d r / lastName d r
workEmail email r
department Department
Department @Staff order="name"
name d r
## Expense reports and their approval
ExpenseReport @Expenses order="submittedOn desc"
<subset name="submitted" order="submittedOn desc">
<condition field="status" value="'submitted'" />
</subset>
<listing name="main">
<tab name="all" label="All" />
<tab name="toApprove" label="To approve" subset="submitted" displayactions="approve" />
</listing>
<filter name="employee" property="employee" display="primary" />
<filter name="status" property="status" display="primary" />
<action name="approve" label="Approve" type="relatedentity" entity="ApproveExpenseReport" location="local" defaultset="forReport" />
employee Employee d r
submittedOn date ds r default="now"
purpose d r
lines ExpenseLine set ei x childproperty="report"
total double ds calculated="sum(lines[*].amount)"
status enum enumset="ExpenseStatus" default="draft" fixed
ExpenseLine @
report ExpenseReport
date r
concept d r
amount double r
receipt image
ApproveExpenseReport @ transient
<defaultset name="forReport">
<param name="report" entity="ExpenseReport" />
<default name="report" value="report" />
</defaultset>
report ExpenseReport fixed
notes text