Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Reads and writes

Reads and writes

Named queries

On this page

Every read of stored records is a named query. An entity is a kind of stored record, like a table with types (Entities). A query is one read of an entity: which records, in what order, and how many. Each query lives in a file of its own in its domain's queries/ folder (Files and names). You call it by name, as you call an action, the function that changes records (Functions and actions).

This query gives an owner's todos a page at a time, newest first. Its filter parameter is a ListTodos, an Input type a route fills from the request: an optional status, an optional due_before, a page_size that defaults to 25 and an optional after, the cursor the previous page returned:

example
// domains/planning/queries/todos_for_owner.jadpo/// The owner's visible todos, newest first, one page at a time.query todos_for_owner(owner_id: User.id, filter: ListTodos) -> Page<TodoView> {    from: Todo    where: Todo.owner_id == owner_id    where_present: Todo.status == filter.status    order: [Todo.created_at desc]    page: {        size: filter.page_size        after: filter.after    }}

from: names the entity, where: the records it keeps, order: their order and page: how many come back at once and where the page starts. where_present: applies its condition only when the value on the right is there: when the request leaves out status, every status comes back. The Filters and Order and pages topics cover each section in full.

How many records come back

The result type says how many records come back, so there is no separate word for it. A query for exactly one record also needs missing:, which names the failure it gives when no record matches (missing:):

Result type Returns Needs
TodoView exactly one record a where: on a unique field, and missing:
TodoView? zero or one a where: on a unique field
List<TodoView> up to the number limit: sets order: and limit:
Page<TodoView> one page and a cursor for the next order: and page:
FeatureCollection<Point, TodoPin> a map layer of up to the number limit: sets, each record a point on the map (A query giving a map layer) geometry:, order: and limit:

Some apps are shared by several tenants, such as companies, whose records are kept apart (Tenancy). There a unique: rule holds within each tenant: two companies can each have a project called Launch (A unique rule holds within its tenant). So a unique field alone doesn't pick one record. A query for one record compares the record's tenancy reference in its where: as well as the unique field, unless it compares a reference to another record of the same tenant, which already picks the tenant.

What a result holds

A result's element type is the entity, or a type built from its fields. type TodoView = Output { pick: [Todo.id, Todo.title] } is a type for output that picks two of Todo's fields (Building a type from other types). The compiler reads only the fields that type needs.

That type can't have a rule across its fields, such as "ends_at is after starts_at" (Rules across fields). This holds even when each field's own limits would make the rule always true. The same goes for every other type that holds a query's records, including a Nearest<T>'s item (Nearest first), a map layer's properties, an aggregate's rows and the related records a query brings with it. A result type with such a rule is the error query.result_rule.

The reason is that Jadpo checks a rule whenever a value is made, and a broken rule is a failure your code handles with attempt (Passing a failure on). A read has no attempt around it, and the database can't check the rule as it reads. So a stored record that broke the rule could only be a defect, a bug rather than a failure the caller handles. Instead, read into a type with the same fields and no rule, then build the type with the rule from it in code, inside an attempt.

Records the entity's lifecycle hides, such as soft-deleted todos, are left out of every query.

A query writes nothing and calls no actions (Functions and actions). Call it by its bare name in its own domain. From another domain, put the domain's name first, planning.todos_for_owner(...), which works when the query is public.

The statement it sends

jadpo sql todos_for_owner prints the SQL statement the query sends. It is one SELECT with the query's conditions, an ORDER BY, and a LIMIT one more than the page's size, so the runtime knows whether a next page exists. It selects only the columns the result type needs, plus the order: fields a page cursor needs and any field a policy condition checks outside the database.

The statement's text is fixed when the project is compiled. A where_present: condition and the cursor are written ($n IS NULL OR ...), so the same query always sends the same statement.

A query called inside an action reads in the action's transaction. Called from a test, it runs in a short transaction of its own.

Markdown for agents: /docs/named-queries.md.Built from Jadpo main at a707efb.