a9script

Docs / Reference

a9script authoring pack

How a script is executed on this platform: the shape of its input, the name of every function it may call, the rules enforced while it runs, and the habits from general JS that do not work here. Two things are parts of their own, named at the end — how to CALL each function, and the language’s own built-ins, which behave here exactly as they do anywhere.

Generated — do not edit. Every block of script below is executed by the platform’s own test suite, and every error message is the one the platform produces today, so none of this can be what was true when someone last wrote it down.

Every example below was run with this input:

{
  "event": {
    "id": "evt_2f1c9a",
    "kind": "webhook",
    "tenantId": "6a1b0f6e-0000-4000-8000-000000000001",
    "environmentId": "6a1b0f6e-0000-4000-8000-000000000002",
    "endpoint": "orders",
    "body": {
      "id": "o-1042",
      "customer": "ada@example.com",
      "items": [
        {
          "sku": "A-1",
          "price": 20
        },
        {
          "sku": "B-2",
          "price": 10
        }
      ]
    },
    "headers": {
      "content-type": "application/json",
      "x-source": "shop"
    },
    "receivedAt": "2026-03-04T09:15:00.000Z"
  },
  "settings": {
    "crmConnection": "crm",
    "pageSize": 50,
    "signingSecret": "shared-secret"
  }
}

A script is a program, not a module

A script is a program. Its statements run once, from top to bottom, and when the last one finishes the run is over. There is no entry point: nothing calls main, nothing looks for a default export, and the platform never searches your code for a function to invoke.

There are no modules either. import and export are refused before the script runs, and require does not exist. Code you want to share lives in a library script: the platform puts it in front of yours before the run starts, so its functions and variables are simply in scope — no import statement, no namespace.

Everything else you would write in a program works. Declare functions and call them, use a wrapper function if you want a private scope — but neither is required, and wrapping the whole script in one buys nothing.

const order = input.event.body;
log("order received", { id: order.id });

function total(items) {
  let sum = 0;
  for (const item of items) {
    sum += item.price;
  }
  return sum;
}

return { id: order.id, total: total(order.items) };

→ {“id”:“o-1042”,“total”:30}

What a script returns

A run has one result. Three rules decide it, in order:

  1. A top-level return wins — its value is the result, and the run stops there.
  2. Otherwise, the final value of a top-level variable called output is the result.
  3. Otherwise the run has no result, which is perfectly normal.

output only counts at the top level. A variable of that name inside a function is an ordinary local variable, and the platform never looks at it. The result becomes the run’s output: it is what the run detail shows, and — for a run triggered by a synchronous API call — what the caller can be answered with.

const output = "ignored";
return { ok: true };

→ {“ok”:true}

const output = { processed: 0 };
output.processed = input.event.body.items.length;

→ {“processed”:2}

function compute() {
  const output = 42;
  return output;
}

compute();

→ no result

log("nothing to report");

→ no result

input — the one global that carries data

input is the only global holding data. Its parts: input.event is what triggered the run; input.settings is the value the linked settings script returned — configuration as data, so environment-specific values never sit in the script (with no settings script linked it is undefined); and on a door that requires an identity, input.caller names the signed-in person — their id, email and roles, bound by the platform from the door’s own decision, absent everywhere else and never settable from any payload. Filter what you return by input.caller and you have an application backend.

input is frozen, at every depth. Assigning to any part of it throws a TypeError you can catch. Build what you need as a new value instead: an object literal for a few fields, JSON.parse(JSON.stringify(input.event.body)) for a deep copy.

What input.event carries depends on the door the run came through. These fields are always present: id, kind, tenantId, environmentId, body and receivedAt. kind is what to branch on when one script serves more than one door.

