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

A query giving a map layer

A query can return its records as a map layer: a FeatureCollection<G, P> (Interop), where G is the geometry type and P the type of each feature's properties.

A query can return its records as a map layer: a FeatureCollection<G, P> (Interop), where G is the geometry type and P the type of each feature's properties. A GET route can then send the layer a mapping library fetches, as plain RFC 7946 GeoJSON (Output and status). Here are a bakery map's layer, and the bakeries within reach of a door:

example
use baking/entities { Bakery }use baking/types { BakeryName }/// What a bakery's pin on the map says: its identity goes in its/// properties.type BakeryPin = Object {    id: Bakery.id    name: BakeryName}
example
use baking/entities { Bakery }use baking/types { BakeryPin }/// Every bakery, as the map's layer.query bakery_map() -> FeatureCollection<Point, BakeryPin> {    from: Bakery    geometry: Bakery.spot    order: [Bakery.name asc]    limit: 500}
example
use baking/entities { Bakery }use baking/types { BakeryPin, SearchReach }/// The bakeries within reach of a door, nearest first, as a layer.query bakeries_near_map(    doorstep: Point,    max_trip: SearchReach) -> FeatureCollection<Point, BakeryPin> {    from: Bakery    geometry: Bakery.spot    where: geo.within(Bakery.spot, doorstep, distance: max_trip)    order: [geo.distance(Bakery.spot, doorstep) asc]    limit: 20}
example
use baking/types { BakeryPin }use baking/queries { bakery_map }/// Bakeries, for the map.routes Bakeries {    prefix: /bakeries    /// Every bakery, as GeoJSON.    GET / {        authentication: RouteAuthentication.none        output: FeatureCollection<Point, BakeryPin>        run: bakery_map()    }}
example
use baking/entities { Bakery }use baking/types { BakeryPin }use baking/actions { open_bakery }use baking/queries { bakeries_near_map, bakery_map }test "the map fetches the bakeries as GeoJSON" {    var soho: Bakery = open_bakery(        "Soho",        Point {            latitude: 51.5136            longitude: -0.1365            altitude: none        }    )    var bermondsey: Bakery = open_bakery(        "Bermondsey",        Point {            latitude: 51.4979            longitude: -0.0637            altitude: none        }    )    var response: Response = request {        method: HttpMethod.get        path: "/bakeries"    }    assert response.status == 200    assert response.output == bakery_map()    assert response.output == FeatureCollection {        features: [            Feature {                geometry: bermondsey.spot                properties: BakeryPin {                    id: bermondsey.id                    name: bermondsey.name                }            },            Feature {                geometry: soho.spot                properties: BakeryPin {                    id: soho.id                    name: soho.name                }            }        ]    }    var near_soho: FeatureCollection<Point, BakeryPin> = bakeries_near_map(soho.spot, 2km)    assert list.map(near_soho.features, pin -> pin.properties.name) == ["Soho"]}

GET /bakeries answers {"type":"FeatureCollection","features":[...]}, each feature {"type":"Feature","geometry":{"type":"Point","coordinates":[-0.0637,51.4979]},"properties":{"id":"...","name":"Bermondsey"}}. jadpo sql bakery_map prints its one statement, a SELECT of the properties' columns and the geometry's.

The result type names the properties type, as List<Nearest<T>> names its records. geometry: names the stored field that is each feature's geometry, a G every record has. It has to be named because an entity may hold several geometry fields, and the result type can't say which. P must be an Object type, as a feature's properties always are (Interop). Its fields are filled from the record's fields of the same names, as any result type's are.

The query picks fields and computes nothing, so it is a bounded list like any other: order: and limit:, with geo. conditions and an order by geo.distance if it needs them (Queries).

  • geometry: names a field of the entity read, of exactly the layer's own geometry type, and never none, since a feature always has a geometry. So a Point? field, a Polygon field in a layer of Points, or any field in a layer of Geometry (which would be a value built from it) is query.geometry_field. A field the entity doesn't have is query.unknown_field, as in a condition.
  • One bounded list. order: and limit: are required. page: is refused, since a FeatureCollection has no cursor, and so are missing:, include:, group: and aggregate:, since a layer's properties are each record's own fields, one feature to a record.
  • Its properties. Each field of P must be the record's field of that name, or the query is query.result_field. A feature's identity goes in its properties, so P picks id where the map needs it.
  • Field policy. A layer is sent as it is, so it is held to what an Output is held to (Field restrictions). Every caller who may read the records must be allowed to read each property, the geometry, and every field the conditions and order test, or the query is policy.output_field_unproved. This is proved where the query is declared, so an action returning the layer carries nothing more.
  • Only what a GET can ask for. A GET's input is its query string (One input), which can't hold a BoundingBox. So a layer of only what is in view would still need a body. The teaching example 21-veg-box-rounds sends its whole drop point layer with GET /drop-points.

A property the record has no field for:

example
type BakeryName = Text {    max_length: 80}entity Bakery {    id: Uuid    name: BakeryName    spot: Point}type BakeryPin = Object {    bakery_id: Bakery.id    name: BakeryName}query bakery_map() -> FeatureCollection<Point, BakeryPin> {    from: Bakery    geometry: Bakery.spot    order: [Bakery.name asc]    limit: 500}
text
error: `BakeryPin.bakery_id` isn't a field of `Bakery` --> main.jadpo:16:23   |16 | query bakery_map() -> FeatureCollection<Point, BakeryPin> {   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `bakery_id` can't be filled  reason: A map layer fills its features' properties from `Bakery`'s fields of the same name (`id`, `name` and `spot`); nothing gives `bakery_id`.  next: Remove `bakery_id` from `BakeryPin`, or name it after a field of `Bakery` (agent)  rule: query.result_field  help: https://jadpo.dev/docs/errors/query.result_fieldcheck failed: 1 error, 1 file

A property only the server may read:

example
type BakeryName = Text {    max_length: 80}type PhoneText = Text {    max_length: 20}entity Bakery {    id: Uuid    name: BakeryName    spot: Point    owner_phone: PhoneText {        policy: FieldAccess.server_only    }    policy: {        read: [Access.public]    }}type BakeryPin = Object {    name: BakeryName    owner_phone: PhoneText}query bakery_map() -> FeatureCollection<Point, BakeryPin> {    from: Bakery    geometry: Bakery.spot    order: [Bakery.name asc]    limit: 500}
text
error: `bakery_map` sends `Bakery.owner_phone`, which not every caller may read --> main.jadpo:27:23   |27 | query bakery_map() -> FeatureCollection<Point, BakeryPin> {   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ carries `owner_phone`  reason: Anyone `Bakery`'s `read:` lets in may call `bakery_map`, anyone at all among them, and no client may read it, as it is `FieldAccess.server_only` `owner_phone`. An output has one shape for everyone, so a field some callers may not read can't be in it.  next: Leave `owner_phone` out of `BakeryPin`, the features' properties (agent)  rule: policy.output_field_unproved  help: https://jadpo.dev/docs/errors/policy.output_field_unprovedcheck failed: 1 error, 1 file

Markdown for agents: /docs/a-query-giving-a-map-layer.md.Built from Jadpo main at a707efb.