An app calls a function another app offers (substrio.apps.<app>.<entry>(...args)), or reads a
query another app offers (substrio.apps.<app>.query.<name>(...args)). Both sides must say so: the owner
offers, the user declares the use. Each app sets only its own word.
The word
In substrio.toml, sent by substrio deploy (operation apps.calls.set, PUT /v1/_app_calls; read back with
apps.calls.get, GET /v1/_app_calls):
# The owner (app "people")
[apps.offer]
peopleByEmail = ["shop", "@account"] # the function, offered to app "shop" and every app of this account
[apps.query.byEmail]
collection = "users"
where = ["email"] # equality on each field, bound to argument i; each declared indexed
select = ["name", "email"] # the only fields a row answers
to = ["shop"]
limit = 100 # optional: at most this many rows (absent: every match)
# The user (app "shop")
[apps.use]
people = ["peopleByEmail"]
[apps.use_query]
people = ["byEmail"] # or { byEmail = ["name"] } to read only some selected fields
"@account" offers to every app of the owner's own account. The document is validated whole: a bad one is
refused with the path of the bad member and the word already in force stays. A word is stored before it is
answered, so a crash after the answer keeps it.
Naming an app that does not exist yet is allowed (deploy order is free): the answer carries
"warnings": [{"code": "APP_USE_UNKNOWN_APP", "app": "<slug>"}].
Using it
// Held once at the top of the file: deploys and runs as fast as a call made inside the handler.
const byEmail = substrio.apps.people.query.byEmail;
module.exports = async function (request) {
const rows = byEmail('[email protected]'); // [{ name: 'Ann', email: '[email protected]' }]
const person = substrio.apps.people.peopleByEmail('[email protected]');
return Response.json({ rows, person });
};
- A read runs straight on the owner's store as an index seek, with no code of the owner run. Its rows are plain objects in the reader's own heap, holding only the selected fields.
- A call runs the owner's function in the owner's own isolate, with the owner's limits. Arguments and the answer must be plain data.
- An app that names no other app has no
substrio.appsat all (typeof substrio.apps === 'undefined'). - A sandbox reaches the other app's sandbox of the same name, never its live data. A test app reaches only test apps.
- A change to either side's word, or a deploy of either app, applies to the next call or read, including one held in a variable.
- Every call and read is counted on both apps (
APP_CALLS).
Refusals
A refused call or read throws an Error whose name and code are the code below, with a message naming the
app.
| code | when |
|---|---|
app_call_unknown_app / app_query_unknown_app |
no app has that slug (never made, or deleted) |
app_call_unavailable / app_query_unavailable |
the app exists but is suspended, or its database could not be opened |
app_call_not_offered / app_query_not_offered |
the owner offers no such entry or query |
app_call_not_granted / app_query_not_granted |
offered, but not to this app, or this app no longer declares it |
app_query_bad_argument |
not one argument per where field, or an argument that is not a string, number or boolean |
app_query_not_servable |
the query's collection is gone, or a where field is no longer declared indexed |
app_query_field_not_exposed |
the reader names a field the owner's offer does not select |
app_call_value_not_copyable |
an argument or the answer is not plain data (a function, promise, class instance, cycle) |
app_call_not_local |
the owner runs where this call cannot reach it |
cpu_timeout / memory_exceeded |
the owner's own CPU or heap limit ended the call (the caller goes on) |
Refusals at apps.calls.set (each answered with its error-table status):
| code | when |
|---|---|
INVALID_ARGUMENT |
the document is malformed (the detail names the path) |
COLLECTION_NOT_FOUND |
an offered query names a collection that does not exist |
APP_QUERY_NOT_INDEXED |
a where field (other than _id) is not declared indexed or unique |
APP_QUERY_FIELD_NOT_EXPOSED |
use_query names a field the owner's granted offer does not select |