TriggerWhat causes itinput.event.bodyFields set
webhookAn HTTP call arrived on one of the environment’s endpoints. The caller is answered immediately, before the script runs.The request body — parsed when it is JSON, the raw text otherwise.id, kind, tenantId, environmentId, endpoint, body, headers, receivedAt
api_serviceAn HTTP call arrived on a synchronous endpoint, and the caller is waiting for this run’s answer — see respond.The request body — parsed when it is JSON, the raw text otherwise.id, kind, tenantId, environmentId, endpoint, body, headers, receivedAt
schedulerA schedule came due. Nothing else triggered it, so there is no caller and no payload from outside.{ schedule, cron, firedAt } — which schedule fired, its expression, and the minute it was due.id, kind, tenantId, environmentId, body, receivedAt
email_inA message arrived in a mailbox the environment watches. One run per message.The message envelope — sender, recipients, subject, body and attachment descriptions.id, kind, tenantId, environmentId, endpoint, body, receivedAt
systemSomething went wrong in this environment — a run failed, queued work could not be delivered, or something was marked as failing. The script reacts to it.The occurrence: { kind, tenantId, environmentId, scriptName?, runId?, taskId?, error?, at } — what broke and where, never the payload that caused it.id, kind, tenantId, environmentId, body, receivedAt
manualSomeone ran the script on demand — from the editor’s Test button or from the command line.Whatever input was supplied for the test run.id, kind, tenantId, environmentId, body, receivedAt
const order = input.event.body;

return order.customer;

→ “ada@example.com

try {
  input.event.body.id = "changed";
} catch (error) {
  return error.name + ": " + error.message;
}

→ “TypeError: cannot mutate the read-only input”

const order = input.event.body;

return { id: order.id, customer: order.customer, status: "processed" };

→ {“id”:“o-1042”,“customer”:“ada@example.com”,“status”:“processed”}

Calling the platform

Everything the platform offers — logging, HTTP calls, checkpoints, and the functions configured for your tenant — is called by its bare name and answers on the spot. There is no await and no .then, and nothing to hand a callback to: the language has no async, and Promise is not defined. (Callbacks themselves are ordinary — map and sort take one; it is the PLATFORM that never asks for one.)

That is not a simplification of something asynchronous. A platform call really does pause the run: the platform saves the run’s entire state, does the work, and continues your script with the result filled in. A run that is resumed — on another worker, an hour later — picks up the saved result instead of calling again, which is why a retried run never sends the same request twice.

You can call the platform from anywhere, a map or sort callback included. Prefer a plain loop when the list is not small: the calls happen one at a time either way, and only a loop can break — when you have enough, when an answer is bad, when the list turns out longer than you expected. The one way out of a callback is throw, which abandons the whole iteration.

What each function does — its arguments, its result, an example — is in the function reference. This page only fixes how calling works.

The results below come from running these examples against a connection that answers 200.

const response = fetch({
  connection: input.settings.crmConnection,
  method: "GET",
  path: "/contacts",
  query: { pageSize: input.settings.pageSize }
});

log("contacts fetched", { count: response.body.items.length });

return "status " + response.status;

→ “status 200”

const items = input.event.body.items;
const skus = [];

for (const item of items) {
  log("item", { sku: item.sku });
  skus.push(item.sku);
}

return skus;

→ [“A-1”,“B-2”]

log("first batch done");
checkpoint();
log("this line runs in the next segment");

→ the run is saved here and continues later

When a call does not simply succeed

A call that fails is not always over. The platform can make it again on its own, and for one kind of answer it can put your whole run to sleep and come back to it — two different things, worth telling apart before you configure either.

What follows is what the platform does; a configured call can change its share of it, and a script’s own fetch gets exactly these defaults.

Repeating — the platform makes the same call again, moments apart, while your run waits. It repeats when no answer arrived at all (the connection failed, timed out, or was refused) and when the answer means not now:

  • 408, 425, 429 — the far end asking to be called again.
  • Any 5xx except 501, 505, which say the request itself will never work — for a call that is safe to repeat (below).

