Remote / foldkit-remote-server
The read lifecycle
An Entity read is more than a direct function call because a Remote selection may contain several ids and nested relations. The handler performs the mechanical work once so Sources do not have to:
client requirements
|
v
group by Entity
|
v
union requested fields
|
v
filter declared + authorized fields
|
v
batch / de-duplicate ids
|
v
Entity Source.read(...)
|
v
follow selected relation refs
|
v
next Entity Source
|
v
normalized patchesA Source therefore receives the useful boundary directly:
RemoteServer.entity(entity, {
read: ({ ids, fields, windows?, principal }) => Effect<
ReadonlyArray<{ id: string; values: Record<string, unknown> }>,
RemoteServerError,
R
>,
authorize?: (principal, fields) => readonly string[]
filter?: (principal, fields) => Effect<{
fields: readonly string[] // what to read: the allowed fields, and any the decision needs
apply: (record) => record // one record as the principal sees it
}, RemoteServerError, R>
})Important consequences:
- a field the Entity does not declare never reaches
authorizeorread. Its fields, relations and derived members are all declared, so a relation such asowneris read and followed like any field; - a requested field is client input, answered only when the source returns it
as its own:
constructoror__proto__is settled, never read offObject.prototype; authorizecan only remove requested fields, never add new ones;- a Source may return a partial entity — a requested field it omits is settled, so the client records it unavailable rather than asking again;
- ids shared by several selections are de-duplicated;
- relation targets are fetched level by level and shared targets are not loaded repeatedly;
- a relation the principal may not read is never followed;
authorizedecides per field, for every row alike;filterdecides per record, afterread. It runs once per read (by id or live), may read fields the client did not ask for so it can decide (an owner id, say), and a member it drops from a record is settled like one the Source did not return.foldkit-guardbuilds one from a member's guards.- a Source, and a query Source, may carry
visible: the rule that decides which rows it returns, as it was given one (a Drizzle binding'svisible,RemoteServer.memory'sreadable). The server never runs it; it is there for a startup check such asGuardServer.validateto confirm a Source applies the rule its Entity requires.
The Source should still avoid reading or returning columns the request did not ask for. The handler filters returned values again, but the data boundary is the best place to preserve least privilege and avoid wasted work.
Nested selections
Suppose the client projects:
Project:p1 {
id
name
owner { id name }
}and the Project Source returns an owner ref:
Project:p1.owner = User:u7The handler follows that ref through the User Source on the next level:
Project requirement
|
v
Project Source
|
| owner = User:u7
v
User requirement
|
v
User SourceThe Sources do not recursively call each other. RemoteServer owns traversal,
depth limits, batching, and de-duplication; each Source still answers only for
its own Entity.