CRUD / foldkit-crud

Sorting a list

How a list is sorted is your state and the query's input. Sort writes that state down once, so the Model, the header clicks and the server agree:

ts
const PostSort = Sort.make(['title', 'created']) // the orders offered, by name

const PostsQuery = Query.make('Posts', {
  Input: { sort: PostSort.Schema },
  Result: Query.connection(Blog.Post),
})
// Model: `postSort: PostSort.Schema`, starting at `PostSort.none`
// update: `SortedPosts` toggles `postSort` by the column the Message carries

PostSort.toggle(model.postSort, 'title') // asc, then desc, then the server's own order
// A drawn table's `sort`: each header sends the column clicked, not the order it
// leads to, so two clicks inside one frame toggle twice.
PostSort.inputs(model.postSort, (_, column) => Message.SortedPosts({ column }))

Sort holds nothing. A name is an order the query offers, never a column. Say what each name means in the query's body, where the server and the client both read it: Query.orderBy(Order.chosen(input.sort, { title: Post.fields.title })) (foldkit-entity). The client then knows which edits can move a row, so a change to another field keeps the list. A Query.make list, which has no body, says it on the server instead, with foldkit-remote-drizzle's sortTerms(sort, { title: posts.title }); any change to its Entity then refetches it.

Typed ids

When the Entity's id is branded, the ids here are too. An editor's helpers.open(id) takes the IdOf of its form's Entity, and target(model) reads the id being edited as that type. A remover whose mutation's input is just the id names the key, id: 'id', and is asked about that key's type; with a fuller input, annotate the function: input: (id: PostId) => ({ id, reason: 'spam' }). A list's rows are its Selection's own value, so row.id is a PostId when the Selection reads id.