By default it repeats 3 times — so up to 4 calls in all — waiting longer before each one, and honouring a Retry-After the far end sends. Everything else is an ANSWER, and you decide what it means: 400, 401, 404, 422 are never repeated, and neither is any other 4xx, because asking again cannot change them.

Which calls repeat on a 5xx depends on what the call DOES. A call whose method promises nothing happens twice — GET, HEAD, OPTIONS, PUT, DELETE — or one you mark idempotent is repeated on a 5xx like any other transient answer. A call that changes something (a POST or a PATCH you have not marked) is repeated only on 408, 425, 429, never on a 5xx: the far end may have done the work before it failed to answer, and doing it twice is worse than telling you. Name a status under retry.status when you know the far end rolls back on it. The same line runs through a failed connection: one that never reached the far end at all — refused, unreachable, a name that did not resolve, a certificate the platform will not trust — is repeated for every call, while a connection that died after the request may have gone out is repeated only for a call that is safe to repeat.

Waiting is the other thing, and it is not the same: on a status you name as a long wait, the run is SUSPENDED for as long as the far end asked — its worker released, so the wait costs nothing — and the call is made again when it wakes. A status you have named that way is never repeated on the spot; waiting wins, because repeating would spend the wait holding a worker.

One wait is cut off at 24 hours, whatever the far end asks for and whatever you configure. The longest honest answer a business system gives is “come back after the daily quota resets”, and waiting that out is exactly what this is for; past a day the far end is not busy, it is broken, and a run parked on it should end loudly enough that somebody looks. A longer wait is taken as 24 hours and the run’s log says both what was asked for and what was taken. The same ceiling holds a script’s own checkpoint, which refuses a longer wait instead: waking a day later than you asked would do the work at the wrong time. A run that wants to wake up next week is a schedule, not a wait.

Two budgets bound every call. The one you can set is about SILENCE: how long a call may go without receiving any data before it is cut off — 5 minutes unless you say otherwise. It is not a limit on how long the call may take, so a large download over a slow link finishes as long as the bytes keep coming. Above it the platform holds an absolute limit of 60 minutes on one call and all its repeats together, which nothing you configure can lengthen.

A configured call can override any of this — how many times to repeat, statuses to add, statuses never to repeat, and the silence budget. What it cannot do is exceed the 60 minutes limit or ask for more than 10 repeats.

What a configured call answers

The functions configured for your tenant are the calls you should be making: someone chose the connection, the credential and the wording, and can change them without touching your script. What they can also choose is how much of the answer you have to deal with.

By default a call hands you both halves of what came back: { status, body }, with the body already read as data when the far end sent JSON. A status outside 200–299 arrives the same way — it is an answer to branch on, not a failure.

Whoever configures the call can narrow that, and most well-configured calls do:

  • The statuses it accepts. Any other answer stops the run where the call was made, saying what came back — the status, what the far end sent, and how many times the platform had already asked. Write no status check: if the next line runs, the call was answered.
  • What the answer IS. The value at a path inside the body, or the one record found in a collection. That value is then what you get — not an envelope around it. Several records where one was asked for stops the run rather than picking one, because which is right is a business question.
  • What nothing means. A search that matches nothing, a status that says the record is not there (404, 410), or a null where the value should be — a far end’s way of saying it made no record — can be an ordinary null you branch on, or it can stop the run, naming what was searched for. Stopping is what happens unless you say otherwise, so a create that quietly made nothing is never handed on as a value. Which one is a decision made in the configuration, not in your script.

So a call configured this way reads as the thing it fetches, and the code around it is business — the example below runs against exactly such a function.

You cannot tell from the call site which of these a function does — that is the point, and it is why the same call can be made stricter later without touching a script. Test the function and read what comes back: the Test panel shows exactly what a script receives.

const subject = findServiceRequest({ key: "SR-1" });

return subject;

→ “Login is broken”

A step, once taken, is never taken again

