Calling the API
const collections = substrio.api("GET", "/v1/_meta/collections");
substrio.api("POST", "/v1/notes", { title: "from a function" }); // an object body is serialised
substrio.api(method, path, body?) calls the app's own API as the app and answers
the parsed JSON body (the error envelope on failure). Email, realtime, vectors
and other operations are reached this way through the operation
registry (GET /v1/_operation_registry).
Calling another function
const out = await substrio.functions.worker({ job: 1 });
const res = await substrio.functions.api.fetch(new Request("http://x/y")); // its fetch(request) entry
substrio.functions.<name> is the app's other function as a plain JS function. It
runs in the same context as the caller: the arguments go in and the result comes
back as live JS values (objects, typed arrays, promises, Responses), with no
serialisation and no new VM. An async callee gives a promise. The callee sees its
own env and secrets, and the caller's env is back when it returns, throws or
settles (an ES module reads its own env by importing it from substrio:env;
a bare global env is not per function, see the builtin modules in index.md). It shares the caller's CPU budget and wall deadline, nests at most 64
deep (the next call throws an error with code
call_depth_exceeded naming the limit) and is refused inside db.transaction
(call_in_transaction). A callee that throws is logged under its own name. Only
the app's own functions are reachable; a function deployed after the caller's
call started is seen from the next call. substrio.usage counts the calls as
direct_calls.
A call can fail with an Error whose code is one of function_not_found,
function_not_loaded (redeployed while a held reference was called),
function_no_plain_entry (the module exports no function; use .fetch), call_depth_exceeded,
call_in_transaction, call_tenant_mismatch, function_compile_failed.
Files
substrio.files.put("avatars/me.png", bytes, { type: "image/png" }); // {path, size, hash, type}
substrio.files.stat("avatars/me.png"); // {path, size, type, cacheControl, public, hash, createdAt, updatedAt} or null
substrio.files.get("avatars/me.png"); // a Uint8Array, or null
substrio.files.remove("avatars/me.png"); // true, or false if it was not there
return substrio.files.response("avatars/me.png"); // a Response serving the stored bytes, or null
return substrio.files.slice("tiles/planet.pmtiles", offset, length); // bytes [offset, offset + length) as a 200, or null
substrio.files is the app's own file store, the same one
/v1/_files uses, reached straight from the function with no base64 and no JSON
round trip. put(path, data, options?) takes an ArrayBuffer, a typed array, a
string (UTF-8) or a Blob; options is { type, cacheControl, public }, all
optional. A path that is not a string throws a TypeError; a bad path string
throws the Error /v1/_files answers for it (KEY_TOO_LARGE over 65535 bytes,
INVALID_ARGUMENT for an empty path, a leading/trailing / or an empty/./..
segment), its message naming FILE_PATH_INVALID; a store error (quota) throws an
Error named after the error code.
response(path, init?) answers a Response whose body is served straight from
the file (no copy into the function's own memory unless it is actually read);
init.range is a Range header's value (bytes=A-B, bytes=A-, bytes=-N):
satisfiable gives 206 with Content-Range, otherwise 416 with
Content-Range: bytes */size and no body. Every file response carries
Accept-Ranges: bytes and the stored Content-Type/Cache-Control unless
init.headers names its own.
slice(path, offset, length, init?) serves bytes [offset, offset + length) of
the file as the whole resource: init.status (200 by default), the stored
Content-Type/Cache-Control, and no Content-Range, so a browser or CDN
keeps it as the answer to a request that sent no Range (one tile out of one
archive, cached as long as its Cache-Control says). Past the end of the file
or of length 0 it answers 416 with Content-Range: bytes */size and no body;
an offset or length that is not a number throws a TypeError, and a number
that is not a whole number from 0 to 2^53 - 1 throws the Error INVALID_ARGUMENT.
Other members
substrio.kv("cache"); // the same as db.kv
substrio.graph("follows"); // the same as db.graph
substrio.tenant.slug; // the app slug; substrio.tenant.sandbox is null or the sandbox name
env.MY_SECRET; // env is its own global, not a member of substrio
substrio.auth.getCurrentUser(); // the caller who started this run: { user_id, email_verified, expires_at } or null (a cron, a queue job, a trigger, an app-key call)
substrio.auth.checkPermission("read"); // true iff the caller holds this app's declared permission "read"
substrio.request.path; // this call's request as text: path, method, body
substrio.search("notes", "hello", { limit: 5 }); // text, vector or hybrid search
The older object API is substrio.db.query, get, insert, update, delete,
count, insertMany, aggregate and collections; prefer db.
substrio.collection(name) gives the Cloudflare-shaped collection helper
(add, get, set, update, delete, find answering { data, count }).
Also substrio.webhooks (create, list, remove, emit), substrio.plans
(get, set), substrio.usage (consume, get), substrio.lua.eval(script, keys, argv),
substrio.batch(operations) and substrio.collections().
Inside db.transaction
A service that writes outside the transaction or runs another function throws
an error saying it is not available inside db.transaction: substrio.functions.<name>
calls, substrio.api writes, substrio.db writes, webhooks create, remove and emit,
plans.set, usage.consume and lua.eval. Call them outside the callback.