Services

Call the function's own app as its API, call another function directly, and reach its files, kv, graph and auth from one object.

Docs menu

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.