The platform saves every step a run takes. A run that is interrupted — by a restart, by a pause you asked for, by a wait on a busy system — continues from where it stopped, on any machine. It does not start over, and it does not repeat a step it already took.

So the ordinary tools work. new Date() gives you the current moment, Date.now() the same moment as a number, Math.random() a random one. Read the clock twice and you get two readings, seconds apart if that is how long the work took. What you read is saved with the run: a value read before a pause is still that value after it, however long the pause lasted.

input.event.receivedAt is still there and still useful — it is when the run’s trigger arrived, which is not the same thing as now, and for a scheduled run that was caught up late it can be well in the past.

What a script cannot see is the MACHINE it is running on. A date-and-time string must carry a timezone (2026-03-04T09:15:00Z), dates are UTC, and localeCompare takes the locale you want as an argument. Without that, the same script would mean different things on different nodes — and which node runs it is not something you chose.

A few constructs are excluded from the language, because they hide control flow the platform has to be able to save. They are refused before the script runs, with the line and column:

class · this · super · new.target · async/await · generators · import/export · tagged templates · getters/setters · with · eval · debugger · Function · directives · destructuring defaults · sparse arrays · computed object keys

const startedAt = Date.now();
const arrivedAt = new Date(input.event.receivedAt);

return { waitedMs: startedAt - arrivedAt.getTime() >= 0, year: arrivedAt.getUTCFullYear() };

→ {“waitedMs”:true,“year”:2026}

return ["Öl", "Zebra"].sort((a, b) => a.localeCompare(b, "sv")).join(",");

→ “Zebra,Öl”

const skus = input.event.body.items.map((item) => item.sku);

return { skus, joined: skus.join(", "), count: skus.length };

→ {“skus”:[“A-1”,“B-2”],“joined”:“A-1, B-2”,“count”:2}

Errors, and what catches them

throw ends the run and marks it failed. The error’s name, message and the line it came from are recorded with the run.

try/catch catches what you would expect — TypeError, ReferenceError, RangeError, and anything you throw yourself — and it also catches failed platform calls. A call that could not be made surfaces as a catchable error named PlatformError, carrying the platform’s own reason in its message. A call that reached the other side and got an unhappy answer does not throw at all: an HTTP call returns its status for you to branch on.

What try/catch never catches is the run’s budget. When a run runs out of time or memory the platform ends it — there is nothing left to catch, and that is deliberate: a script must not be able to trap its own kill switch.

A run that fails ends with one of these, recorded on the run:

  • PARSE_ERROR — The script could not be read as a program. The message says where.
  • UNSUPPORTED_SYNTAX — The script used something the language excludes. The message names it and says where.
  • UNCAUGHT_EXCEPTION — Something was thrown and nothing caught it — including your own throw.
  • EXECUTION_LIMIT — The run used up its time or its work allowance. Split long work with checkpoint().
  • HEAP_LIMIT — The run held more data at once than it is allowed to. Work in batches.
  • EXTERNAL_TIMEOUT — A platform call did not answer in time.
  • EXTERNAL_FAILED — A platform call failed and nothing caught it. Wrap the call in try/catch to handle it yourself.
  • RETRY_NOT_PERSISTED — The script asked to be paused in a run that cannot be saved and resumed.
  • INTERNAL — The platform itself failed. Nothing in the script can cause or prevent this one.
try {
  const missing = null;
  return missing.id;
} catch (error) {
  return error.name + ": " + error.message;
}

→ “TypeError: cannot read properties of null (reading ‘id’)”

try {
  fetch({ connection: "unconfigured", method: "GET", path: "/contacts" });
} catch (error) {
  logError("the call could not be made", { reason: error.message });
  return error.name;
}

→ “PlatformError”

throw new Error("order " + input.event.body.id + " has no shipping address");

→ fails: UNCAUGHT_EXCEPTION — Error: order o-1042 has no shipping address

The result, and what JSON allows

