Remote / foldkit-remote-server
Queries
A Query Source answers the ordered connection itself. The client's select
travels with the request, and the handlers return the selected fields of the
page's items with the edges, so one response carries both:
import { Query } from 'foldkit-remote'
const ProjectsByOwner = Query.make('ProjectsByOwner', {
Input: Schema.Struct({ ownerId: Schema.String }),
Result: Query.connection(Project),
})
const ProjectsByOwnerSource = RemoteServer.query(
ProjectsByOwner,
({ input, window, principal, select }) =>
projectsPage({ ownerId: input.ownerId, window, principal }),
)
const Server = RemoteServer.make({
entities: [ProjectSource],
queries: [ProjectsByOwnerSource],
})A Query returns:
{
edges,
start,
end,
}A Source answers membership only; it may ignore select. The handlers fetch
the selected fields through the entity sources (authorization included) and
return them as entities/settled beside the page. Query membership and
entity data stay separate on the way in and travel together on the way out.
start and end are Remote Boundary values (Terminal, Cursor, or
Unknown). The client uses those explicit boundaries when merging pages instead
of guessing from row count.
Query input is decoded through the Query's own Schema before your callback runs.
The callback receives the decoded input, the requested QueryWindow, the
select slice when the client sent one, and the principal.
Running a query body over rows you already hold
A descriptor declared with
Query.define carries its meaning as a
value, and evaluate runs that value directly:
import { evaluate } from 'foldkit-entity'
evaluate(PostsBySlug.body!, { slug: 'intro' }, rows)
// the matching rows, in the order the body asks forIt lives in foldkit-entity, beside the IR it interprets, because
it is that IR's reference semantics rather than anything this package does —
and because a client judging rows it already holds needs it without depending on
a server package. This package re-exports it, so importing it from either place
means the same thing.
It is pure — it reads the rows it is given and nothing else — and it is the
reference the compiled interpreters are checked against. foldkit-remote-drizzle
turns the same body into SQL; the test suite runs both over the same rows in a
real SQLite and requires the same ids in the same order. That is what makes a
body source-neutral in fact rather than in principle.
It follows SQL, not JavaScript. The case that matters is null: null = null
is unknown in SQL and matches no row, where JavaScript would call the two
equal. A comparison keeps a row only when it is true, so a null on either side
drops it.
What it refuses rather than guesses:
- Ordering by a column that is null in some row. SQLite sorts nulls first,
Postgres sorts them last for
asc: the databases disagree with each other, so there is no answer to be conformant to and picking one would make this wrong against the other. - Comparing values it has no order for, such as a number against a string.
- Searching text that holds a NUL character, which Postgres text cannot
hold and SQLite's
likestops at.
Each throws where the query runs, naming the query. An ordering is checked
before anything is sorted: every term over every matched row, in row order, so
the error names the first row that breaks it (number and string when a later
row holds text), the same on every engine, whichever pair a sort would have
compared first. One matched row, or none, has nothing to order and is never
refused.
Checking an interpreter against the semantics
evaluate is the reference, and cases is what it is a reference to: the
conformance suite, exported so an interpreter written elsewhere can be held to
the same semantics.
import { Subject, cases, rows } from 'foldkit-remote-server'
for (const { what, body, input, expected } of cases) {
expect(yourInterpreter(body, input, rows).map(row => row.id)).toEqual(expected)
}Every case is chosen to make interpreters disagree, which is the only kind
that tests anything: mixed case, nulls on both sides of a comparison, % and
_ as literal text, the empty search, a predicate compared to a boolean either
way round. That is not caution for its own sake — contains reached a released
package meaning three different things because every value in the original
fixture happened to be lowercase.
Both interpreters here run it: this package over rows in memory, and
foldkit-remote-drizzle compiled to SQL against a real SQLite.