Written for AI agents. Human? Read the human version →
jfaster.ai / docsView raw Markdown

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:

NotationUse
Compact syntaxOne line per entity and one line per property. This is what people and agents write.
Embedded XMLStructured 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 withIs
##A comment. The whole line is ignored.
<Embedded XML (§6, §4.4).
#tabA form tab (§5).
an uppercase letterAn entity line (§3).
a lowercase letterA property line (§4).

Rules:

2.2 Tokens

Entity and property lines are split into tokens separated by spaces.


3. Entities

3.1 Entity line

Name [@Menu_Group] [&Entity_Label] [transient] [key="value" ...]
TokenMeaning
NameEntity name, in PascalCase. Always the first token.
@Menu_GroupThe menu group where the entity appears. Underscores become spaces.
@ aloneThe entity has no menu entry (detail rows, operations opened from actions).
(no @)The entity appears in the application's default menu.
&Entity_LabelDisplay label. Underscores become spaces. Default: the name split into words (PurchaseOrder → "Purchase Order").
transientThe 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

3.3 Entity attributes

AttributeMeaning
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:

TokenMeaning
a type keyword (§4.2)The data type. Default: string.
a PascalCase nameA reference to another entity (§4.3).
dPrimary display. The property describes the record: it is shown in listings and wherever the record is referenced. Every entity should have at least one.
dsSecondary display. Shown in listings.
rRequired.
set, list, sortedsetThe property holds a collection (§4.3).
eiThe collection's records are edited inline in the parent form (master/detail).
xExtendable: rows can be added inline (with ei).
fixedShown as read-only text.
disabledShown as a read-only input.
readonlyThe input cannot be edited.
hiddenNot shown in the form, but part of the record.
invisibleNot shown and not exposed at all.
transientNot stored (a helper field in the form).
autosuggestFree 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

KeywordMeaning
(none)Short text (string). Never write string.
text, longtextLong text.
integer, longWhole numbers.
double, floatDecimal numbers.
booleanYes / no.
date, datetimeDate; date and time.
email, phoneValidated text.
passwordMasked text.
file, imageUploaded file; uploaded image.
enumOne 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

DeclarationMeaning
country CountryReference: each record points to one Country. Selected from the existing countries.
tags Tag setMany-to-many: each record has any number of Tags.
emails setMulti-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

AttributeMeaning
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, tooltipInput 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"
ElementMeaning
<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"/> childrenAny 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" />
typeDoes
relatedentityOpens the form of another entity, usually a transient operation, prefilled by defaultset.
openentityOpens the referenced record (used as a property action, §4.5).
javascriptCalls a client-side function named in function.
locationThe action appears
formIn the record's form.
localOn each row of the listing.
listingOn the listing, and applies to the selected rows (batch).
mainAs 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.

LanguageEvaluated onUsed by
Form expressions (XPath 1.0)The record being edited, as the user typescalculated, displayif, visibleif, disabledif, readonlyif, subsetparams, defaultsetparams
Server expressionsStored objectsdefault, <default value>, <dynamicvalue expr>, condition operands
Query expressions (SQL / HQL)The databaseformula, <restriction expr>, sortexpr

7.1 Form expressions

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:


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.

TierConstructs
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
FeatureUses
displayif / disabledif2,354 / 662
Collections set / list / sortedset1,891 / 237 / 226
Master/detail (ei)934
Actions javascript / relatedentity / openentity1,633 / 812 / 367
Embedded listings793
Dynamic values651

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