A script’s result becomes the run’s output. A run that produces no result at all is not a failure; it simply has no output.

For a run triggered by a synchronous API call, respond(value) answers the caller. It can be called once, the run keeps going afterwards, and the run’s own result is still its output. A run triggered any other way may call respond too — nobody is listening, and nothing breaks.

The output is stored as JSON, so the usual JSON rules apply at that edge: properties whose value is undefined are dropped, and there is no form for a bigint or for an object that refers to itself. A result the platform cannot represent fails the run rather than being silently trimmed, and says which of these it hit:

  • cyclic — The result refers to itself, directly or through a chain of properties.
  • bigint — A bigint has no JSON form. Convert it to a string or a number first.
  • undefined — The value undefined has no JSON form, so it cannot be stored as an output.
  • unrepresentable — Part of the result has no JSON form — a function, for instance.
return JSON.stringify({ id: input.event.body.id, note: undefined });

→ ”{“id”:“o-1042”}”

respond({ received: input.event.body.id });

return "answered";

→ “answered”

Budgets

Every segment of a run is bounded, so nothing can run away. These are the defaults a script gets when it asks for nothing; a script can be given a segment budget of its own — longer or shorter — up to a ceiling the deployment sets. A run that pauses at a checkpoint starts its next segment with a fresh budget, which is what lets a job of hours finish inside one measured in seconds.

  • One segment of a run: 60000 ms.
  • Steps across a whole run: 5000000.
  • Values held at once: 100000.

What a script may call

Everything below is called by bare name. A call marked pauses does real work outside the script: the run is saved, and continues on the next line with the result. What each one does, its arguments and an example live in the part named beside each group — ask for that part when you are about to write the call.

Core — the functions-core part

  • log(message, data?) — pauses
  • logInfo(message, data?) — pauses
  • logError(message, data?) — pauses
  • logWarning(message, data?) — pauses
  • logVerbose(message, data?) — pauses
  • logDebug(message, data?) — pauses
  • respond(value) — pauses
  • stop() — pauses
  • echo(value) — pauses
  • fetch(request) — pauses
  • input — read, not called
  • input.event — read, not called
  • input.settings — read, not called

Encoding — the functions-encoding part

  • base64Encode(text) — pauses
  • base64Decode(text) — pauses
  • hexEncode(text) — pauses
  • hexDecode(text) — pauses

Crypto — the functions-crypto part

  • uuid() — pauses
  • randomHex(bytes?) — pauses
  • sha256(text) — pauses
  • hmacSha256(key, message) — pauses

Text — the functions-text part

  • slugify(text) — immediate
  • template(text, values) — immediate

Numbers — the functions-number part

  • formatNumber(value, options?) — pauses
  • formatCurrency(value, currency, options?) — pauses

Structured data — the functions-structured part

  • get(data, path) — immediate
  • set(data, path, value) — immediate
  • jsonPath(data, expression) — immediate
  • parseCsv(text, options?) — pauses
  • toCsv(rows, options?) — pauses
  • parseXml(text, options?) — pauses
  • toXml(value, options?) — pauses

Documents — the functions-document part

  • createPdf(document) — pauses

Markdown — the functions-markdown part

  • markdownToHtml(markdown, options?) — pauses
  • sanitizeHtml(html, options?) — pauses
  • escapeHtml(text) — pauses
  • stripMarkdown(markdown) — pauses
  • markdownToPdf(markdown, options?) — pauses

