Entity / foldkit-entity

Selecting a view

A Selection names the members a consumer wants and carries the schema of the value that results. It does not fetch that value; whoever produces it (a server adapter, a test, a form) decodes against selection.schema.

ts
const AuthorOption = Entity.select(Blog.Author, { id: true, name: true })

const PostRow = Entity.select(Blog.Post, {
  title: true,
  commentCount: true,
  author: AuthorOption,
  editor: AuthorOption,
  comments: true,
})

type PostRow = Selected<typeof PostRow>
// {
//   title: string
//   commentCount: number
//   author: { id: string; name: string }
//   editor: { id: string; name: string } | null
//   comments: ReadonlyArray<{ entity: 'Comment'; id: string }>
// }
MemberSelect withYields
Field, Derivedtruethe member's own schema, checks included
Relationtruean EntityRef: { entity, id }
Relationa Selection of its targetthat Selection's value
many RelationEntity.page(selection, window)a page of that Selection's values: { items, hasNext, hasPrevious }

A many relation yields an array, and an optional one is nullable. A Selection is an ordinary value, so AuthorOption above is declared once and reused. Nesting is always explicit, which is what keeps a cycle finite.

A page of a relation

A post may have ten thousand comments. Entity.page reads a many relation a page at a time:

ts
const CommentBody = Entity.select(Blog.Comment, { body: true })

const PostWithComments = Entity.select(Blog.Post, {
  title: true,
  comments: Entity.page(CommentBody, { first: 10 }),
})
// { title: string, comments: { items: { body: string }[], hasNext: boolean, hasPrevious: boolean } }

The window is first or last, with after or before a cursor. A page is a view's shape, as neutral as "many means an array", so it is declared here. What a cursor is and how the rows are ordered belong to whoever fetches: the cursor is an opaque string an earlier answer handed out. Reading on is another Selection with another window.

An unknown member, a nested Selection on a field, a page of a one relation, and a Selection of the wrong Entity are type errors, and Entity.select throws for untyped callers.