These services are available through substrio.
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 API as the app and returns
the parsed JSON body, or the error envelope on failure. Email, realtime, vector,
and other operations are available 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> calls another function in the app as a plain JavaScript
function. It runs in the caller's context. Arguments and results remain live JS
values, including objects, typed arrays, promises, and Responses, without
serialization or a new VM. An async callee returns a promise. The callee receives
its env and secrets, and the caller's env is restored when it returns, throws,
or settles. An ES module must import env from substrio:env to use the
per-function value. A bare global env is not per function; see the builtin
modules in index.md.
The callee shares the caller's CPU budget and wall deadline. Calls can nest up
to 64 deep; the next call throws call_depth_exceeded. Calls are refused inside
db.transaction with call_in_transaction. A thrown error is logged under the
callee's name. Only functions in the app are reachable. A function deployed after
the caller's call starts is available from the next call. substrio.usage counts
these 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 accesses the same app file store as /v1/_files, directly from
the function without a base64 or 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?) returns a Response whose body is served directly from
the file, without copying it into the function's memory unless it is 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) provides the Cloudflare-shaped collection helper
(add, get, set, update, delete, and find, which returns { data, count }).
Other members include substrio.webhooks (create, list, remove, emit), substrio.plans
(get, set), substrio.usage (consume, get), substrio.lua.eval(script, keys, argv)
(not in the current release: it throws, as Lua comes with the Redis frontend, SUBSTRIO_BUILD_REDIS),
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.