Entity / foldkit-entity
Reading an operation's input
Experimental: the smallest mapping that several operation shapes and
foldkit-formneeded.
An Entity does not decide what may be written; an operation does (a Remote
mutation, an RPC, a form). Entity.input takes that operation's input schema
and says what each key means in terms of the Entity, so a consumer can find the
field's metadata for a label, or the relation's target for a picker.
const CreatePostInput = Schema.Struct({
title: Schema.String,
authorId: Schema.String,
notify: Schema.Boolean,
})
const CreatePost = Entity.input(Blog.Post, CreatePostInput, {
authorId: Relation.input(Blog.Post.relations.author),
notify: Entity.unmapped,
})
CreatePost.members.title // Blog.Post.fields.title: it names a field, so it maps itself
CreatePost.members.authorId.relation.target() // Blog.Author: what a picker chooses from
CreatePost.members.notify // Entity.unmapped: about the operation, not the Post- A key that names a field, with a value that fits it, maps itself. Only the
present value has to fit: a key may be optional (a partial update), admit
null(clearing it), or hold anOption(an id absent for something new), which is the input schema's rule to make. A declared write (Write.update,Write.insert) writes what anOptionholds and nothing for one holding nothing or a key left out; its id may not be absent, since it names the row it writes. - Every other key needs an entry: a Field under another name, a relation's ids
with
Relation.input(relation), orEntity.unmapped. Nothing is inferred from a name likeauthorId. - An entry may name the member by its key:
{ headline: 'title', authorId: 'author' }is thetitleField andRelation.inputofauthor. It is the same reading, checked the same way; a name that is no field or relation is a type error. Entity.fields(Post, 'id', 'title')is those fields' schemas, to spread into the input's struct, so the input keeps the field's rules without repeatingPost.fields.title.schemaper key.Relation.inputexpects one id for aone,id | nullfor an optionalone, and an array of ids for amany.- A derived member cannot be written, and a member of another Entity cannot be mapped. Both are type errors and throw.
An input that holds the target itself
A post created together with a new author carries the author, not an id.
Relation.nested maps the key to the relation and to an input of its target:
const NewAuthor = Entity.input(Blog.Author, Schema.Struct({ name: Schema.String }))
const CreatePost = Entity.input(
Blog.Post,
Schema.Struct({ title: Schema.String, author: NewAuthor.schema }),
{ author: Relation.nested(Blog.Post.relations.author, NewAuthor) },
)A one holds the nested input's value, a many a list of them; the nested
input must be of the relation's target, and both are checked by type and at
runtime. Entity.selectFor then loads what the nested input writes of the
target, and Entity.valuesFor turns the loaded target back into nested values.
What a nested write does (insert, update, replace the list) is the
operation's handler to decide.
Showing what is there
An edit screen has to load the current values and turn them into input values. Both follow from the reading, so neither is written by hand:
const PostForEdit = Entity.selectFor(CreatePost)
// a Selection of `title` and `author`, the members the input writes; `author` as a ref
Entity.valuesFor(CreatePost, { title: 'Hello', author: { entity: 'Author', id: 'a1' } })
// { title: 'Hello', authorId: 'a1' }selectFor selects every member the input writes, by the member's key, with
each relation as refs. valuesFor reads such a value back under the input's
keys: a field as it is, a relation as the id or ids of what it holds. A relation
read without its ids (a nested Selection that left id out) fills nothing. An
unmapped key appears in neither, since the Entity knows nothing about it.
The schema is an ordinary Schema.Struct, so declare it once and give the same
value to the operation, for example Mutation.make('CreatePost', { Input:
CreatePostInput, … }) in foldkit-remote.
Declaring what an input writes
An input says what each key is; a Write says what an operation does with
them: Write.update one row, named by a key, setting every other key mapped to
a field; Write.insert a row under an id the client chose; or Write.delete
one, which sets nothing.
const EditPost = Entity.input(Blog.Post, EditPostInput)
const Edit = Write.update(EditPost, { id: 'id' })
Write.writes(Edit) // the fields any invocation may change
Write.bind(Edit, { id: 'p1', title: 'Compilers' })
// { entity: 'Post', id: 'p1', values: { title: 'Compilers' } }, as the store holds itIt is declared data, like a query body: nothing is written by building one, and
an Entity having a field grants no one the right to set it. bind encodes each
value through its field's schema, so the patch is in the store's shape, and
takes the keys to write when an author changed only some. expect names a key
holding the revision the row was read at, for an interpreter to write only a row
still at it. An unmapped key is not written. A key mapped to a one relation
points the row elsewhere: bind gives it as a ref in links (none for an
optional relation set to nothing), and each interpreter carries it its own way,
a ref key in a store, a foreign key in a table. A many relation or a nested
key is refused where the write is declared.