Entity / foldkit-entity

Mental model

Entity
 ├── fields      intrinsic values: the properties of entity.schema
 ├── relations   navigation edges: one / many of another Entity (Entity.relate)
 ├── derived     readable values an interpreter supplies, counts or finds
 ├── members     all three under one namespace; keys never collide
 ├── sets        members the write's context fills (who asks, when)
 └── invariants  what every stored row must hold (kept in its metadata)

over it, as data:
 Selection   which members a consumer reads            Entity.select
 Query       which rows, in which order                Query.from · Expr
 Write       what one operation does to one row        Write.update / insert / delete

interpreter ──(foldkit-metadata)──► Entity / member        attaches what it needs
interpreter ◄── reads all of the above, and runs it (SQL, the client's store)

An Entity has one set of members. Entity.relate, Entity.derived and Entity.sets add to it, and every descriptor of the Entity reads it: the one declared before relate, the one a relation's target() returns, the one a query captured. Metadata is the exception: Entity.annotate, Entity.annotateMembers and Entity.invariant return a new descriptor that carries its own, so one reader's view (a form's labels) stays its own. Compare descriptors with Entity.same(a, b), not a === b.

identity ── relations, derived, sets       one per Entity; relate / derived / sets add
   ├── descriptor  fields, metadata         define, relate, derived, sets return it
   └── descriptor  fields, metadata'        annotate / annotateMembers / invariant make one

A descriptor's type still shows only the steps it came through: read typed members from the descriptor relate returned, or a later one.

Everything here is checked where it is declared: a type error where the types can see it, and a throw for what they cannot. An interpreter that cannot run something refuses it by name rather than skipping it.

Install

sh
pnpm add effect foldkit-entity