Select

A select endpoint is a read: it returns a table of records from one collection. It narrows the collection with a filter, can take typed parameters from the query string, sorts and limits the result, and chooses which columns to return, optionally under public names. It is the datahoster-endpoint-select.json file in an endpoint folder; its presence makes that endpoint answer a GET.

{
  "datahoster-endpoint-select-collection": "messages",
  "datahoster-endpoint-select-columns": [
    {
      "datahoster-endpoint-select-column-of": "name",
      "datahoster-endpoint-select-column-as": "sender"
    },
    {
      "datahoster-endpoint-select-column-of": "message"
    }
  ],
  "datahoster-endpoint-select-parameters": [
    {
      "datahoster-endpoint-select-parameter-name": "since",
      "datahoster-endpoint-select-parameter-type": "number"
    }
  ],
  "datahoster-endpoint-select-filter": [
    ["at", "ge", { "param": "since" }]
  ],
  "datahoster-endpoint-select-sort": [
    ["at", "desc"]
  ],
  "datahoster-endpoint-select-limit": 100,
  "datahoster-endpoint-select-public": false,
  "datahoster-endpoint-select-users": ["you"]
}
An inbox: messages since since, newest first, readable only by you.

Contents

The file is one JSON object; its keys are fixed names starting with datahoster-endpoint-select-. The example above, in an inbox/ endpoint, reads messages since a given time, newest first, readable only by you.

NameTypeDescription
datahoster-endpoint-select-collectiontextThe collection this endpoint reads. Required.
datahoster-endpoint-select-columnsarrayThe columns to return, each optionally renamed. Defaults to every column.
datahoster-endpoint-select-parametersarrayTyped inputs the caller supplies in the query string. Optional.
datahoster-endpoint-select-filterarrayConditions a record must all match. Optional.
datahoster-endpoint-select-sortarrayA list of [column, "asc"|"desc"] keys over the in-memory columns, applied in order. Optional.
datahoster-endpoint-select-limitintegerThe most records to return. The server also caps every read. Optional.
datahoster-endpoint-select-publicbooleanWhether anyone may call it, with no token. Defaults to false.
datahoster-endpoint-select-usersarrayThe users who may call it, each proving a token. Defaults to [].

datahoster-endpoint-select-columns

The columns to return, in order. Each element names a collection column with -column-of and may give it a public name with -column-as; without -as, the column's own name is used. A column the filter tests need not be listed here, so you can filter on a column you do not return.

NameTypeDescription
datahoster-endpoint-select-column-oftextThe column of the collection to return. Required.
datahoster-endpoint-select-column-astextThe name it takes in the response. Defaults to the column's own name.

datahoster-endpoint-select-parameters

Typed inputs the caller supplies in the query string. A filter condition binds one with { "param": "name" } in place of a literal. A parameter with a -parameter-default is optional and falls back to that value; one without is required, and omitting it is a 400. A supplied value is coerced to the parameter's type.

NameTypeDescription
datahoster-endpoint-select-parameter-nametextThe query-string key the caller supplies. Required.
datahoster-endpoint-select-parameter-typetextAny column type. The supplied value is coerced to it. Required.
datahoster-endpoint-select-parameter-defaultthe parameter's typeA default value. Its presence makes the parameter optional. Optional.

datahoster-endpoint-select-filter

A list of conditions, each [column, op, value] over a column of the collection. The value is a literal or { "param": "name" } to bind a parameter. A record is returned only if it matches all conditions. The value is compared with the record's typed value, so ["handled", "eq", false] tests the boolean, not the string.

A filter tests the columns the server holds in memory: the numeric columns (integer, number, boolean, int128), the fixed-size char columns (char8 to char1024), and the automatic id, at and by. A text column keeps its value on disc and cannot be filtered or sorted, and every column named in a filter, sort, returned-column, field or forced value must exist — an endpoint that filters or sorts on a text column, or names a column its collection does not have, fails to sync with a message naming the endpoint and column. The contains and prefix operators match on bytes, so they apply to a char column (and to by).

OpKeeps a record when its column is…
eqequal to the value
nenot equal to the value
lt / leless than / at most the value
gt / gegreater than / at least the value
containsa char value that contains the value's bytes
prefixa char value that starts with the value's bytes

HTTP request

GET the endpoint's path, at <domain>/<endpoint>, supplying each declared parameter as a query-string value. If the endpoint is not public, send the user's token as a bearer token. The response is a JSON array of records, each an object under the public column names. A request that is not allowed — no token where one is needed, a wrong token, or a user this endpoint does not list — gets the same 404 as a path with no endpoint.

GET https://api.example.com/inbox?since=1782864000000
Authorization: Bearer k7Qw2f9xJ4mB6pR8tZ1nD3sV5hL0aYc
Reading the inbox endpoint as you, since a millisecond time.
StatusMeaning
200A JSON array of matching records, with an ETag header.
304The request carried a matching If-None-Match and the result has not changed, so no body is sent.
400A required parameter is missing, or a value fails its type.
404No select endpoint answers on this path, or the caller is not allowed it — a missing token, an invalid one, or a user the endpoint does not list. The two are indistinguishable, so a probe cannot tell the endpoint is there.

Change detection

Every select response carries an ETag: a short tag that stands for exactly the rows the endpoint returned. It changes whenever an insert, update or delete alters that result, and stays the same when nothing in the result moved, even if the rest of the collection changed. So a caller can tell whether a view is worth re-reading without downloading it.

There are two ways to use it. Send the last tag back in If-None-Match on a GET and the server answers 304 Not Modified, with no body, when the result is unchanged. Or send a HEAD to get the current tag on its own. A browser does the first automatically, so a page that fetches the endpoint re-downloads it only when it has really changed; a build script or a site's search box can use either.

HEAD https://api.example.com/list
Asking for the current ETag only, no body.
GET https://api.example.com/list
If-None-Match: "fef5c63a045d0b1ecd11f228"
A conditional read: 304 when the result has not changed since that tag.