<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Functions on backd</title><link>https://fernandezvara.github.io/backd/docs/functions/</link><description>Recent content in Functions on backd</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://fernandezvara.github.io/backd/docs/functions/index.xml" rel="self" type="application/rss+xml"/><item><title>Quickstart</title><link>https://fernandezvara.github.io/backd/docs/functions/quickstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/quickstart/</guid><description>&lt;p&gt;You will create a config repository with a working sample function, run it locally, call it three ways, write your own, see what it did, and test it. You need Docker (or Podman) and the &lt;code&gt;backd&lt;/code&gt; binary; Deno is optional, because the build can run in Deno&amp;rsquo;s own image.&lt;/p&gt;
&lt;h2 id="1-create-a-project"&gt;1. Create a project&lt;a class="hd-anchor" href="#1-create-a-project" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backd template project --dir ./shop --realm shop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; shop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This writes a complete repository: the &lt;code&gt;shop&lt;/code&gt; realm with a sample database, a sample function with a test, Dockerfiles, a &lt;code&gt;compose.yaml&lt;/code&gt; for local development (MongoDB, &lt;code&gt;backd&lt;/code&gt;, the functions executor and the egress proxy) and a CI workflow. Everything you need is in the generated &lt;code&gt;README.md&lt;/code&gt;; the parts below follow it.&lt;/p&gt;</description></item><item><title>Writing a function</title><link>https://fernandezvara.github.io/backd/docs/functions/writing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/writing/</guid><description>&lt;h2 id="layout"&gt;Layout&lt;a class="hd-anchor" href="#layout" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Each database may have one &lt;code&gt;_functions&lt;/code&gt; directory: a Deno project for that application&amp;rsquo;s functions.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;realm&amp;gt;/&amp;lt;database&amp;gt;/
 posts/ collections, as usual
 _functions/
 deno.json the project&amp;#39;s Deno config (imports), optional
 deno.lock pinned dependency versions, written by Deno
 lib/ code shared by the functions (not a function)
 stats/ one folder per function, named like collections
 function.yaml how the function runs (all keys optional)
 index.ts the code: index.ts or index.js
 input.schema.json optional JSON Schema of the input
 output.schema.json optional JSON Schema of the output
 .build/ bundles, written by `backd functions build`
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Function names follow the same rules as collection names; &lt;code&gt;lib&lt;/code&gt; is reserved for shared code.&lt;/li&gt;
&lt;li&gt;The module&amp;rsquo;s &lt;strong&gt;default export&lt;/strong&gt; is the function: it receives the call&amp;rsquo;s context and returns the output (any JSON value).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;input&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;email_verified&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;: &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hello&lt;/span&gt;: &lt;span class="kt"&gt;ctx.user?.email&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create a starter with:&lt;/p&gt;</description></item><item><title>Calling functions</title><link>https://fernandezvara.github.io/backd/docs/functions/calling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/calling/</guid><description>&lt;h2 id="calling-a-function"&gt;Calling a function&lt;a class="hd-anchor" href="#calling-a-function" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST https://api.example.com/v1/shop/orders/_func/checkout &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;cart&amp;#34;: &amp;#34;c1&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → 200 {&amp;#34;order&amp;#34;: &amp;#34;o7&amp;#34;, &amp;#34;total&amp;#34;: 1250}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;The body is the function&amp;rsquo;s input (&lt;code&gt;ctx.input&lt;/code&gt;), any JSON value; an empty body is &lt;code&gt;null&lt;/code&gt;. The answer is its output. &lt;strong&gt;&lt;code&gt;webhook&lt;/code&gt; functions are different:&lt;/strong&gt; see &lt;a href="../webhooks/"&gt;Webhooks&lt;/a&gt; — the raw body reaches &lt;code&gt;ctx.request.body&lt;/code&gt; unparsed, &lt;code&gt;Content-Type&lt;/code&gt; isn&amp;rsquo;t required to be JSON, and the function&amp;rsquo;s own response is returned as-is.&lt;/li&gt;
&lt;li&gt;In realms with &lt;code&gt;auth: enabled&lt;/code&gt;, &lt;a href="../../auth/api-keys/"&gt;API keys&lt;/a&gt; may always call; other callers need the function&amp;rsquo;s &lt;a href="../reference/#invoke-rules"&gt;&lt;code&gt;invoke&lt;/code&gt; rule&lt;/a&gt; (&lt;code&gt;webhook&lt;/code&gt; always needs one that allows anonymous callers). With &lt;code&gt;X-Backd-On-Behalf-Of&lt;/code&gt;, an API key calls as that user.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;input.schema.json&lt;/code&gt;, invalid input answers &lt;code&gt;400 validation_error&lt;/code&gt;; with &lt;code&gt;output.schema.json&lt;/code&gt;, an output that doesn&amp;rsquo;t match answers &lt;code&gt;500 invalid_output&lt;/code&gt; (and is logged). Neither applies to &lt;code&gt;webhook&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;Idempotency-Key&lt;/code&gt; header reaches the function as &lt;code&gt;ctx.idempotencyKey&lt;/code&gt;, and also deduplicates the call itself (see &lt;a href="#idempotency"&gt;Idempotency&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync&lt;/code&gt; functions run immediately and answer with their output, as above. &lt;code&gt;async&lt;/code&gt; functions (&lt;a href="../jobs/"&gt;below&lt;/a&gt;) answer &lt;code&gt;202&lt;/code&gt; with a job to poll. &lt;code&gt;webhook&lt;/code&gt; functions (&lt;a href="../webhooks/"&gt;below&lt;/a&gt;) set their own response.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Answer&lt;/th&gt;
 &lt;th&gt;When&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A &lt;code&gt;sync&lt;/code&gt; function returned; the body is its output&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;202&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;An &lt;code&gt;async&lt;/code&gt; function was queued; poll &lt;code&gt;Location&lt;/code&gt; for its result (see &lt;a href="../jobs/"&gt;Async jobs&lt;/a&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;em&gt;(the function&amp;rsquo;s own choice)&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;A &lt;code&gt;webhook&lt;/code&gt; function&amp;rsquo;s own response (see &lt;a href="../webhooks/"&gt;Webhooks&lt;/a&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;4xx&lt;/code&gt; with the function&amp;rsquo;s code&lt;/td&gt;
 &lt;td&gt;The function threw &lt;code&gt;ctx.error(status, code, message)&lt;/code&gt; (for &lt;code&gt;async&lt;/code&gt;, this lands in the job&amp;rsquo;s &lt;code&gt;result&lt;/code&gt; instead; &lt;code&gt;webhook&lt;/code&gt; functions answer errors through their own response, not &lt;code&gt;ctx.error&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;400 validation_error&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The input doesn&amp;rsquo;t match &lt;code&gt;input.schema.json&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;400 idempotency_key_required&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;function.yaml&lt;/code&gt; has &lt;code&gt;idempotency: required&lt;/code&gt; and the call has no &lt;code&gt;Idempotency-Key&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;401&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The &lt;code&gt;invoke&lt;/code&gt; rule doesn&amp;rsquo;t allow the caller&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;404 not_found&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;No such function&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;409 request_in_progress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A call with this &lt;code&gt;Idempotency-Key&lt;/code&gt; is still running (see &lt;a href="#idempotency"&gt;Idempotency&lt;/a&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;422 idempotency_key_reused&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;This &lt;code&gt;Idempotency-Key&lt;/code&gt; was already used with different input&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;429 too_many_requests&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;A &lt;code&gt;sync&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; function, or its realm, is at its &lt;a href="#concurrency-limits"&gt;concurrency limit&lt;/a&gt; on this instance (&lt;code&gt;Retry-After: 1&lt;/code&gt;); or the caller is over the function&amp;rsquo;s &lt;a href="#rate-limits"&gt;rate limit&lt;/a&gt; (&lt;code&gt;Retry-After&lt;/code&gt; set to when it resets)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;500 function_failed&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The function threw another error, crashed, or went over its memory or CPU limit. The stack trace is in &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s log only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;500 invalid_output&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The output is larger than &lt;code&gt;max_output&lt;/code&gt;, or doesn&amp;rsquo;t match &lt;code&gt;output.schema.json&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;500 secret_missing&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The function declares a secret that isn&amp;rsquo;t set for its database or realm&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;503 unavailable&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The executor isn&amp;rsquo;t configured, doesn&amp;rsquo;t answer, or runs its maximum of functions (&lt;code&gt;Retry-After: 1&lt;/code&gt;); or a &lt;code&gt;webhook&lt;/code&gt; function is at its &lt;a href="#concurrency-limits"&gt;concurrency limit&lt;/a&gt; (&lt;code&gt;Retry-After: 1&lt;/code&gt; — &lt;code&gt;503&lt;/code&gt;, not &lt;code&gt;429&lt;/code&gt;, since webhook senders retry on 5xx)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;504 function_timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The function didn&amp;rsquo;t finish within its &lt;code&gt;timeout&lt;/code&gt;; it was stopped&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="from-the-javascript-client-and-the-command-line"&gt;From the JavaScript client and the command line&lt;a class="hd-anchor" href="#from-the-javascript-client-and-the-command-line" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The same call, three ways:&lt;/p&gt;</description></item><item><title>function.yaml reference</title><link>https://fernandezvara.github.io/backd/docs/functions/reference/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/reference/</guid><description>&lt;h2 id="functionyaml"&gt;function.yaml&lt;a class="hd-anchor" href="#functionyaml" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Every key is optional. &lt;code&gt;backd&lt;/code&gt; reads the file strictly: an unknown key or an invalid value stops startup, naming the file.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Key&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;sync&lt;/code&gt;: the caller waits for the output. &lt;code&gt;async&lt;/code&gt;: the caller gets a &lt;a href="../jobs/"&gt;job&lt;/a&gt; to check later. &lt;code&gt;webhook&lt;/code&gt;: for callbacks from other services (see &lt;a href="../webhooks/"&gt;Webhooks&lt;/a&gt;) — needs &lt;code&gt;invoke&lt;/code&gt; set to a rule that allows anonymous callers&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10s&lt;/code&gt; (&lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;webhook&lt;/code&gt;), &lt;code&gt;15m&lt;/code&gt; (&lt;code&gt;async&lt;/code&gt;)&lt;/td&gt;
 &lt;td&gt;The function is stopped after this long. At least &lt;code&gt;1s&lt;/code&gt;; at most &lt;code&gt;60s&lt;/code&gt; for &lt;code&gt;sync&lt;/code&gt; and &lt;code&gt;webhook&lt;/code&gt;, &lt;code&gt;24h&lt;/code&gt; for &lt;code&gt;async&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;128MB&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Memory for the function&amp;rsquo;s JavaScript heap, from &lt;code&gt;32MiB&lt;/code&gt; to &lt;code&gt;4GiB&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;max_output&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1MiB&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;The largest output accepted, from &lt;code&gt;1KiB&lt;/code&gt; to &lt;code&gt;64MiB&lt;/code&gt; (&lt;code&gt;15MiB&lt;/code&gt; for &lt;code&gt;async&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;concurrency&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;How many calls of the function may run at once &lt;strong&gt;on each &lt;code&gt;backd&lt;/code&gt; instance&lt;/strong&gt; (1–1000). With several instances, up to that many times this value run at once&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;rate_limit&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;Calls allowed per caller, counted across instances: &lt;code&gt;{per: user | ip, limit: &amp;lt;n&amp;gt;, window: &amp;lt;duration&amp;gt;}&lt;/code&gt;, window from &lt;code&gt;1s&lt;/code&gt; to &lt;code&gt;24h&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;idempotency&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;optional&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Whether calls must send an &lt;code&gt;Idempotency-Key&lt;/code&gt; header: &lt;code&gt;optional&lt;/code&gt; or &lt;code&gt;required&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;invoke&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;Who may call the function (see below). Without it, only &lt;a href="../../auth/api-keys/"&gt;API keys&lt;/a&gt; may call it&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;admin&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt; gives the function full access to the realm&amp;rsquo;s data, whatever the caller may do. Keep it off unless the function needs it&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;internal&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt; removes the function&amp;rsquo;s HTTP route: &lt;code&gt;_func/&amp;lt;name&amp;gt;&lt;/code&gt; answers &lt;code&gt;404&lt;/code&gt; to everyone, like a function that doesn&amp;rsquo;t exist. Only &lt;code&gt;backd&lt;/code&gt; and scheduled runs may call it (see &lt;a href="../internal/"&gt;Internal functions&lt;/a&gt;). Can&amp;rsquo;t be combined with &lt;code&gt;invoke&lt;/code&gt; or &lt;code&gt;mode: webhook&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;dev_only&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt; makes &lt;code&gt;backd&lt;/code&gt; refuse to start with the function unless &lt;code&gt;BACKD_DEV=true&lt;/code&gt;: for development helpers such as the &lt;a href="../email/#developing-without-a-provider"&gt;email capture&lt;/a&gt; function, which must never run in production&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;true&lt;/code&gt; gives the function &lt;code&gt;ctx.email.send()&lt;/code&gt;: &lt;a href="../email/#sending-email-from-functions"&gt;custom emails&lt;/a&gt; through the realm&amp;rsquo;s templates and delivery function, to any recipient, within the caps of &lt;code&gt;email.limits&lt;/code&gt;. Needs &lt;code&gt;email&lt;/code&gt; in &lt;code&gt;realm.yaml&lt;/code&gt;; read the warning on that page first&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;calls&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;The functions of the same database this one lists as callable (see &lt;a href="../internal/#declaring-which-functions-it-may-call"&gt;Internal functions&lt;/a&gt;); startup rejects unknown names, cycles and chains longer than 4 functions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;retry&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;Tries a failed &lt;code&gt;async&lt;/code&gt; function again: &lt;code&gt;{attempts, backoff, max_backoff}&lt;/code&gt; (see &lt;a href="../jobs/#retrying-a-failed-job"&gt;Retrying a failed job&lt;/a&gt;). A single attempt unless &lt;code&gt;attempts&lt;/code&gt; is above 1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;on_complete&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;For an &lt;code&gt;async&lt;/code&gt; function: &lt;code&gt;{function, on?}&lt;/code&gt; queues that async function of the same database when a job of this one ends, with the outcome as its input; &lt;code&gt;on&lt;/code&gt; narrows it to &lt;code&gt;ok&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;cancelled&lt;/code&gt;. See &lt;a href="../jobs/#be-told-when-it-ends-on_complete"&gt;Be told when it ends&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;schedule&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;A cron expression: the function &lt;a href="../cron/"&gt;runs on a schedule&lt;/a&gt;. Needs &lt;code&gt;mode: async&lt;/code&gt; and at least one running &lt;a href="../running/#running-the-worker"&gt;worker&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;timezone&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;UTC&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;For a scheduled function: the IANA time zone the &lt;a href="../cron/#time-zones"&gt;schedule&lt;/a&gt; is read in, such as &lt;code&gt;Europe/Madrid&lt;/code&gt;. Daylight saving changes are handled (documented there). Needs &lt;code&gt;schedule&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;overlap&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;For a scheduled function: &lt;code&gt;skip&lt;/code&gt; doesn&amp;rsquo;t run a scheduled time while the function&amp;rsquo;s previous job hasn&amp;rsquo;t finished, and records the time as a &lt;a href="../cron/#skipping-a-run-while-the-previous-one-is-going"&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/a&gt; job. Needs &lt;code&gt;schedule&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;secrets&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;Secrets the function may read: &lt;code&gt;NAME&lt;/code&gt; (its own database&amp;rsquo;s) or &lt;code&gt;realm.NAME&lt;/code&gt; (the realm&amp;rsquo;s). Names are upper-case letters, digits and &lt;code&gt;_&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;network&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;Hosts the function may call: &lt;code&gt;host&lt;/code&gt; or &lt;code&gt;host:port&lt;/code&gt;, lower case, no scheme, path or wildcard. Without a list, a function can only call &lt;code&gt;backd&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Durations are written as in &lt;a href="../../configuration/realm/"&gt;&lt;code&gt;realm.yaml&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;30s&lt;/code&gt;, &lt;code&gt;15m&lt;/code&gt;, &lt;code&gt;1d&lt;/code&gt;). Sizes need a unit: &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;KB&lt;/code&gt;, &lt;code&gt;MB&lt;/code&gt;, &lt;code&gt;GB&lt;/code&gt; (powers of 1000) or &lt;code&gt;KiB&lt;/code&gt;, &lt;code&gt;MiB&lt;/code&gt;, &lt;code&gt;GiB&lt;/code&gt; (powers of 1024).&lt;/p&gt;</description></item><item><title>Async jobs</title><link>https://fernandezvara.github.io/backd/docs/functions/jobs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/jobs/</guid><description>&lt;p&gt;&lt;code&gt;mode: async&lt;/code&gt; runs a function in the background instead of within the request. The call answers &lt;code&gt;202&lt;/code&gt; at once with a &lt;strong&gt;job&lt;/strong&gt;, and a &lt;a href="../running/#running-the-worker"&gt;worker&lt;/a&gt; runs the function later, on its own schedule and for as long as its &lt;code&gt;timeout&lt;/code&gt; allows.&lt;/p&gt;
&lt;p&gt;Use it when the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;takes more than a few seconds (a &lt;code&gt;sync&lt;/code&gt; call is capped at 60 s, and a caller shouldn&amp;rsquo;t be made to hold a request open for anything close to that);&lt;/li&gt;
&lt;li&gt;must survive the caller going away (a browser tab closed, a mobile app suspended);&lt;/li&gt;
&lt;li&gt;can be retried safely if something goes wrong;&lt;/li&gt;
&lt;li&gt;fans out (a batch of emails, a report over many documents, a slow third-party call).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use a &lt;code&gt;sync&lt;/code&gt; function when the caller needs the answer to continue and it&amp;rsquo;s quick. Use &lt;a href="../cron/"&gt;cron&lt;/a&gt; when nobody asks for the work: it just has to happen on a schedule.&lt;/p&gt;</description></item><item><title>Scheduled functions</title><link>https://fernandezvara.github.io/backd/docs/functions/cron/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/cron/</guid><description>&lt;p&gt;Add &lt;code&gt;schedule&lt;/code&gt; to an &lt;code&gt;async&lt;/code&gt; function and it runs by itself, with no caller: nightly cleanups, daily digests, hourly syncs, monthly reports.&lt;/p&gt;
&lt;p class="hd-example-path"&gt;&lt;code&gt;examples/config/workshop/main/_functions/nightly_cleanup/function.yaml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# A scheduled function: `schedule` (cron, UTC) on an async function. There is&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# no caller, so ctx.input is empty, ctx.user is null, and ctx.admin.db (full&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# access, recorded as this function) is always available in a scheduled run.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Needs at least one running worker: `backd worker`, or&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# `backd serve --with-worker`.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# `internal: true`: nobody can call it over HTTP. An administrator can still&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# run it by hand to test it, as its schedule would run it:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# backd functions invoke --function workshop/main/nightly_cleanup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;async&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 3 * * *&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# every day at 03:00 UTC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="hd-example-path"&gt;&lt;code&gt;examples/config/workshop/main/_functions/nightly_cleanup/index.ts&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Internal functions</title><link>https://fernandezvara.github.io/backd/docs/functions/internal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/internal/</guid><description>&lt;p&gt;Some functions must never be reachable from the internet: a clean-up that deletes data, a helper shared by several public functions, a scheduled job nobody should trigger by hand. An &lt;strong&gt;internal&lt;/strong&gt; function has no HTTP route at all.&lt;/p&gt;
&lt;h2 id="marking-a-function-internal"&gt;Marking a function internal&lt;a class="hd-anchor" href="#marking-a-function-internal" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# _functions/cleanup/function.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;async&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;POST /v1/&amp;lt;realm&amp;gt;/&amp;lt;database&amp;gt;/_func/cleanup&lt;/code&gt; now answers &lt;code&gt;404 not_found&lt;/code&gt;, exactly like a function that doesn&amp;rsquo;t exist, to every caller: anonymous, signed in, API keys of either role. Nothing reaches the executor, and the answer doesn&amp;rsquo;t reveal that the function exists.&lt;/p&gt;</description></item><item><title>Email</title><link>https://fernandezvara.github.io/backd/docs/functions/email/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/email/</guid><description>&lt;p&gt;&lt;code&gt;backd&lt;/code&gt; &lt;strong&gt;never talks to a mail server&lt;/strong&gt;. A realm names one &lt;strong&gt;delivery function&lt;/strong&gt;: an &lt;a href="../internal/"&gt;internal&lt;/a&gt;, &lt;code&gt;async&lt;/code&gt; function that receives a finished message and hands it to whatever provider you chose (Postmark, SES, Resend, Mailgun, SendGrid, …). &lt;code&gt;backd&lt;/code&gt; does everything else: the templates, the tokens and links, the retries, the limits.&lt;/p&gt;
&lt;aside class="hd-hint hd-hint--note"&gt;
 &lt;p class="hd-hint-title"&gt;&lt;svg class="hd-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;&lt;circle cx="12" cy="12" r="9"/&gt;&lt;path d="M12 16v-4m0-4h.01"/&gt;&lt;/svg&gt;
Note&lt;/p&gt;
 &lt;code&gt;backd&lt;/code&gt; sends no mail from its own servers or addresses, so &lt;strong&gt;deliverability is your responsibility&lt;/strong&gt;, handled with your provider: a verified sending domain (SPF, DKIM, DMARC), a dedicated sender address, the provider&amp;rsquo;s bounce and complaint handling and suppression lists. Watch your provider&amp;rsquo;s bounce and complaint rates before sending real email, and try the flows with a test mailbox first.
&lt;/aside&gt;

&lt;h2 id="configuration"&gt;Configuration&lt;a class="hd-anchor" href="#configuration" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Email is configured in &lt;a href="../../configuration/realm/"&gt;&lt;code&gt;realm.yaml&lt;/code&gt;&lt;/a&gt;; &lt;code&gt;backd template realm&lt;/code&gt; writes the section commented, with every key explained. A realm without &lt;code&gt;email&lt;/code&gt; sends no email at all.&lt;/p&gt;</description></item><item><title>Webhooks</title><link>https://fernandezvara.github.io/backd/docs/functions/webhooks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/webhooks/</guid><description>&lt;p&gt;&lt;code&gt;mode: webhook&lt;/code&gt; receives a callback from another service — Stripe, GitHub, and the like — and answers it directly: &lt;code&gt;ctx&lt;/code&gt; carries the raw request instead of parsed input, and the function sets the response&amp;rsquo;s status, headers and body itself instead of returning a JSON value backd interprets.&lt;/p&gt;
&lt;aside class="hd-hint hd-hint--warning"&gt;
 &lt;p class="hd-hint-title"&gt;&lt;svg class="hd-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;&lt;path d="M12 3 2 21h20Z"/&gt;&lt;path d="M12 10v4m0 3h.01"/&gt;&lt;/svg&gt;
Warning&lt;/p&gt;
 A webhook function is callable by &lt;strong&gt;anyone on the internet&lt;/strong&gt; (&lt;code&gt;invoke&lt;/code&gt; must allow anonymous callers), so the function itself is the only check. Verify the sender&amp;rsquo;s signature against the raw body before doing anything, and deduplicate by the sender&amp;rsquo;s event id: providers retry.
&lt;/aside&gt;

&lt;p&gt;Payment provider example: &lt;code&gt;payment_webhook&lt;/code&gt; verifies the sender&amp;rsquo;s HMAC signature, deduplicates the provider&amp;rsquo;s retries by event id, and marks the order paid.&lt;/p&gt;</description></item><item><title>Secrets</title><link>https://fernandezvara.github.io/backd/docs/functions/secrets/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/secrets/</guid><description>&lt;p&gt;A function that needs a third-party API key, or any value it shouldn&amp;rsquo;t hold in its own code, declares it in &lt;code&gt;function.yaml&lt;/code&gt;&amp;rsquo;s &lt;code&gt;secrets&lt;/code&gt; list and reads it from &lt;code&gt;ctx.secrets&lt;/code&gt;, decrypted, at call time — never from an environment variable, which the function&amp;rsquo;s process doesn&amp;rsquo;t have.&lt;/p&gt;
&lt;aside class="hd-hint hd-hint--tip"&gt;
 &lt;p class="hd-hint-title"&gt;&lt;svg class="hd-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;&lt;path d="M9 18h6M10 21h4"/&gt;&lt;path d="M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.5 1 2.5h6c0-1 .3-1.8 1-2.5A6 6 0 0 0 12 3Z"/&gt;&lt;/svg&gt;
Best practice&lt;/p&gt;</description></item><item><title>Outbound network</title><link>https://fernandezvara.github.io/backd/docs/functions/network/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/network/</guid><description>&lt;h2 id="calling-an-outside-api"&gt;Calling an outside API&lt;a class="hd-anchor" href="#calling-an-outside-api" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A function reaches the outside world only for hosts it declares. Say it needs to email a digest through a provider&amp;rsquo;s HTTP API: declare the host, declare the key as a &lt;a href="../secrets/"&gt;secret&lt;/a&gt;, and use &lt;code&gt;fetch&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# function.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;async&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;@daily&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;api.mailer.example] &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# host or host:port, lower case; no scheme, path or wildcard&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;MAILER_KEY]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://api.mailer.example/v1/send&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Bearer &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAILER_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;JSON.stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ops@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Digest &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; orders, &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;revenue&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; in revenue`&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;424&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mailer_failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`the mail provider answered &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;A request to any host not in &lt;code&gt;network&lt;/code&gt; fails, and so does one to a private, loopback or link-local address, even for a declared host name that resolves to one.&lt;/li&gt;
&lt;li&gt;Without a &lt;code&gt;network&lt;/code&gt; list, a function can only call &lt;code&gt;backd&lt;/code&gt; itself (through &lt;code&gt;ctx.db&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;This is how the &lt;a href="../cron/#another-example-a-daily-digest"&gt;cookbook&amp;rsquo;s &lt;code&gt;daily_digest&lt;/code&gt;&lt;/a&gt; would send its result by email instead of storing it.&lt;/li&gt;
&lt;li&gt;Send an idempotency key to the provider when the call has side effects and the function can run twice (&lt;a href="../jobs/#designing-jobs-that-are-safe-to-repeat"&gt;Async jobs&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="egress-the-network-allowlist-completed"&gt;Egress: the network allowlist, completed&lt;a class="hd-anchor" href="#egress-the-network-allowlist-completed" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why this exists.&lt;/strong&gt; A function runs code you may not have written yourself (an npm dependency, a library update). If its outbound network were left to a single hostname allowlist, a bug or a compromised dependency could still: connect to MongoDB or another private service directly; reach your cloud provider&amp;rsquo;s metadata endpoint (&lt;code&gt;169.254.169.254&lt;/code&gt;) and steal the credentials your infrastructure runs as (SSRF); or reach the executor&amp;rsquo;s or &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s own internal endpoints. A hostname allowlist alone doesn&amp;rsquo;t stop any of this, because it checks &lt;strong&gt;names&lt;/strong&gt;, not the addresses they resolve to — an allowed name can resolve to a private or loopback address (by mistake, by DNS rebinding, or because an attacker controls that DNS record) — and a raw TCP connection (&lt;code&gt;Deno.connect&lt;/code&gt;, used directly instead of &lt;code&gt;fetch&lt;/code&gt;) never goes through an HTTP proxy at all, so a proxy-based check alone can&amp;rsquo;t see it either.&lt;/p&gt;</description></item><item><title>Build, dev and test</title><link>https://fernandezvara.github.io/backd/docs/functions/testing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/testing/</guid><description>&lt;h2 id="building"&gt;Building&lt;a class="hd-anchor" href="#building" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Functions are bundled before &lt;code&gt;backd&lt;/code&gt; starts: each function, with everything it imports (&lt;code&gt;lib/&lt;/code&gt;, npm and JSR packages), becomes one JavaScript file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONFIG_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./config backd functions build
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# bundled blog/main/stats → stats-3f2a9c0d1e4b5a6c.js (1843 bytes)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;On Windows and macOS&lt;/strong&gt;, &lt;code&gt;backd&lt;/code&gt; itself runs, but its executor doesn&amp;rsquo;t (see &lt;a href="../running/#what-runs-where"&gt;what runs where&lt;/a&gt;): develop with the local stack&amp;rsquo;s containers, which include one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The build runs &lt;code&gt;deno bundle&lt;/code&gt; and needs &lt;strong&gt;Deno 2.9.7&lt;/strong&gt; on the &lt;code&gt;PATH&lt;/code&gt; (or the binary named by &lt;code&gt;DENO&lt;/code&gt;); it refuses any other version, because bundles must match the Deno that runs them. Without Deno installed, run it in the official Deno image with a Linux &lt;code&gt;backd&lt;/code&gt; binary (from the release archives):&lt;/p&gt;</description></item><item><title>Running in production</title><link>https://fernandezvara.github.io/backd/docs/functions/running/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/running/</guid><description>&lt;h2 id="what-runs-where"&gt;What runs where&lt;a class="hd-anchor" href="#what-runs-where" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Functions add up to four kinds of process. All are the same &lt;code&gt;backd&lt;/code&gt; binary (the executor&amp;rsquo;s image adds Deno), each in its own role:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Process&lt;/th&gt;
 &lt;th&gt;Command&lt;/th&gt;
 &lt;th&gt;Needed for&lt;/th&gt;
 &lt;th&gt;How many&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;API&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;backd serve&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;calling functions over HTTP&lt;/td&gt;
 &lt;td&gt;as many as you like, all with the same config&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Executor&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;backd executor&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;running any function&lt;/td&gt;
 &lt;td&gt;one or more, reachable only from &lt;code&gt;backd&lt;/code&gt; and workers&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;backd worker&lt;/code&gt;, or &lt;code&gt;backd serve --with-worker&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="../jobs/"&gt;async jobs&lt;/a&gt; and &lt;a href="../cron/"&gt;cron schedules&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;one or more; each job and each scheduled run is done by exactly one&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Egress&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;backd egress&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;functions that declare &lt;a href="../network/"&gt;&lt;code&gt;network&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;one or more; the executor&amp;rsquo;s only way out&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;aside class="hd-hint hd-hint--note"&gt;
 &lt;p class="hd-hint-title"&gt;&lt;svg class="hd-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;&lt;circle cx="12" cy="12" r="9"/&gt;&lt;path d="M12 16v-4m0-4h.01"/&gt;&lt;/svg&gt;
Note&lt;/p&gt;</description></item><item><title>Function logs</title><link>https://fernandezvara.github.io/backd/docs/functions/logs/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/logs/</guid><description>&lt;p&gt;Every function call — &lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;webhook&lt;/code&gt;, and once an &lt;a href="../jobs/"&gt;async&lt;/a&gt; job finishes — is recorded in &lt;code&gt;&amp;lt;realm&amp;gt;___system.invocations&lt;/code&gt;: what happened, not what was sent or returned. &lt;code&gt;backd&lt;/code&gt; is a short debugging window, not a log archive — plan to ship logs elsewhere for anything you need to keep.&lt;/p&gt;
&lt;h2 id="whats-recorded"&gt;What&amp;rsquo;s recorded&lt;a class="hd-anchor" href="#whats-recorded" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The function (&lt;code&gt;&amp;lt;database&amp;gt;/&amp;lt;name&amp;gt;&lt;/code&gt;), who called it, its mode, status and duration.&lt;/li&gt;
&lt;li&gt;The function&amp;rsquo;s own console output (&lt;code&gt;console.log&lt;/code&gt;, &lt;code&gt;console.error&lt;/code&gt;, …), already masked of any &lt;a href="../secrets/"&gt;declared secrets&amp;rsquo;&lt;/a&gt; exact values.&lt;/li&gt;
&lt;li&gt;The &lt;a href="../jobs/#reporting-progress-from-a-long-job"&gt;steps&lt;/a&gt; the function reported with &lt;code&gt;ctx.step()&lt;/code&gt;, closed when the call ended (at most 100).&lt;/li&gt;
&lt;li&gt;The request id, so a record can be matched to &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s own JSON log line for the same call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never&lt;/strong&gt; the input or output. If you need to reproduce what a specific call did, log what matters yourself with &lt;code&gt;console.log&lt;/code&gt; inside the function.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;d3c9ljp8hc2g00b6s1m0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;at&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-09-29T12:00:03.500Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;orders/checkout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;actor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user:d3c9lg38hc2g00b6s1lg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sync&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;duration_ms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;request_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;darf9mq5mh5g00aq9df0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;job_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;parent_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;origin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;logs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;line&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;charging card for 1250&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;steps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;steps_omitted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;status&lt;/code&gt; is one of &lt;code&gt;ok&lt;/code&gt;, &lt;code&gt;function_error&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt;, &lt;code&gt;cpu&lt;/code&gt;, &lt;code&gt;crash&lt;/code&gt;, &lt;code&gt;output_too_large&lt;/code&gt;, &lt;code&gt;busy&lt;/code&gt;, &lt;code&gt;bundle&lt;/code&gt; or &lt;code&gt;cancelled&lt;/code&gt; (an async job&amp;rsquo;s attempt that an administrator &lt;a href="../jobs/#cancelling-and-re-running-a-job"&gt;cancelled&lt;/a&gt; while it ran) — the same reasons a call can end that show up in &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s own log and in the &lt;a href="../calling/#calling-a-function"&gt;error table&lt;/a&gt;. &lt;code&gt;code&lt;/code&gt; is the function&amp;rsquo;s own error code (&lt;code&gt;ctx.error&lt;/code&gt;&amp;rsquo;s second argument) when &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;function_error&lt;/code&gt;. For an &lt;a href="../jobs/"&gt;async&lt;/a&gt; call, &lt;code&gt;mode&lt;/code&gt; is &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;job_id&lt;/code&gt; names the job; the record is written once the job finishes, not when it&amp;rsquo;s queued.&lt;/p&gt;</description></item><item><title>Cookbook</title><link>https://fernandezvara.github.io/backd/docs/functions/cookbook/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/cookbook/</guid><description>&lt;p&gt;Every recipe here is a function of the &lt;code&gt;workshop&lt;/code&gt; realm (&lt;a href="https://github.com/fernandezvara/backd/tree/main/examples/config/workshop"&gt;&lt;code&gt;examples/config/workshop&lt;/code&gt;&lt;/a&gt; in the repository): a tiny shop with &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;refunds&lt;/code&gt;, &lt;code&gt;reports&lt;/code&gt;, &lt;code&gt;digests&lt;/code&gt; and &lt;code&gt;events&lt;/code&gt;. The code on this page is read straight from those files, and &lt;code&gt;TestWorkshopExample&lt;/code&gt; runs every function for real (MongoDB, a real executor, a real worker), so what you read is what runs.&lt;/p&gt;
&lt;h2 id="run-the-workshop"&gt;Run the workshop&lt;a class="hd-anchor" href="#run-the-workshop" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;



&lt;p&gt;The repository&amp;rsquo;s local stack serves the realm, and its &lt;code&gt;backd&lt;/code&gt; runs a worker (&lt;code&gt;serve --with-worker&lt;/code&gt;), so jobs and schedules work:&lt;/p&gt;</description></item><item><title>Files in functions</title><link>https://fernandezvara.github.io/backd/docs/functions/files/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/functions/files/</guid><description>&lt;p&gt;A function reaches the files of a document through the same client it uses for data: &lt;code&gt;files(id, field)&lt;/code&gt; on a collection of &lt;code&gt;ctx.db&lt;/code&gt; or &lt;code&gt;ctx.admin.db&lt;/code&gt;. It makes the same requests as any client, to the &lt;a href="../../files/transfers/"&gt;&lt;code&gt;_files&lt;/code&gt; routes&lt;/a&gt;, so the same limits, type detection, journal and usage totals apply.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;people&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;personId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;photo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ada.png&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;image/png&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// the updated document
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// { file, response, bytes(), text() }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// { url, expires_at }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// the field&amp;#39;s files; delete(fileId) removes one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;put(data, { name, type, ifMatch })&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Stores a file (a string, bytes, a &lt;code&gt;Blob&lt;/code&gt; or a stream): it replaces a single field&amp;rsquo;s file and is added to a &lt;code&gt;multiple&lt;/code&gt; field&amp;rsquo;s. Resolves with the document, which now holds the file&amp;rsquo;s details. &lt;code&gt;type&lt;/code&gt; is a hint: backd detects the real one from the content&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;get(fileId?)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Downloads a file. Without &lt;code&gt;fileId&lt;/code&gt;, the one a single field holds; a field with several needs one. The bytes come through backd, even where downloads are redirects for everyone else, because a function can&amp;rsquo;t reach the bucket&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;link(fileId?)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;{ url, expires_at }&lt;/code&gt;, exactly like &lt;code&gt;?link=json&lt;/code&gt;: for the app to open. A link of the storage is for the bucket; a &lt;a href="../../files/transfers/#links-for-apps"&gt;backd link&lt;/a&gt; needs &lt;code&gt;BACKD_URL&lt;/code&gt;, so it can name backd&amp;rsquo;s public address&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;delete(fileId?, { ifMatch })&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Removes one file, or every file of the field. Resolves with the document&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="as-the-caller-or-as-the-function"&gt;As the caller, or as the function&lt;a class="hd-anchor" href="#as-the-caller-or-as-the-function" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ctx.db&lt;/code&gt; acts as the caller: &lt;code&gt;put&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; are asked of the document&amp;rsquo;s &lt;code&gt;update&lt;/code&gt; rule (with &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;changed()&lt;/code&gt; as for an upload), &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;link&lt;/code&gt; of the &lt;code&gt;read&lt;/code&gt; rule, and an upload is recorded as theirs. A document the caller can&amp;rsquo;t read is a &lt;code&gt;NotFoundError&lt;/code&gt;.&lt;/p&gt;</description></item></channel></rss>