Journal

September 19, 2026 · 2 min read

Queries that say what they mean

An Entity declared once, screens that name the fields they read, and a query the server compiles from what it means: how Entity, Remote and Crud move this blog’s data.

Every screen of the studio reads data the same way. It says what it needs, and the Model fills in. No view fetches, and nothing keeps a cache the Model does not know about.

An Entity, declared once

A post is declared once with foldkit-entity, as the fields it has. Everything else reads that one declaration: the server binds it to a table, the form edits it, the CMS finds its slug in it, and a view asks for a selection of it:

export const PostPage = Entity.select(Post, {
  id: true,
  title: true,
  slug: true,
  excerpt: true,
  cover: true,
  body: true,
  publishedAt: true,
})

Remote: the server’s facts, in the Model

foldkit-remote keeps what the server owns (posts, pages, drafts) as a cache inside the Model, each record once. A screen that is showing declares what it reads. A Subscription turns that into requests for what is missing, the answers come back as Messages, and one reducer puts them in the Model. When the screen goes, its need goes with it, and the data can be let go.

Losing that cache is never losing work: the facts were the server’s, and asking again is how it recovers. Work that is not yet on the server, like a draft being typed, is the form’s, and the CMS saves it.

A query is a value

The blog’s index reads the newest published posts. The query is declared by what it means, not by an address to fetch:

export const RecentPosts = Query.define('RecentPosts', {}, () =>
  Query.from(Post).pipe(
    Query.where(Expr.isNotNull(Post.fields.publishedAt)),
    Query.orderBy(Order.desc(Post.fields.publishedAt)),
  ),
)

The server compiles it to SQL with foldkit-remote-drizzle, and the client asks for it by name. There is no endpoint to write, and no second copy of the question to keep in step. The server still decides what the asker may see: it answers a visitor and an editor with only what is published.

Crud, in brief

The studio’s list of posts is foldkit-crud. A list joins a query to a selection:

export const WorklistList = Crud.list('Worklist', {
  query: Cms.Entries,
  selection: EntryRow,
})

foldkit-mixins-crud draws it as a table whose rows open the entry. A Crud editor joins a form to the mutation its value feeds: it loads what the form edits, fills the form, turns a valid submit into the mutation, and says how it went. The CMS’s editor has the same shape, with drafts and publishing added.

Nothing is generated. The application declares the form and the mutation, and Crud only joins them, so a screen does what those two say and nothing more. The post on Crud goes further.

← More from the blog