Date & time — the functions-datetime part

  • formatDate(date, pattern) — immediate
  • addDays(date, days) — immediate
  • dateDiff(from, to) — immediate
  • now() — pauses
  • today() — pauses
  • dateAdd(date, amount, unit, timeZone?) — pauses
  • dateSubtract(date, amount, unit, timeZone?) — pauses
  • dateDifference(from, to, unit, timeZone?) — pauses
  • dateIsBefore(date, other, unit?, timeZone?) — pauses
  • dateIsAfter(date, other, unit?, timeZone?) — pauses
  • dateIsEqual(date, other, unit?, timeZone?) — pauses
  • dateIsBetween(date, start, end) — pauses
  • dateStartOf(date, unit, timeZone?) — pauses
  • dateEndOf(date, unit, timeZone?) — pauses
  • dateParse(text, pattern, timeZone?) — pauses
  • dateFormat(date, pattern, timeZone?, locale?) — pauses
  • dateGet(date, unit, timeZone?) — pauses
  • dateSet(date, unit, value, timeZone?) — pauses
  • isWeekend(date, timeZone?) — pauses
  • isLeapYear(date, timeZone?) — pauses
  • daysInMonth(date, timeZone?) — pauses

Email — the functions-email part

  • sendMail(message) — pauses

Files — the functions-blob part

  • writeBlob(contentBase64, mime?) — pauses
  • readBlob(blobId) — pauses
  • readFile(path, options?) — pauses
  • writeFile(path, contentBase64, options?) — pauses

Mapping — the functions-mapping part

  • setMapping(namespace, source, target) — pauses
  • getMapping(namespace, source) — pauses
  • deleteMapping(namespace, source) — pauses
  • setMappings(namespace, pairs) — pauses
  • getMappings(namespace, sources) — pauses
  • listMappings(namespace, page?) — pauses

Datasets — the functions-dataset part

  • dsPut(dataset, key, value) — pauses
  • dsPutMany(dataset, rows) — pauses
  • dsGet(dataset, key) — pauses
  • dsGetMany(dataset, keys) — pauses
  • dsPage(dataset, options?) — pauses
  • dsCount(dataset) — pauses
  • dsClear(dataset) — pauses
  • dsRename(from, to) — pauses
  • dsDiff(base, current) — pauses
  • dsDiffKeys(base, current, kind, page?) — pauses
  • dsStats(dataset, path) — pauses
  • dsMapInto(source, mappingName, target, options?) — pauses
  • dsEnrich(dataset, functionName, options) — pauses
  • dsDuplicates(dataset, path, options?) — pauses
  • dsToCsv(dataset, mappingName, options?) — pauses
  • dsFromCsv(file, into, mappingName?, options?) — pauses
  • csvLineRecord(file, line) — pauses
  • upsertBySourceId(namespace, record, options) — pauses
  • applyLinks(dataset, spec) — pauses
  • stageDiff(baseline, current, kinds, into) — pauses
  • sendReport(channel, options) — pauses
  • importAll(files, importFn, options?) — pauses

State — the functions-state part

  • getState(key) — pauses
  • setState(key, value) — pauses

Pagination — the functions-pagination part

  • fetchPage(functionName, arguments, cursor?) — pauses
  • dsFetchInto(functionName, arguments, dataset, options?) — pauses

Run control — the functions-control part

  • checkpoint(waitMs?) — pauses

Config functions — the functions-functions part

  • MyFunction(arguments?) — pauses

Rules for calling the platform

Call platform functions from a plain loop, not from inside a callback

fetch, checkpoint and every other platform function work anywhere — inside a map, filter, forEach or sort callback included. Write a loop anyway: the calls happen one at a time either way, and only a loop can break when the list turns out to be long, the budget short, or an answer bad. A callback’s only exit is throw, which abandons the whole iteration.

Never:

const contacts = input.event.body.items.map((item) =>
  fetch({ connection: "crm", method: "GET", path: "/contacts/" + item.sku }),
);

return contacts.length;

Instead:

const items = input.event.body.items;
const contacts = [];

for (const item of items) {
  contacts.push(fetch({ connection: "crm", method: "GET", path: "/contacts/" + item.sku }));
}

return contacts.length;

// The loop can stop. `break` on a budget, a count, a bad answer —
// none of which a callback can say. From inside a callback the only
// way out is `throw`, which abandons the whole iteration.

