# 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

```text
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

```text
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 `Tag`s. |
| `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 is `div`**, 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

1. Every entity has `order="..."` and at least one `d` property.
2. Persistent entities are singular nouns; transient entities are verbs. Name relationship entities by their
   meaning (`Enrollment`, not `StudentCourse`).
3. Don't prefix entity names with the owner (`Category`, not `ProductCategory`) unless there is ambiguity.
4. Comments are `##` lines. XML is one tag per line.
5. Labels are quoted tokens, menus are `@Group`, entity labels are `&Label`.
6. Queries go in subsets, not in code. Subsets declare an `order`.
7. Operations are transient entities opened by actions and prefilled by defaultsets.
8. 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
```
