Specifications
Every framework answers the same questions. Who can touch this row? What does a write announce? What comes back over the wire, and what happens when it fails? FrontierJS gives each answer exactly one owner, and one owner is what you need to write an answer down as a specification. The goal is a set any framework could implement, with FrontierJS as one implementation of it.
None of them is written yet. FrontierJS is pre-alpha, and a spec freezes things. Nothing gets frozen until something other than FrontierJS passes the tests.
.lite or .mesa.There are about forty candidates. These ten come first because the others lean on them, and each one leans on the ones above it.
What's worth saying about a model: its fields and types, its identity, relations, constraints, defaults, derived values, labels, and which fields only the app itself may write. Everything else on this list derives from it.
The catch is that a spec describes JSON, not syntax, and there's no JSON form of a model separate from .lite yet. generateJsonSchema comes closest, but it's a projection of the model, not the model.
Access as a number. A model's gate says how high a caller has to stand to read, create, update or delete, and a session is graded onto the same scale. Rungs 0 to 7 are callers. Rung 8 is the app acting on its own behalf, and 9 means there's no code path at all, so those two are never compared with >=.
On the client, an answer the UI doesn't know is treated as a yes, because the data layer enforces the gate either way.
@frontierjs/toolbelt/gateA gate answers who. A policy answers which rows. @@allow and @@deny are predicates over a row and its fields, and one evaluator gives two kinds of answer: yes or no when everything is known, and a leftover condition the query applies when the row hasn't been read yet.
Protected fields belong here too. @encrypted, @guarded and @secret each make a promise, and every one of them shows up as [redacted] in the audit trail.
Policies and flows are written in the same small language. The spec covers its grammar in two dialects: one over a single record, for policies, and one with paths, calls and lambdas, for flows. It also says what every operator does with null.
@frontierjs/toolbelt/predicateEvery write emits exactly one event: create, update, remove or transition. The audit trail, live channels and the data on the client all hang off it. The event's origin is the row, never the caller.
$tapEventsA service is CRUD plus the methods you declare, and declaring methods narrows what's exposed. The description says who may call each method and what the call announces. Junction generates OpenAPI too, but OpenAPI has no field for either of those.
svc.describe()One shape on both sides of the wire: kind, data, errors, total, limit, offset, and a cursor for the next page. kind is always stated and never guessed. A list keeps its envelope and a single record unwraps. The same function does both, on the server and in the client.
How a thrown error becomes an HTTP status, in a fixed order. Which errors are retryable, meaning re-read and try again, and which are refusals. And the shape of field errors that a form reads. Junction already commits a table of these cases, which is most of a test suite.
errors.snapshot.md as the tableWhat a list request means. There are five directives, $limit, $offset, $orderBy, $select and $after, and a request carrying any other $ key is refused by name. A query-string value is a number only if it round-trips, and brackets carry structure.
Ordering and paging go here too: how ties are broken, and why a list that can't break its ties gives no next-page cursor rather than a wrong one.
@frontierjs/toolbelt/directives and /queryOne call, two transports: the HTTP mapping, the WebSocket frame, and the short list of headers a call may carry, since a socket has no headers per call. The test is simple. Make the same call both ways and get the same answer.
verifyTransportParityA schema implies its own checks: the gate ladder, constraints, field protection, row policies and tenant isolation. Generate those for any app, each with a case that must fail, and you have test vectors for most of the list above. Litestone's verify checks and fli test:access are the start of it.
Nothing in the ten depends on either one, so both can be written first.
Where a standard exists, FrontierJS implements it or narrows it, and doesn't write a competitor.
.lite and .mesa are syntaxes, and a spec describes the JSON they compile to. SQLite, Bun, fli, file-tree routing, the app layout and the port formula are FrontierJS's own choices. Another implementation makes its own.
A spec with one implementation is a README.