Why: A map reads as a transformation and hides that each item is a call. A loop puts the calls in front of you, lets you stop early, and is the shape to reach for whenever the list is not small and known.

Turn a stored file into rows with the file calls, not by reading it into the script

A script may hold only a few hundred kilobytes of a file, so reading a stored CSV and parsing its text passes on a sample and refuses on a real export. dsFromCsv reads the file where it is stored into a dataset, a batch at a time; dsToCsv writes one back out.

Never:

const file = writeBlob(btoa("email\nada@example.com\n"), "text/csv");

return parseCsv(atob(readBlob(file).contentBase64)).length;

Instead:

const file = writeBlob(btoa("email\nada@example.com\n"), "text/csv");

return dsFromCsv(file, "run:contacts", { keyPath: "email" }).rows;

Why: A file’s contents are kept outside a run on purpose: a handle costs nothing to carry, a megabyte costs the run.

Count and summarise where the rows are, not in the script

A loop that reads staged rows only to add them up pulls the whole set through the run to produce a handful of numbers. dsCount answers a total, dsStats groups by any field, dsDuplicates finds the collisions a unique target field would refuse — each one query, no rows moved. The same holds for the sync’s own account of itself: sendReport renders the report dataset — totals, breakdowns, and the failures by name — and sends it, without a script reading a row.

Never:

dsPutMany("run:people", [
  { key: "u1", value: { dept: "Research" } },
  { key: "u2", value: { dept: "Research" } },
  { key: "u3", value: { dept: "Operations" } },
]);

const counts = {};
let at = 0;
for (;;) {
  const page = dsPage("run:people", { offset: at, limit: 200 });
  if (page.length === 0) { break; }
  for (const row of page) {
    counts[row.value.dept] = (counts[row.value.dept] || 0) + 1;
  }
  at = at + page.length;
}

return counts.Research;

Instead:

dsPutMany("run:people", [
  { key: "u1", value: { dept: "Research" } },
  { key: "u2", value: { dept: "Research" } },
  { key: "u3", value: { dept: "Operations" } },
]);

const groups = dsStats("run:people", "dept");

// One GROUP BY where the rows are. Each group is the value as JSON
// text — "Research" arrives with its quotes.
return groups[0].count;

Why: Staged data lives outside the run so that its size is not the run’s problem. A tally written in script quietly pulls it back in — the one thing the dataset calls exist to avoid.

Take the result of a call directly — there are no promises

A platform call returns its result to the next line. There is no await (the word does not parse), no .then, and nothing to hand a callback to — Promise is not defined. Callbacks themselves are ordinary; the platform simply never asks for one.

Never:

const contacts = fetch({ connection: "crm", method: "GET", path: "/contacts" }).then(function (response) {
  return response.body.items;
});

return contacts;

Instead:

const response = fetch({ connection: "crm", method: "GET", path: "/contacts" });

return response.body.items;

Why: A call returns the answer itself — there is no promise, so there is no .then on it, and no callback for the platform to run later.

The rest of this pack

The page above is everything that is true only HERE. Ask for these parts by name for the rest:

  • functions-core — the core functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-encoding — the encoding functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-crypto — the crypto functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-text — the text functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-number — the numbers functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-structured — the structured data functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-document — the documents functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-markdown — the markdown functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-datetime — the date & time functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-email — the email functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-blob — the files functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-mapping — the mapping functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-dataset — the datasets functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-state — the state functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-pagination — the pagination functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-control — the run control functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • functions-functions — the config functions — what each one does, its arguments, whether it pauses the run, and a line of real script.
  • mistakes — the habits from general JS that do not work here — each with the exact error it produces and the shape to write instead.
  • standard-library — every built-in the language itself provides — JSON, Math, Object, String, Array, Number, Map, Set, Date, RegExp and the bare globals — one line each, with an example and its answer.
Rendered from docs/guide/authoring-pack.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.