Zyora Labs
zMesh / Edge Functions

Edge Functions

Sandboxed TypeScript that runs on demand. Write a handler, deploy, then call it from anywhere — your frontend, a cron job, or a webhook source.

Write your first handler

Every function is a single TypeScript module that exports a default async handler.

ts
export default async function handler(req: {
  method: string;
  headers: Record<string, string>;
  query: Record<string, string>;
  body: string | null;
  user: { id: string; email?: string } | null;
  env: Record<string, string>;
}) {
  console.log("invoked", req.method);
  return {
    status: 200,
    headers: { "x-fn": "zmesh" },
    body: { ok: true, you_sent: req.body, you_are: req.user },
  };
}

The runtime is Deno with --allow-net only. No filesystem, no subprocess, no Deno.env. Use the env field on req instead — it's populated from the per-function env vars you configure in Settings.

Deploy from the console

Code is saved as a draft on every edit. The public URL only updates when you click Deploy.

  1. Open Project → Edge Functions and click New function.
  2. Edit the handler in the Code tab, then hit Save.
  3. Use the Test tab to invoke the current draft with a sample request.
  4. Hit Deploy to publish — the version counter bumps and the public URL goes live.
Call from the browser SDK

The @zmesh/client SDK ships a typed invoker that auto-attaches the auth token.

ts
import { createClient } from "@zmesh/client";

const zmesh = createClient({
  url: "<api-url>",
  appId: "<your-app-id>",
  projectId: "<your-project-id>",   // required only for functions
});

const res = await zmesh.functions.invoke("send-welcome", {
  body: { to: "alice@example.com" },
});

if (res.ok) {
  console.log(res.status, res.data);
} else {
  console.error("function failed", res.status, res.body);
}

When the user is signed in, the SDK attaches their bearer token automatically. Auth-gated functions reject anonymous callers with 401.

Call from any HTTP client

Edge functions are plain HTTP endpoints. cURL, Postman, server code — anything works.

bash
# Public function — no headers required
curl -X POST <api-url>/v1/fn/<your-project-id>/send-welcome \
  -H "Content-Type: application/json" \
  -d '{"to":"alice@example.com"}'

# Auth-required function — pass app id + user bearer token
curl -X POST <api-url>/v1/fn/<your-project-id>/private-fn \
  -H "X-App-Id: <your-app-id>" \
  -H "Authorization: Bearer <user-jwt>" \
  -H "Content-Type: application/json" \
  -d '{"hi":1}'
Public vs auth-required

Toggle this in the function's Settings tab.

Public — anyone with the URL can invoke. Use for webhooks, public APIs, healthchecks. req.user is null.
Auth required — caller must send X-App-Id + a valid end-user bearer token from this project. req.user contains { id, email }.
Environment variables

Per-function key/value pairs.

ts
// In Settings → Environment variables, save:
//   { "RESEND_API_KEY": "re_abc123" }

export default async function handler(req) {
  const apiKey = req.env.RESEND_API_KEY;
  const r = await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ from: "me@x.com", to: req.body }),
  });
  return { status: r.status, body: await r.json() };
}

Values are stored at rest and only injected into the sandbox for the duration of the invocation.

Runtime limits

Tunable per function in the Settings tab.

  • Timeout — 100 ms to 60 s (default 10 s). Exceeding it returns 504.
  • Memory — 64 MB to 1024 MB (default 256 MB). Caps the V8 old-generation heap.
  • Source size — 256 KB max.
  • Permissions — outbound HTTP only. No filesystem, env, subprocess, FFI or worker access.
Response shape

The handler's return value becomes the HTTP response.

ts
return {
  status: 201,                              // optional, defaults to 200
  headers: { "x-fn": "zmesh" },             // optional
  body: { ok: true },                       // object → JSON-stringified,
                                            // string → sent as-is
};

Default Content-Type is application/json unless you override it in headers.

Observability

Every invocation is recorded.

Open the function's Logs tab to see the last 50 calls — status code, method, duration, captured console.log output and any thrown errors.