Services

Call your app's API and other functions directly, and access files, KV, graph data, and authentication.

Docs menu

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.