The query is a document.
You send Vex what you want to know and the shape you want back. A model writes the query once, as a small JSON document. Vex checks it against the schema it read from the database, compiles it to SQL with every value as a parameter, and Postgres answers. The next time the question comes, nobody writes anything.
pnpm add @niscorp/vex @niscorp/prism @niscorp/strata zod · 0.4.2 · Apache-2.0
Postgres is starting in this page and loading the theatre’s bookings. It is the real database, compiled to run in a browser, so the first time takes a few seconds. Nothing is sent to a server.
Six words.
A screen reads by name.
An action loads what it shows with one endpoint declaration: a stored name, and the values that change. There is no handler behind it and no SELECT in the app’s code. A GET on the endpoint lists every stored name, what it takes and what it returns, read off the documents themselves.
coming: {
url: '/api/vex',
method: 'POST',
request: {
fingerprint: 'boxoffice/coming',
context: { night: '2026-10-25' },
},
target: 'coming',
}A closed grammar, and a compiler.
A query is a JSON object in a grammar of nine keys. The grammar is a Zod schema with a description on every field, and the JSON Schema made from it is what a model is handed. The engine takes two optional hooks, generateDsl and mapToShape. Without them it replays what is stored and generates nothing.
One query. Two members. Two answers.
A stored query belongs to no caller. A policy says, per table, which rows are whose. Vex places it into the statement after the document is read, bound to values the server knows and the request does not carry. A request that leaves them out is refused: omitting the values means nobody, never everybody.
Postgres is starting in this page and loading the theatre’s bookings. It is the real database, compiled to run in a browser, so the first time takes a few seconds. Nothing is sent to a server.
The worst a query can do is be refused.
A model can write a query that is wrong, and a caller can send a value that is hostile. Vex takes each against the schema first, and only then to Postgres.
Postgres is starting in this page and loading the theatre’s bookings. It is the real database, compiled to run in a browser, so the first time takes a few seconds. Nothing is sent to a server.
Stored under a name.
Every stored query has a fingerprint, and a request that carries one replays it. When a query is generated, Vex makes the fingerprint, fp_ and a hash, and returns it with the first answer; the query under it never changes. When you seed a query, you give it the name, and a request cannot replace what you seeded.
What is kept is the query and its mapping, never the rows. A migration that changes the schema throws the entry out, and a seeded one is seeded again at boot.
What a database engineer asks.
On hearing that a model writes the queries. Twenty-one answers, each the package’s own.
SELECT *?NoEvery column is named. A column the schema does not have does not compile.offset.missing_scope.refresh: 'reactive' and follow it. It answers again when a table it reads is written.exists, compiled inline, so a list of ids is never fetched and sent back.