> For the complete documentation index, see [llms.txt](https://support.attackforge.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.attackforge.com/app/afscript.md).

# AFScript

{% embed url="<https://youtu.be/DlTl4o5J2qY>" %}

## What is AFScript?

AFScript is an interpreted programming language created by AttackForge.

It was built to help our customers to configure and personalize their AttackForge application to better match their requirements and needs.

Applications of AFScript could include:

* Changing the logic in parts of the application to align with existing or intended workflows
* Drive behaviour of forms and their respective fields
* Create in-app automations
* Apply pre-and-post data transformations
* Build bespoke dashboards and analytics

AFScript is the next generation in empowerment for AttackForge customers. It comes off the back of the successes we’ve had with *Hide Expressions* ([custom fields and sections](https://support.attackforge.com/attackforge-enterprise/getting-started/custom-fields-and-forms#hide-expressions-conditions), [vulnerability SLAs](https://support.attackforge.com/attackforge-enterprise/getting-started/vulnerability-slas#configuring-sla-rules), [custom vulnerability parsing](https://support.attackforge.com/attackforge-enterprise/getting-started/creating-vulnerabilities#custom-import-mapping)) and *Filter Expressions* ([custom emails](https://support.attackforge.com/attackforge-enterprise/getting-started/notifications#filter), [APIs](https://support.attackforge.com/attackforge-enterprise/modules/self-service-restful-api/advanced-query-filter)) which have provided customers with ways to make AttackForge their own.

We built AFScript to provide a *safe and secure* path for our customers to apply their own code to their AttackForge application, in a performant manner and without creating any security holes.

The language was built to look and feel like [JavaScript](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript) to make it familiar and easy to use for security teams, pentesters and software engineers.

Importantly, the language itself is *not executable*. This makes it safe to use in a secure way. AttackForge will interpret AFScript and derive actions to take, without executing arbitrary code. This important distinction is why we *had to build* our own programming language instead of going with many of the existing languages already available.

## How does AFScript work?

For the most part, write AFScript the same way you would write [JavaScript](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript) code.

If you are not familiar with JavaScript, we recommend checking the [JavaScript basics](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics) guide by Mozilla.

We built AFScript to resemble the primary syntax of JavaScript. You can define and use variables, create loops, create functions, call functions inside functions, etc.

To make the language easier to use, we have included built-in Functions which resemble common JavaScript built-in objects like [Math](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math), [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) and others.

We’ve also added our own functions and syntax which was inspired by various other programming languages, such as [Date.datetime](https://support.attackforge.com/attackforge-enterprise/afscript#dates).

## What can’t AFScript do?

For mostly security reasons, there are some limitations in AFScript that could otherwise be found in JavaScript. This is by design, to prevent accidental or intentional security holes.

The following is a non-exhaustive list of limitations. We will continue to update this list over time as the language evolves.

* No support for [anonymous functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions)
* No support for creating [Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)
* No support for [try…catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch)
* No support for [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
* No support for [new operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new)
* No support for [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) or [export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export)
* No support for [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)

## Writing AFScript

AFScript is a **sandboxed subset of JavaScript**. A script is a program: top-level statements run in order, and whatever it `return`s is the result. It gets `let`/`const`, functions, closures, arrows, template strings, destructuring, spread and a curated standard library. It gets **no** path into the host runtime — no `require`, no `eval`, no `Function`, no timers, no network, no filesystem, no `console`, and no reachable JavaScript prototypes.

If you already know JavaScript you know 90% of AFScript. The remaining 10% is where every mistake lives, so read the next two sections before writing a line.

### The one rule: no method call syntax

`a.b` is a **property read**, never a method lookup. There is no `a.b()` call form for anything the value did not itself put there.

Reading a name the value **owns** works exactly as in JavaScript:

```javascript
const row = { title: 'RCE', severity: 'Critical', limits: { max: 9 } };

row.title;          // 'RCE'
row['severity'];    // 'Critical'
row.limits.max;     // 9
row.missing;        // undefined
```

Reading a name that only a **prototype** would have supplied **throws**. It does not return `undefined` — it ends the script:

```javascript
'abc'.toUpperCase();   // throws: toUpperCase is not a property of a string;
                       // AFScript has no method call syntax - use String.toUpperCase(string, ...) instead

[1, 2, 3].map(f);      // throws: map is not a property of an array;
                       // AFScript has no method call syntax - use Array.map(array, ...) instead

[1].constructor;       // throws: constructor is not a property of an array
```

`length` is the one exception worth knowing: it is a genuine own property of an array and of a string, so `items.length` and `s.length` do read. **Use `Array.length(items)` and `String.length(s)` anyway** — they are the documented API, and they *check their receiver*, where `.length` silently answers `undefined` on anything that is not an array or a string.

Built-ins live on **namespace objects**, and the receiver is the first argument:

```javascript
String.toUpperCase('abc');            // 'ABC'
Array.map([1, 2, 3], (n) => n * 2);   // [2, 4, 6]
Array.length([1, 2, 3]);              // 3
String.length('abc');                 // 3
```

**The mechanical rule.** Before you write a dot, ask what is on the left.

* About to write `x.foo(a, b)` → write `Namespace.foo(x, a, b)`.
* About to write `x.length` → write `Array.length(x)` or `String.length(x)`.
* Left side is a value **you** built (an object literal, a host global, a parsed JSON result) and the name is one **you** put there → the dot is correct, leave it.

The ten namespaces are `Array`, `String`, `Object`, `JSON`, `XML`, `Date`, `Math`, `Number`, `Util`, `Logger`. All ten are `const` — readable, never replaceable. `Infinity` and `NaN` are the only bare globals the language defines.

### JavaScript → AFScript

| JavaScript                                                                                                                | AFScript                                                                         |
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `xs.length`                                                                                                               | `Array.length(xs)` — `.length` does read, but is unchecked; prefer this          |
| `s.length`                                                                                                                | `String.length(s)`                                                               |
| `xs.map(f)` · `.filter` · `.reduce` · `.find` · `.some` · `.every` · `.sort` · `.join` · `.includes` · `.slice` · `.push` | `Array.map(xs, f)`, `Array.filter(xs, f)`, …                                     |
| `s.trim()` · `.split` · `.replace` · `.toUpperCase` · `.startsWith` · `.padStart`                                         | `String.trim(s)`, `String.split(s, ',')`, …                                      |
| `'' + n` or `` `${n}` `` used as a coercion                                                                               | `String.from(n)` — `String.concat` **refuses** a non-string                      |
| `Object.keys(o)` · `entries` · `values`                                                                                   | same — but `Object.keys('abc')` **throws**                                       |
| `Object.assign` · `freeze` · `fromEntries` · `hasOwn`                                                                     | **absent** — use `{ ...a, ...b }` in place of `assign`                           |
| `JSON.parse` · `JSON.stringify`                                                                                           | same — `JSON.parse` answers `undefined` on bad input, it does not throw          |
| `new Date()` · `d.toISOString()`                                                                                          | `Date.datetime('now')` — there is **no `Date` value type**                       |
| `/re/.test(s)`                                                                                                            | `s =~ m/re/`                                                                     |
| `s.match(/re/g)`                                                                                                          | `String.match(s, m/re/g)` — answers a plain array of strings, or `null`          |
| `let a = 1, b = 2;`                                                                                                       | two statements — **one binding per declaration**                                 |
| `x => ({ a: 1 })`                                                                                                         | same — `{` after `=>` starts a block, so an object literal must be parenthesised |
| `({ a }) => a`                                                                                                            | same — a destructuring arrow parameter **must** be parenthesised                 |
| `[...a, ...b]` · `{ ...o }` · `f(...args)`                                                                                | same — spread works in all three places                                          |
| `for (const x of xs)`                                                                                                     | same — but only over an array or a string                                        |
| `'k' in obj`                                                                                                              | `obj.k !== undefined` — there is no `in` operator                                |
| `console.log(x)`                                                                                                          | `Logger.info(x)` — wrap objects in `JSON.stringify` first                        |
| `try { … } catch { … }`                                                                                                   | **no equivalent** — validate before you act                                      |
| `class` · `new` · `async` · `await` · `yield` · `var` · `instanceof` · `void`                                             | **no equivalent**                                                                |

### What the language has

**Present:**

* `let`/`const` and block scoping;
* hoisted function declarations;
* arrow functions;
* real closures;
* parameter defaults;
* destructuring in declarations, assignment, parameters and `for…of`/`for…in` heads (with defaults, rest, nesting, renamed and computed keys);
* spread in array literals, object literals and call arguments;
* shorthand properties;
* template strings with `${}`;&#x20;
* `if`/`else`, `while`, `do…while`, C-style `for`, `for…of`, `for…in`, `switch` with fall-through, `break`, `continue`, `return`;&#x20;
* arithmetic including `**`, bitwise ops, shifts, comparison, `===`/`!==`, `&&`, `||`, `??`, ternary, `typeof`, `delete`, pre/post `++`/`--`, compound assignment (`+=`, `??=`, …);&#x20;
* optional access `a?.b` and `a?[k]` (**no dot before the bracket**);
* regex literals `m/pattern/flags` matched with `=~`;
* numbers in decimal, hex, octal and binary, plus their BigInt forms;&#x20;
* `//` and `/* */` comments;

**Absent:**

* `class`/`extends`/`super`; `new`; `try`/`catch`/`finally`/`throw`; `async`/`await` and promises;
* generators and `yield`;
* `var`;
* `this`;
* `arguments`;
* `instanceof`;
* `in` as a binary operator;
* `void`;
* the comma operator and multi-declarator `let a = 1, b = 2` (so no `for (let i = 0, j = 0; …)` either);
* labelled statements and `break label`;
* tagged templates and `String.raw`;
* `/re/` literal syntax;
* optional call `a?.()`;
* getters, setters and object method shorthand `{ foo() {} }`;
* a trailing comma in a **parameter list** (allowed in call arguments, literals and patterns);
* iterators and `Symbol.iterator`;
* `import`/`export`/`require`;
* `eval`;
* `Function`;
* timers;
* I/O;
* `console`;
* `Map`, `Set`, `Symbol`, `Promise`, typed arrays;
* `RegExp`, `Date` and `Error` object types;

### Values are checked, never coerced

Almost every built-in validates what it is handed and **throws** rather than guessing. This is the second-biggest source of surprise after method calls.

```javascript
String.concat('count: ', 3);   // throws - 3 is not a string
Array.from(5);                 // throws - JavaScript would answer []
Object.keys('abc');            // throws - a string is not an object here
```

Convert explicitly. `String.from` is the one member that exists to coerce, and the one that never throws:

```javascript
String.concat('count: ', String.from(3));   // 'count: 3'
`count: ${3}`;                              // 'count: 3' - interpolation coerces too
```

The exceptions: `Array.isArray`, all of `Number`, and all of `Math` except `secureRandom` and `secureRandomInt` are host intrinsics passed straight through, so they coerce exactly as JavaScript does.

## Examples

### `for…of` and `for…in`

`for…of` walks an array's elements or a string's characters, and throws on anything else. Strings are walked **by code point**, so an astral character is one iteration rather than a surrogate pair.

```javascript
const out = [];
for (const ch of 'a😀b') { Array.push(out, ch); }
out;   // ['a', '😀', 'b']
```

`for…in` walks own enumerable keys as strings — an object's names, an array's or string's index names, and nothing at all for `null`, `undefined` or a primitive.

```javascript
const scope = { host: 'a.example', port: 443 };
const out = [];
for (const key in scope) { Array.push(out, `${key}=${scope[key]}`); }
out;   // ['host=a.example', 'port=443']
```

`for…of` gives the loop variable a **fresh binding each iteration**, so a function made in the body captures that iteration's value. A C-style `for` header keeps its single binding. Both match JavaScript, and the difference is worth knowing:

```javascript
const fns = [];
for (const x of [1, 2, 3]) { Array.push(fns, () => x); }
Array.map(fns, (f) => f());              // [1, 2, 3]

const fns2 = [];
for (let i = 0; i < 3; i++) { Array.push(fns2, () => i); }
Array.map(fns2, (f) => f());             // [3, 3, 3]
```

### Arrow functions

An expression that evaluates to a function, so a callback no longer has to be hoisted out into a named declaration.

```javascript
Array.map([1, 2, 3], (n) => n * 2);   // [2, 4, 6]

const add = (a) => (b) => a + b;
add(5)(3);                            // 8
```

An expression body is the result outright; a block body needs `return`. `x => {}` is an empty block that answers `undefined`, not an object literal. `function f() {}` — an empty body on a declaration — is now legal too.

### Template string literals

```javascript
const host = 'example.com';
const port = 443;

`https://${host}:${port}/`;   // 'https://example.com:443/'
```

Interpolations hold any expression, nest, and coerce their value exactly as `+` would. Escapes `\`` and` $\` are available for a literal backtick or dollar. Tagged templates are not included.

### Switch

`===` matching, fall-through until a `break`, and `default` honoured wherever it sits in the clause list. The whole body shares one block scope.

```javascript
function rating(severity) {
  switch (severity) {
    case 'Critical':
    case 'High':
      return 'act now';
    case 'Medium':
      return 'schedule';
    default:
      return 'backlog';
  }
}

[rating('High'), rating('Medium'), rating('Info')];   // ['act now', 'schedule', 'backlog']
```

### Spread and shorthand properties

Spread works in the three places JavaScript puts it — array literals, object literals and call arguments — and object literals gained `{ x, y }` shorthand.

```javascript
const a = [1, 2];
const b = [3, 4];
[...a, ...b, 5];                         // [1, 2, 3, 4, 5]

const base = { severity: 'Low', open: true };
({ ...base, severity: 'High' });         // {"severity":"High","open":true}

function tag(a, b, c) { return `${a}/${b}/${c}`; }
tag(...['x', 'y', 'z']);                 // 'x/y/z'

const x = 1;
const y = 2;
({ x, y });                              // {"x":1,"y":2}
```

What may be spread is decided by type, not by asking the value for an iterator: arrays and strings for the array and argument forms, own enumerable keys for the object form. A function value cannot be spread at all.

## Logging

Logging is imperative when writing any script or code.

AFScript supports logging to help you debug your scripts and logic.

You can invoke a log message using the following syntax:

`Logger.<type>(“message”);`

You can also include multiple messages in the same log entry using a comma to separate each message:

`Logger.<type>(“message1”,“message2”,“message3”)`**`;`**

Where **\<type>** is one of the following log level types:

1. fatal
2. error
3. warn
4. info
5. debug
6. trace

#### Fatal

`Logger.fatal(“a fatal error occurred in xyz”);`

#### Error

`Logger.error(“an error occurred in xyz”);`

#### Warn

`Logger.warn(“a warning for xyz”);`

#### Info

`Logger.info(“an informational message for xyz”);`

#### Debug

`Logger.debug(“a debug statement”);`

#### Trace

`Logger.trace(“function abc was called”);`

## Built-in Functions

### Dates

<table data-header-hidden="false" data-header-sticky data-first-column-sticky><thead><tr><th width="131" valign="top">Function</th><th width="525" valign="top">Description</th><th width="394" valign="top">Example</th></tr></thead><tbody><tr><td valign="top">datetime</td><td valign="top"><p>The <strong>Date.datetime(“timeValue”, “modifiers”, “isostring”, “epoch”)</strong> function can be used to construct a date and time.</p><p>You can modify the date and time by passing modifiers.</p><p>The default response is the date &#x26; time in an ISO string format.</p><p>However, you can set the response to be in epoch format by passing in “epoch”.</p><p>Similarly, you can explicitly set the response to an ISO string format by passing in “isostring”.</p><p>timeValue - must be either:</p><ul><li>“now”</li><li>“YYYY-MM-DD”</li><li>“YYYY-MM-DDTHH:MM”</li><li>“YYYY-MM-DDTHH:MM:SS”</li><li>“YYYY-MM-DDTHH:MM:SS:MMMZ”</li><li>time in milliseconds e.g. "1727246762913"</li></ul><p>modifiers - must be either:</p><ul><li>“+999 years”</li><li>“-999 years”</li><li>“+999 months”</li><li>“-999 months”</li><li>“+999 days”</li><li>“-999 days”</li><li>“+999 hours”</li><li>“-999 hours”</li><li>“+999 minutes”</li><li>“-999 minutes”</li><li>“start of year”</li><li>“start of month”</li><li>“start of day”</li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">//Example 1:
Logger.debug(Date.datetime("2020-06-01"));
// Expected output: “2020-06-01T00:00:00.000Z”

//Example 2:
Logger.debug(Date.datetime("now"));
// Expected output: “2024-09-09T13:24:05.274Z”

//Example 3:
Logger.debug(Date.datetime("now", "epoch"));
// Expected output: 1725888245274

//Example 4:
Logger.debug(Date.datetime("now", "-7 days"));
// Expected output: “2024-09-02T13:24:05.274Z”

//Example 5:
Logger.debug(Date.datetime("now", "-7 days", "+1 years"));
// Expected output: “2025-09-02T13:24:05.274Z” </code></pre></td></tr><tr><td valign="top">format</td><td valign="top"><p>The <strong>Date.format(“timeValue”, “mask”, </strong><em><strong>"</strong></em><strong>timezone</strong><em><strong>"</strong></em><strong>)</strong> function can be used to convert a date and time to a specified mask and optional timezone.</p><p></p><p><strong>timeValue</strong> - must be either:</p><ul><li>“now”</li><li>“YYYY-MM-DD”</li><li>“YYYY-MM-DDTHH:MM”</li><li>“YYYY-MM-DDTHH:MM:SS”</li><li>“YYYY-MM-DDTHH:MM:SS:MMMZ”</li><li>time in milliseconds e.g. "1727246762913"</li></ul><p></p><p><strong>mask</strong> - must be either:</p><h4 id="named-formats">Named formats</h4><ul><li><p><code>default</code></p><ul><li>ddd mmm dd yyyy HH:MM:ss</li><li><em>Sat Jun 09 2007 17:46:21</em></li></ul></li><li><p><code>shortDate</code></p><ul><li>m/d/yy</li><li><em>6/9/07</em></li></ul></li><li><p><code>paddedShortDate</code> </p><ul><li>mm/dd/yyyy</li><li><em>06/09/2007</em></li></ul></li><li><p><code>mediumDate</code> </p><ul><li>mmm d, yyyy</li><li><em>Jun 9, 2007</em></li></ul></li><li><p><code>longDate</code></p><ul><li>mmmm d, yyyy</li><li><em>June 9, 2007</em></li></ul></li><li><p><code>fullDate</code></p><ul><li>dddd, mmmm d, yyyy</li><li><em>Saturday, June 9, 2007</em></li></ul></li><li><p><code>shortTime</code></p><ul><li>h:MM TT</li><li><em>5:46 PM</em></li></ul></li><li><p><code>mediumTime</code></p><ul><li>h:MM:ss TT</li><li><em>5:46:21 PM</em></li></ul></li><li><p><code>longTime</code></p><ul><li>h:MM:ss TT Z</li><li><em>5:46:21 PM EST</em></li></ul></li><li><p><code>isoDate</code></p><ul><li>yyyy-mm-dd</li><li><em>2007-06-09</em></li></ul></li><li><p><code>isoTime</code></p><ul><li>HH:MM:ss</li><li><em>17:46:21</em></li></ul></li><li><p><code>isoDateTime</code></p><ul><li>yyyy-mm-dd'T'HH:MM:sso</li><li><em>2007-06-09T17:46:21+0700</em></li></ul></li><li><p><code>isoUtcDateTime</code></p><ul><li>UTC:yyyy-mm-dd'T'HH:MM:ss'Z'</li><li><em>2007-06-09T22:46:21Z</em></li></ul></li></ul><h4 id="mask-options">Mask options</h4><ul><li><p><code>d</code></p><ul><li>Day of the month as digits; no leading zero for single-digit days.</li></ul></li><li><p><code>dd</code></p><ul><li>Day of the month as digits; leading zero for single-digit days.</li></ul></li><li><p><code>ddd</code></p><ul><li>Day of the week as a three-letter abbreviation.</li></ul></li><li><p><code>DDD</code></p><ul><li>"Ysd", "Tdy" or "Tmw" if date lies within these three days. Else fall back to ddd.</li></ul></li><li><p><code>dddd</code></p><ul><li>Day of the week as its full name.</li></ul></li><li><p><code>DDDD</code></p><ul><li>"Yesterday", "Today" or "Tomorrow" if date lies within these three days. Else fall back to dddd.</li></ul></li><li><p><code>m</code></p><ul><li>Month as digits; no leading zero for single-digit months.</li></ul></li><li><p><code>mm</code></p><ul><li>Month as digits; leading zero for single-digit months.</li></ul></li><li><p><code>mmm</code></p><ul><li>Month as a three-letter abbreviation.</li></ul></li><li><p><code>mmmm</code></p><ul><li>Month as its full name.</li></ul></li><li><p><code>yy</code></p><ul><li>Year as last two digits; leading zero for years less than 10.</li></ul></li><li><p><code>yyyy</code></p><ul><li>Year represented by four digits.</li></ul></li><li><p><code>h</code></p><ul><li>Hours; no leading zero for single-digit hours (12-hour clock).</li></ul></li><li><p><code>hh</code></p><ul><li>Hours; leading zero for single-digit hours (12-hour clock).</li></ul></li><li><p><code>H</code></p><ul><li>Hours; no leading zero for single-digit hours (24-hour clock).</li></ul></li><li><p><code>HH</code></p><ul><li>Hours; leading zero for single-digit hours (24-hour clock).</li></ul></li><li><p><code>M</code></p><ul><li>Minutes; no leading zero for single-digit minutes.</li></ul></li><li><p><code>MM</code></p><ul><li>Minutes; leading zero for single-digit minutes.</li></ul></li><li><p><code>N</code></p><ul><li>ISO 8601 numeric representation of the day of the week.</li></ul></li><li><p><code>o</code></p><ul><li>GMT/UTC timezone offset, e.g. -0500 or +0230.</li></ul></li><li><p><code>p</code></p><ul><li>GMT/UTC timezone offset, e.g. -05:00 or +02:30.0</li></ul></li><li><p><code>s</code></p><ul><li>Seconds; no leading zero for single-digit seconds.</li></ul></li><li><p><code>ss</code></p><ul><li>Seconds; leading zero for single-digit seconds.</li></ul></li><li><p><code>S</code></p><ul><li>The date's ordinal suffix (st, nd, rd, or th). Works well with <code>d</code>.</li></ul></li><li><p><code>l</code></p><ul><li>Milliseconds; gives 3 digits.</li></ul></li><li><p><code>L</code></p><ul><li>Milliseconds; gives 2 digits.</li></ul></li><li><p><code>t</code></p><ul><li>Lowercase, single-character time marker string: a or p.</li></ul></li><li><p><code>tt</code></p><ul><li>Lowercase, two-character time marker string: am or pm.</li></ul></li><li><p><code>T</code></p><ul><li>Uppercase, single-character time marker string: A or P.</li></ul></li><li><p><code>TT</code></p><ul><li>Uppercase, two-character time marker string: AM or PM.</li></ul></li><li><p><code>W</code></p><ul><li>ISO 8601 week number of the year, e.g. 4, 42</li></ul></li><li><p><code>WW</code></p><ul><li>ISO 8601 week number of the year, leading zero for single-digit, e.g. 04, 42</li></ul></li><li><p><code>Z</code></p><ul><li>US timezone abbreviation, e.g. EST or MDT. For non-US timezones, the GMT/UTC offset is returned, e.g. GMT-0500</li></ul></li><li><p><code>'...'</code>, <code>"..."</code></p><ul><li>Literal character sequence. Surrounding quotes are removed.</li></ul></li><li><p><code>UTC</code></p><ul><li>Must be the first four characters of the mask. Converts the date from local time to UTC/GMT/Zulu time before applying the mask. The "UTC:" prefix is removed.</li></ul></li></ul><p><strong>timezone</strong> - must be a <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">canonical timezone</a></p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">//Example 1:
Logger.debug(Date.format("2007-06-09T00:00:00:000Z","fullDate"));
// Expected output: “Saturday, June 9, 2007”

//Example 2:
Logger.debug(Date.format("now","fullDate"));
// Expected output: “Monday, November 17, 2025”

//Example 3 - with Timezone:
Logger.debug(Date.format("now","fullDate","America/Chicago"));
// Expected output: “Monday, November 17, 2025” adjusted for America/Chicago timezone </code></pre></td></tr></tbody></table>

### Strings

<table data-first-column-sticky><thead><tr><th width="144" valign="top">Function</th><th width="378" valign="top">Description</th><th width="511.58984375" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at">at</a></td><td valign="top">The <strong>String.at()</strong> method takes an integer value and returns a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String"><code>String</code></a> consisting of the single UTF-16 code unit located at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'Cats are the best!';

Logger.debug(String.at(str1, -1));
// Expected output: '!' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt">charAt</a></td><td valign="top"><p>The <strong>String.charAt()</strong> method returns a new string consisting of the single UTF-16 code unit at the given index.</p><p><strong>String.charAt()</strong> always indexes the string as a sequence of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters">UTF-16 code units</a>, so it may return lone surrogates. </p></td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'Brave new world';

Logger.debug(String.charAt(str1, 0));
// Expected output: 'B' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat">concat</a></td><td valign="top">The <strong>String.concat()</strong> method concatenates the string arguments to this string and returns a new string.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">Logger.debug(String.concat('Hello', ', ', 'World!'));
// Expected output: 'Hello, World!' </code></pre></td></tr><tr><td valign="top"><strong>decode</strong></td><td valign="top"><p>The <strong>String.decode()</strong> method takes a string and decodes it to a supplied format. </p><p></p><p>The following formats are supported:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.decode('dGhpcyBpcyBlbmNvZGVk', 'base64'));
// Expected output: this is encoded <strong> </strong>Logger.debug(String.decode('dGhpcyBpcyBlbmNvZGVk', 'base64url'));
// Expected output: this is encoded

Logger.debug(String.decode('ORUGS4ZANFZSAZLOMNXWIZLE', 'base32'));
// Expected output: this is encoded

Logger.debug(String.decode('EHK6ISP0D5PI0PBECDNM8PB4', 'base32hex'));
// Expected output: this is encoded

Logger.debug(String.decode('7468697320697320656E636F646564', 'base16'));
// Expected output: this is encoded

Logger.debug(String.decode('BzbxfazC)twO#0\@wmYo{'));
// Expected output: this is encoded! </code></pre></td></tr><tr><td valign="top"><strong>digest</strong></td><td valign="top"><p></p><p>The <strong>String.digest()</strong> method takes a string and a supplied hashing algorithm and encodes it to a supplied format.</p><p></p><p>The following hashing algorithms are supported:</p><ul><li><a href="https://www.rfc-editor.org/rfc/rfc3174.html">SHA1</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA224</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA256</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA384</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA512</a></li><li><a href="https://www.ietf.org/rfc/rfc1321.txt">MD5</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc2286.html">RMD160</a></li></ul><p></p><p>The following formats are supported:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.digest('hash this', 'SHA1'));
// Expected output: E1449548860671BCFED5A5D1E4701532A0308D20

Logger.debug(String.digest('hash this', 'SHA1', 'base64'));
// Expected output: 4USVSIYGcbz+1aXR5HAVMqAwjSA= <strong> </strong>Logger.debug(String.digest('hash this', 'SHA224', 'base64'));
// Expected output: q6y5930Dpk8Y5BZSLgRFXYDoBfmzbWi10vCftQ====

Logger.debug(String.digest('hash this', 'SHA256', 'base64'));
// Expected output: GUZ3iLwM8ReQoHXqcYRSzs8OedtZ0ZZGcEdeX+LkphE=

Logger.debug(String.digest('hash this', 'SHA384', 'base64'));
// Expected output: Evu6KXz7wzv1U8QjEV20uNvBuWWoIGJ/O7YgG5EQFekQaqOd21lct9vFjtkLmxGP

Logger.debug(String.digest('hash this', 'SHA512', 'base64'));
// Expected output: zwboR4ZuVgz0iMrhfBmT6ntxTfAhXyFrBMKDQY62mymdjq99N4QzncNLtZcyfzobqo8a1GWL2IY5p4Ri1vgvcA==

Logger.debug(String.digest('hash this', 'MD5', 'base64'));
// Expected output: 6AxxXl1OiF9o16OFO1/Kcw==

Logger.debug(String.digest('hash this', 'RMD160', 'base64'));
// Expected output: 1UQ6FU8WfiwTMvbecs+0xqucjBc= </code></pre></td></tr><tr><td valign="top"><strong>encode</strong></td><td valign="top"><p></p><p></p><p>The <strong>String.encode()</strong> method takes a string and encodes it to a supplied format. </p><p></p><p>The following formats are supported:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript"><strong>Logger.debug(String.encode('this is encoded', 'base64')); </strong>// Expected output: dGhpcyBpcyBlbmNvZGVk <strong> </strong>Logger.debug(String.encode('this is encoded', 'base64url'));
// Expected output: dGhpcyBpcyBlbmNvZGVk

Logger.debug(String.encode('this is encoded', 'base32'));
// Expected output: ORUGS4ZANFZSAZLOMNXWIZLE

Logger.debug(String.encode('this is encoded', 'base32hex'));
// Expected output: EHK6ISP0D5PI0PBECDNM8PB4

Logger.debug(String.encode('this is encoded', 'base16'));
// Expected output: 7468697320697320656E636F646564

Logger.debug(String.encode('this is encoded!', 'Z85'));
// Expected output: BzbxfazC)twO#0\@wmYo{ </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith">endsWith</a></td><td valign="top">The <strong>String.endsWith()</strong> method determines whether a string ends with the characters of this string, returning true or false as appropriate.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = 'Cats are the best!';

Logger.debug(String.endsWith(str1, 'best!'));
// Expected output: true </code></pre></td></tr><tr><td valign="top">from</td><td valign="top">The <strong>String.from()</strong> method returns a string representing the primitive or object.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">Logger.debug(String.from({}));
// Expected output: "\[object Object]"

Logger.debug(String.from(\[1, 2, 3]));
// Expected output: "1,2,3"

Logger.debug(String.from(m/abc/gi));
// Expected output: "/abc/gi"

Logger.debug(String.from(42));
// Expected output: "42"

Logger.debug(String.from(3.14));
// Expected output: "3.14"

Logger.debug(String.from(0));
// Expected output: "0"

Logger.debug(String.from(-1));
// Expected output: "-1"

Logger.debug(String.from(NaN));
// Expected output: "NaN"

Logger.debug(String.from(true));
// Expected output: "true"

Logger.debug(String.from(false));
// Expected output: "false"

Logger.debug(String.from(null));
// Expected output: "null"

Logger.debug(String.from(undefined));
// Expected output: "undefined"

Logger.debug(String.from("hello"));
// Expected output: "hello"

Logger.debug(String.from(123n));
// Expected output: "123"

Logger.debug(String.from(0n));
// Expected output: "0" </code></pre></td></tr><tr><td valign="top"><strong>hmac</strong></td><td valign="top"><p></p><p>The <strong>String.hmac()</strong> method is used to create an HMAC string that uses the stated 'algorithm' and 'secret'.</p><p></p><p>The following hashing algorithms are supported:</p><ul><li><a href="https://www.rfc-editor.org/rfc/rfc3174.html">SHA1</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA224</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA256</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA384</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA512</a></li><li><a href="https://www.ietf.org/rfc/rfc1321.txt">MD5</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc2286.html">RMD160</a></li></ul><p></p><p>The following encodings are supported:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.hmac('foobar','secret','MD5','base64'));
// Expected output: xS+mP00upU0wsM3v9SMwXg==

Logger.debug(String.hmac('foobar','secret','MD5','base32'));
// Expected output: YUX2MP2NF2SU2MFQZXX7KIZQLY======

Logger.debug(String.hmac('foobar','secret','MD5','base32hex'));
// Expected output: OKNQCFQD5QIKQC5GPNNVA8PGBO======

Logger.debug(String.hmac('foobar','secret','MD5','base16'));
// Expected output: C52FA63F4D2EA54D30B0CDEFF523305E

Logger.debug(String.hmac('foobar','secret','MD5','Z85'));
// Expected output: -v\[8Mo!JnpfTe\*<]=(d%

Logger.debug(String.hmac('foobar','secret','SHA256','base64'));
// Expected output: T8wGkVtD2KSa/xk0QenhhlTmonwsQosC6PzEHMwimfk=

Logger.debug(String.hmac('foobar','secret','SHA256','base16'));
// Expected output: 4FCC06915B43D8A49AFF193441E9E18654E6A27C2C428B02E8FCC41CCC2299F9

Logger.debug(String.hmac('foobar','secret','SHA512','base64'));
// Expected output: rHbR8hqzr/yrcT3OwWXMUXodm3mxrCH+mWGf2n377pi5JggNyQEXqKpgCHX02+fVCw8TcSu/yduLV9ft25G8DA== </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes">includes</a></td><td valign="top">The <strong>String.includes()</strong> method performs a case-sensitive search to determine whether a given string may be found within this string, returning <code>true</code> or <code>false</code> as appropriate.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const sentence = "The quick brown fox jumps over the lazy dog.";
const word = "fox";

Logger.debug(String.includes(sentence, word));
// Expected output: true </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf">indexOf</a></td><td valign="top">The <strong>String.indexOf()</strong> method searches this string and returns the index of the first occurrence of the specified substring. It takes an optional starting position and returns the first occurrence of the specified substring at an index greater than or equal to the specified number.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'Blue Whale';

Logger.debug(String.indexOf(str1, 'Whale'));
// Expected output: '5' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf">lastIndexOf</a></td><td valign="top">The <strong>String.lastIndexOf()</strong> method searches this string and returns the index of the last occurrence of the specified substring. It takes an optional starting position and returns the last occurrence of the specified substring at an index less than or equal to the specified number.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'canal';

Logger.debug(String.lastIndexOf(str1, 'a'));
// Expected output: '3' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length">length</a></td><td valign="top">The <strong>String.length</strong> data property of a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a> value contains the length of the string in UTF-16 code units.</td><td valign="top"><p></p><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.length('The quick brown fox jumps over the lazy dog.'));
// Expected output: 44 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match">match</a></td><td valign="top">The <strong>String.match()</strong> method of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a> values retrieves the result of matching this string against a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions">regular expression</a>.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str = "1. do this. 2. do that. {{{screenshot.png}}} 4. do other. {{{screenshot2.png}}}";

Logger.debug(JSON.stringify(String.match(str, m/{{{(.\*?)}}}/gi)));
// Expected output: "\["{{{screenshot.png}}}","{{{screenshot2.png}}}"]" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll">matchAll</a></td><td valign="top">The <strong>String.matchAll()</strong> method of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a> values returns an iterator of all results matching this string against a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions">regular expression</a>, including <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Groups_and_backreferences">capturing groups</a>.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str = "1. do this. 2. do that. {{{screenshot.png}}} 4. do other. {{{screenshot2.png}}}";

Logger.debug(JSON.stringify(String.matchAll(str, m/{{{(.\*?)}}}/gi)));
// Expected output: "\[\["{{{screenshot.png}}}","screenshot.png"],\["{{{screenshot2.png}}}","screenshot2.png"]]" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd">padEnd</a></td><td valign="top">The <strong>String.padEnd()</strong> method pads this string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of this string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = 'Breaded Mushrooms';

Logger.debug(String.padEnd(str1, 25, '.'));
// Expected output: "Breaded Mushrooms........" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart">padStart</a></td><td valign="top">The <strong>String.padStart()</strong> method pads this string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of this string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = '5';

Logger.debug(String.padStart(str1, 2, '0'));
// Expected output: "05" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat">repeat</a></td><td valign="top">The <strong>String.repeat()</strong> method constructs and returns a new string which contains the specified number of copies of this string, concatenated together.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'abc';

Logger.debug(String.repeat(str1, 2));
// Expected output: "abcabc" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace">replace</a></td><td valign="top">The <strong>String.replace()</strong> method returns a new string with one, some, or all matches of a <code>pattern</code> replaced by a <code>replacement</code>. The <code>pattern</code> can be a string or a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"><code>RegExp</code></a>, and the <code>replacement</code> can be a string or a function called for each match. If <code>pattern</code> is a string, only the first occurrence will be replaced. The original string is left unchanged.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const paragraph = "I think Ruth's dog is cuter than your dog!";

Logger.debug(String.replace(paragraph, "Ruth's", 'my'));
// Expected output: "I think my dog is cuter than your dog!"

Logger.debug(String.replace(paragraph, m/Dog/i, 'ferret'));
// Expected output: "I think Ruth's ferret is cuter than your dog!" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll">replaceAll</a></td><td valign="top">The <strong>String.replaceAll()</strong> method returns a new string with all matches of a <code>pattern</code> replaced by a <code>replacement</code>. The <code>pattern</code> can be a string or a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp"><code>RegExp</code></a>, and the <code>replacement</code> can be a string or a function to be called for each match. The original string is left unchanged.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const paragraph = "I think Ruth's dog is cuter than your dog!";

Logger.debug(String.replaceAll(paragraph, 'dog', 'monkey'));
// Expected output: "I think Ruth's monkey is cuter than your monkey!"

// Global flag required when calling replaceAll with regex
Logger.debug(String.replaceAll(paragraph, m/Dog/gi, 'ferret'));
// Expected output: "I think Ruth's ferret is cuter than your ferret!" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search">search</a></td><td valign="top">The <strong>String.search()</strong> method executes a search for a match between a regular expression and this string, returning the index of the first match in the string.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str1 = 'Hello world 42!';

Logger.debug(String.search(str1, m/\d+/));
// Expected output: '12' </code></pre></td></tr><tr><td valign="top"><strong>sign /</strong> <strong>verify</strong></td><td valign="top"><p>The <strong>String.sign()</strong> method is used to cryptographically sign a message using a private key for signing, which can be later verified with a public key using the <strong>String.verify()</strong> method.</p><p></p><p>The following hashing algorithms are supported:</p><ul><li><a href="https://www.rfc-editor.org/rfc/rfc3174.html">SHA1</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA224</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA256</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA384</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4634.html">SHA512</a></li><li><a href="https://www.ietf.org/rfc/rfc1321.txt">MD5</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc2286.html">RMD160</a></li></ul><p></p><p>The following encodings are supported:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul></td><td valign="top"><pre class="language-javascript"><code class="lang-javascript"><strong>const signature = String.sign('foobar',secrets.private\_key,'MD5','base64')); </strong>Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base64','MD5'));

const signature = String.sign('foobar',secrets.private\_key,'MD5','base32'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base32','MD5'));

const signature = String.sign('foobar',secrets.private\_key,'MD5','base32hex'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base32hex','MD5'));

const signature = String.sign('foobar',secrets.private\_key,'MD5','base16'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base16','MD5'));

const signature = String.sign('foobar',secrets.private\_key,'MD5','Z85'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'Z85','MD5'));

const signature = String.sign('foobar',secrets.private\_key,'SHA256','base64'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base64','SHA256'));

const signature = String.sign('foobar',secrets.private\_key,'SHA256','base16'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base16','SHA256'));

const signature = String.sign('foobar',secrets.private\_key,'SHA512','base64'));
Logger.debug(String.verify('foobar',secrets.public\_key,signature,'base64','SHA512')); </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice">slice</a></td><td valign="top">The <strong>String.slice()</strong> method extracts a section of this string and returns it as a new string, without modifying the original string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = 'The quick brown fox jumps over the lazy dog.';

Logger.debug(String.slice(str1, 31));
// Expected output: "the lazy dog."

Logger.debug(String.slice(str1, 4, 19));
// Expected output: "quick brown fox" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split">split</a></td><td valign="top">The <strong>String.split()</strong> method takes a pattern and divides this string into an ordered list of substrings by searching for the pattern, puts these substrings into an array, and returns the array.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const str = "The quick brown fox jumps over the lazy dog.";

const words = String.split(str, " ");
Logger.debug(words\[3]);
// Expected output: "fox"

const chars = String.split(str, "");
Logger.debug(chars\[8]);
// Expected output: "k" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith">startsWith</a></td><td valign="top">The <strong>String.startsWith()</strong> method determines whether this string begins with the characters of a specified string, returning true or false as appropriate.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = 'Saturday night plans';

Logger.debug(String.startsWith(str1, 'Sat'));
// Expected output: true

Logger.debug(String.startsWith(str1, 'Sat', 3));
// Expected output: false </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring">substring</a></td><td valign="top">The <strong>String.substring()</strong> method returns the part of this string from the start index up to and excluding the end index, or to the end of the string if no end index is supplied.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = 'Mozilla';

Logger.debug(String.substring(str1, 1, 3));
// Expected output: "oz"

Logger.debug(String.substring(str1, 2));
// Expected output: "zilla" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase">toLowerCase</a></td><td valign="top">The <strong>String.toLowerCase()</strong> method returns this string converted to lowercase.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.toLowerCase('The quick brown fox jumps over the lazy dog.'));
// Expected output: the quick brown fox jumps over the lazy dog. </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase">toUpperCase</a></td><td valign="top">The <strong>String.toUpperCase()</strong> method returns this string converted to uppercase.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(String.toUpperCase('The quick brown fox jumps over the lazy dog.'));
// Expected output: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim">trim</a></td><td valign="top"><p>The <strong>String.trim()</strong> method removes whitespace from both ends of this string and returns a new string, without modifying the original string.</p><p>To return a new string with whitespace trimmed from just one end, use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart">trimStart()</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd">trimEnd()</a>.</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = '   Hello world!   ';

Logger.debug(str1);
// Expected output: "   Hello world!   ";

Logger.debug(String.trim(str1));
// Expected output: "Hello world!"; </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd">trimEnd</a></td><td valign="top">The <strong>String.trimEnd()</strong> method removes whitespace from the end of this string and returns a new string, without modifying the original string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = '   Hello world!   ';

Logger.debug(str1);
// Expected output: "   Hello world!   ";

Logger.debug(String.trimEnd(str1));
// Expected output: "   Hello world!"; </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart">trimStart</a></td><td valign="top">The <strong>String.trimStart()</strong> method removes whitespace from the beginning of this string and returns a new string, without modifying the original string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const str1 = '   Hello world!   ';

Logger.debug(str1);
// Expected output: "   Hello world!   ";

Logger.debug(String.trimStart(str1));
// Expected output: "Hello world!   "; </code></pre></td></tr></tbody></table>

### Arrays

<table data-first-column-sticky><thead><tr><th width="143" valign="top">Function</th><th width="380" valign="top">Description</th><th width="500" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at">at</a></td><td valign="top">The <strong>Array.at()</strong> method takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = [5, 12, 8, 130, 44];

Logger.debug(Array.at(array1, 2));
// Expected output: 8

Logger.debug(Array.at(array1, -2));
// Expected output: 130 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat">concat</a></td><td valign="top">The <strong>Array.concat()</strong> method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \['a', 'b', 'c'];
const array2 = \['d', 'e', 'f'];
const array3 = Array.concat(array1, array2);

Logger.debug(JSON.stringify(array3));
// Expected output: “\["a","b","c","d","e","f"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries">entries</a></td><td valign="top">The <strong>Array.entries()</strong> method returns a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator"><em>array iterator</em></a> object that contains the key/value pairs for each index in the array.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const a = \['a', 'b', 'c'];

Logger.debug(JSON.stringify(Array.entries(a)));
// Expected output: '\[\[0,"a"],\[1,"b"],\[2,"c"]]' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every">every</a></td><td valign="top">The <strong>Array.every()</strong> method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function isBelowThreshold(currentValue) {
return currentValue < 40;
}

const array1 = \[1, 30, 39, 29, 10, 13];

Logger.debug(Array.every(array1, isBelowThreshold));
// Expected output: true </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill">fill</a></td><td valign="top">The <strong>Array.fill()</strong> method changes all elements within a range of indices in an array to a static value. It returns the modified array.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">Logger.debug(JSON.stringify(Array.fill(\[1, 2, 3], 4)));
// Expected output: '\[4,4,4]' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter">filter</a></td><td valign="top">The <strong>Array.filter()</strong> method creates a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy">shallow copy</a> of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">function isBigEnough(value, index, array) {
return value >= 10;
}

return Array.filter(\[12, 5, 8, 130, 44], isBigEnough);
// Expected output: \[12, 130, 44] </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find">find</a></td><td valign="top"><p>The <strong>Array.find()</strong> method returns the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined">undefined</a> is returned.</p><ul><li>If you need the index of the found element in the array, use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex">findIndex()</a>.</li><li>If you need to find the index of a value, use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf">indexOf()</a>. (It's similar to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex">findIndex()</a>, but checks each element for equality with the value instead of using a testing function.)</li><li>If you need to find if a value exists in an array, use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes">includes()</a>. Again, it checks each element for equality with the value instead of using a testing function.</li><li>If you need to find if any element satisfies the provided testing function, use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some">some()</a>.</li></ul></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[5, 12, 8, 130, 44];

function isFound(number) {
return number === 8;
}

const found = Array.find(array1, isFound);

Logger.debug(found);
// Expected output: 8 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex">findIndex</a></td><td valign="top"><p>The <strong>Array.findIndex()</strong> method returns the index of the first element in an array that satisfies the provided testing function. If no elements satisfy the testing function, -1 is returned.</p><p>See also the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find">find()</a> method, which returns the first element that satisfies the testing function (rather than its index).</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[5, 12, 8, 130, 44];

function isLargeNumber(number) {
return number > 13;
}

Logger.debug(Array.findIndex(array1, isLargeNumber));
// Expected output: 3 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast">findLast</a></td><td valign="top">The <strong>Array.findLast()</strong> method iterates the array in reverse order and returns the value of the first element that satisfies the provided testing function. If no elements satisfy the testing function, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined"><code>undefined</code></a> is returned.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const array1 = \[5, 12, 50, 130, 44];
const found = Array.findLast(array1, (element) => element > 45);

Logger.debug(found);
// Expected output: '130' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex">findLastIndex</a></td><td valign="top">The <strong>Array.findLastIndex()</strong> method iterates the array in reverse order and returns the index of the first element that satisfies the provided testing function. If no elements satisfy the testing function, -1 is returned.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const array1 = \[5, 12, 50, 130, 44];
const isLargeNumber = (element) => element > 45;

Logger.debug(Array.findLastIndex(array1, isLargeNumber));
// Expected output: '3' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat">flat</a></td><td valign="top">The <strong>Array.flat()</strong> method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const arr1 = \[0, 1, 2, \[3, 4]];

Logger.debug(JSON.stringify(Array.flat(arr1)));
// Expected output: “\[0,1,2,3,4]”

const arr2 = \[0, 1, \[2, \[3, \[4, 5]]]];

Logger.debug(JSON.stringify(Array.flat(arr2)));
// Expected output: “\[0,1,2,\[3,\[4,5]]]”

Logger.debug(JSON.stringify(Array.flat(arr2, 2)));
// Expected output: “\[0,1,2,3, Array \[4,5]]”

Logger.debug(JSON.stringify(Array.flat(arr2, Infinity)));
// Expected output: “\[0,1,2,3,4,5]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap">flatMap</a></td><td valign="top">The <strong>Array.flatMap()</strong> method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const arr1 = \[1, 2, 1];

function callbackFunc(number) {
return number === 2 ? \[2, 2] : 1;
}

const result = Array.flatMap(arr1, callbackFunc);

Logger.debug(JSON.stringify(result));
// Expected output: “\[1,2,2,1]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach">forEach</a></td><td valign="top">The <strong>Array.forEach()</strong> method executes a provided function once for each array element.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const array1 = \['a', 'b', 'c'];

Array.forEach(array1, (element) => Logger.debug(element));
// Expected output: 'a','b','c' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from">from</a></td><td valign="top">The <strong>Array.from()</strong> static method creates a new, shallow-copied Array instance from an <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol">iterable</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects">array-like</a> object.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">Logger.debug(JSON.stringify(Array.from('foo')));
// Expected output: \["f","o","o"] </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes">includes</a></td><td valign="top">The <strong>Array.includes()</strong> method determines whether an array includes a certain value among its entries, returning true or false as appropriate.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[1, 2, 3];

Logger.debug(Array.includes(array1, 2));
// Expected output: true

const pets = \['cat', 'dog', 'bat'];

Logger.debug(Array.includes(pets, 'cat'));
// Expected output: true

Logger.debug(Array.includes(pets, 'at'));
// Expected output: false </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf">indexOf</a></td><td valign="top">The <strong>Array.indexOf()</strong> method returns the first index at which a given element can be found in the array, or -1 if it is not present.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const beasts = \['ant', 'bison', 'camel', 'duck', 'bison'];

Logger.debug(Array.indexOf(beasts, 'bison'));
// Expected output: 1

// Start from index 2
Logger.debug(Array.indexOf(beasts, 'bison', 2));
// Expected output: 4

Logger.debug(Array.indexOf(beasts, 'giraffe'));
// Expected output: -1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray">isArray</a></td><td valign="top">The <strong>Array.isArray()</strong> static method determines whether the passed value is an <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a>.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Array.isArray(\[1, 3, 5]));
// Expected output: true

Logger.debug(Array.isArray('\[]'));
// Expected output: false </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join">join</a></td><td valign="top">The <strong>Array.join()</strong> method creates and returns a new string by concatenating all of the elements in this array, separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const elements = \['Fire', 'Air', 'Water'];

Logger.debug(Array.join(elements));
// Expected output: "Fire,Air,Water"

Logger.debug(Array.join(elements, ''));
// Expected output: "FireAirWater"

Logger.debug(Array.join(elements, '-'));
// Expected output: "Fire-Air-Water" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys">keys</a></td><td valign="top">The <strong>Array.keys()</strong> method returns a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator"><em>array iterator</em></a> object that contains the keys for each index in the array.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const array1 = \['a', 'b', 'c'];

Logger.debug(JSON.stringify(Array.keys(array1)));
// Expected output: '\[0,1,2]' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf">lastIndexOf</a></td><td valign="top">The <strong>Array.lastIndexOf()</strong> method returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const animals = \['Dodo', 'Tiger', 'Penguin', 'Dodo'];

Logger.debug(Array.lastIndexOf(animals, 'Dodo'));
// Expected output: 3

Logger.debug(Array.lastIndexOf(animals, 'Tiger'));
// Expected output: 1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length">length</a></td><td valign="top">The <strong>Array.length</strong> data property represents the number of elements in that array. The value is an unsigned, 32-bit integer that is always numerically greater than the highest index in the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const clothing = \['shoes', 'shirts', 'socks', 'sweaters'];

Logger.debug(Array.length(clothing));
// Expected output: 4 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a></td><td valign="top">The <strong>Array.map()</strong> method creates a new array populated with the results of calling a provided function on every element in the calling array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[1, 4, 9, 16];

function double(number) {
return number \* 2;
}

const map1 = Array.map(array1, double);

Logger.debug(JSON.stringify(map1));
// Expected output: “\[2,8,18,32]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of">of</a></td><td valign="top">The <strong>Array.of()</strong> static method creates a new Array instance from a variable number of arguments, regardless of number or type of the arguments.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">Logger.debug(JSON.stringify(Array.of('foo', 2, 'bar', true)));
// Expected output: '\["foo",2,"bar",true]' </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop">pop</a></td><td valign="top">The <strong>Array.pop()</strong> method removes the last element from an array and returns that element. This method changes the length of the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const plants = \['broccoli', 'cauliflower', 'cabbage', 'kale', 'tomato'];

Logger.debug(Array.pop(plants));
// Expected output: "tomato"

Logger.debug(JSON.stringify(plants));
// Expected output: “\["broccoli","cauliflower","cabbage","kale"]”

Array.pop(plants);

Logger.debug(JSON.stringify(plants));
// Expected output: “\["broccoli","cauliflower","cabbage"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push">push</a></td><td valign="top">The <strong>Array.push()</strong> method adds the specified elements to the end of an array and returns the new length of the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const animals = \['pigs', 'goats', 'sheep'];

const count = Array.push(animals, 'cows');
Logger.debug(count);
// Expected output: 4
Logger.debug(JSON.stringify(animals));
// Expected output: “\["pigs","goats","sheep","cows"]”

Array.push(animals, 'chickens', 'cats', 'dogs');
Logger.debug(JSON.stringify(animals));
// Expected output: “\["pigs","goats","sheep","cows","chickens","cats","dogs"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce">reduce</a></td><td valign="top"><p>The <strong>Array.reduce()</strong> method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.</p><p>The first time that the callback is run there is no "return value of the previous calculation". If supplied, an initial value may be used in its place. Otherwise the array element at index 0 is used as the initial value and iteration starts from the next element (index 1 instead of index 0).</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[1, 2, 3, 4];

function reducer(accumulator, currentValue, index) {
return accumulator + currentValue;
}

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = Array.reduce(array1, reducer,
initialValue,
);

Logger.debug(sumWithInitial);
// Expected output: 10 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight">reduceRight</a></td><td valign="top">The <strong>Array.reduceRight()</strong> method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[
\[0, 1],
\[2, 3],
\[4, 5],
];

function reducer(accumulator, currentValue) {
return Array.concat(accumulator, currentValue);
}

const result = Array.reduceRight(array1, reducer);

Logger.debug(JSON.stringify(result));
// Expected output: “\[4,5,2,3,0,1]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse">reverse</a></td><td valign="top">The <strong>Array.reverse()</strong> method reverses an array <a href="https://en.wikipedia.org/wiki/In-place_algorithm"><em>in place</em></a> and returns the reference to the same array, the first array element now becoming the last, and the last array element becoming the first. In other words, elements order in the array will be turned towards the direction opposite to that previously stated.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \['one', 'two', 'three'];
Logger.debug(JSON.stringify(array1));
// Expected output: “\["one","two","three"]”

const reversed = Array.reverse(array1);
Logger.debug(JSON.stringify(reversed));
// Expected output: “\["three","two","one"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift">shift</a></td><td valign="top">The <strong>Array.shift()</strong> method removes the first element from an array and returns that removed element. This method changes the length of the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[1, 2, 3];

const firstElement = Array.shift(array1);

Logger.debug(JSON.stringify(array1));
// Expected output: “\[2,3]”

Logger.debug(firstElement);
// Expected output: 1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">slice</a></td><td valign="top">The <strong>Array.slice()</strong> method returns a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy">shallow copy</a> of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const animals = \['ant', 'bison', 'camel', 'duck', 'elephant'];

Logger.debug(JSON.stringify(Array.slice(animals, 2)));
// Expected output: “\["camel","duck","elephant"]”

Logger.debug((JSON.stringify(Array.slice(animals, 2, 4)));
// Expected output: “\["camel","duck"]”

Logger.debug((JSON.stringify(Array.slice(animals, 1, 5)));
// Expected output: “\["bison","camel","duck", "elephant"]”

Logger.debug((JSON.stringify(Array.slice(animals, -2)));
// Expected output: “\["duck","elephant"]”

Logger.debug((JSON.stringify(Array.slice(animals, 2, -1)));
// Expected output: “\["camel","duck"]”

Logger.debug((JSON.stringify(Array.slice(animals)));
// Expected output: “\["ant","bison","camel", "duck", "elephant"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some">some</a></td><td valign="top">The <strong>Array.some()</strong> method tests whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array = \[1, 2, 3, 4, 5];

function isEven(number) {
return number % 2 === 0;
}

Logger.debug(Array.some(array, isEven));
// Expected output: true </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">sort</a></td><td valign="top">The <strong>Array.sort()</strong> method sorts the elements of an array <a href="https://en.wikipedia.org/wiki/In-place_algorithm"><em>in place</em></a> and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">//Example 1 – Sort ascending:
const arr1 = \[5,2,1,9];

Array.sort(arr1);

Logger.debug(JSON.stringify(arr1));
// Expected output: “\[1,2,5,9]”

//Example 2 – Sort descending:
const arr1 = \[5,2,1,9];

function compareFn(a, b) {
return b - a;
}

Array.sort(arr1, compareFn);

Logger.debug(JSON.stringify(arr1));
// Expected output: “\[9,5,2,1]”

//Example 3:
const arr2 = \["b","d","c","a"];

function compareFn(a, b) {
if (a > b) {
return 1;
}
else if (a < b) {
return -1;
}
else {
return 0;
}
}

Array.sort(arr2, compareFn);

Logger.debug(JSON.stringify(arr2));
// Expected output: “\["a","b","c","d"]”

//Example 4:
const arr3 = \[{v:"b"},{v:"d"},{v:"c"},{v:"a"}];

function compareFn(a, b) {
if (a.v > b.v) {
return 1;
}
else if (a.v < b.v) {
return -1;
}
else {
return 0;
}
}

Array.sort(arr3, compareFn);

Logger.debug(JSON.stringify(arr3));
// Expected output: “\[{"v":"a"},{"v":"b"},{"v":"c"},{"v":"d"}]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">splice</a></td><td valign="top"><p>The <strong>Array.splice()</strong> method changes the contents of an array by removing or replacing existing elements and/or adding new elements <a href="https://en.wikipedia.org/wiki/In-place_algorithm">in place</a>.</p><p>To access part of an array without modifying it, see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">slice()</a>.</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const months = \['Jan', 'March', 'April', 'June'];
Array.splice(months, 1, 0, 'Feb');
// Inserts at index 1
Logger.debug(JSON.stringify(months));
// Expected output: “\["Jan","Feb","March","April", "June"]”

Array.splice(months, 4, 1, 'May');
// Replaces 1 element at index 4
Logger.debug(JSON.stringify(months));
// Expected output: “\["Jan","Feb","March","April","May"]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift">unshift</a></td><td valign="top">The <strong>Array.unshift()</strong> method adds the specified elements to the beginning of an array and returns the new length of the array.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const array1 = \[1, 2, 3];

Logger.debug(Array.unshift(array1, 4, 5));
// Expected output: 5

Logger.debug(JSON.stringify(array1));
// Expected output: “\[4,5,1,2,3]” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values">values</a></td><td valign="top">The <strong>Array.values()</strong> method returns a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator"><em>array iterator</em></a> object that iterates the value of each item in the array.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const array1 = \['a', 'b', 'c'];

Logger.debug(JSON.stringify(Array.values(array1)));
// Expected output: '\["a","b","c"]' </code></pre></td></tr></tbody></table>

### Objects

<table data-first-column-sticky><thead><tr><th width="128" valign="top">Function</th><th width="382" valign="top">Description</th><th width="527" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries">entries</a></td><td valign="top">The <strong>Object.entries()</strong> static method returns an array of a given object's own enumerable string-keyed property key-value pairs.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const obj = {
 a: 1,
 b: 2
};

// Iterate over entries
const entries = Object.entries(obj);

for (let x = 0; x < Array.length(entries); x++) {
const key = entries\[x]\[0];
const value = entries\[x]\[1];

Logger.debug('key: ' + key);
Logger.debug('value: ' + value);
}

// Expected output (Loop 1):
// key: a
// value: 1

// Expected output (Loop 2):
// key: b
// value: 2 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys">keys</a></td><td valign="top">The <strong>Object.keys()</strong> static method returns an array of a given object's own enumerable string-keyed property names.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const obj = {
a: 1,
b: 2
};

// Iterate over keys
const keys = Object.keys(obj);

for (let x = 0; x < Array.length(keys); x++) {
const key = keys\[x];
const value = obj\[key];

Logger.debug('key: ' + key);
Logger.debug('value: ' + value);
}

// Expected output (Loop 1):
// key: a
// value: 1

// Expected output (Loop 2):
// key: b
// value: 2 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values">values</a></td><td valign="top">The <strong>Object.values()</strong> static method returns an array of a given object's own enumerable string-keyed property values.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">const obj = {
a: 1,
b: 2
};

// Iterate over keys
const values = Object.values(obj);

for (let x = 0; x < Array.length(values); x++) {
const value = values\[x];

Logger.debug('value: ' + value);
}

// Expected output (Loop 1):
// value: 1

// Expected output (Loop 2):
// value: 2 </code></pre></td></tr></tbody></table>

### JSON

<table data-header-hidden="false" data-header-sticky data-first-column-sticky><thead><tr><th width="128" valign="top">Function</th><th width="382" valign="top">Description</th><th width="527" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse">parse</a></td><td valign="top">The <strong>JSON.parse()</strong> static method parses a JSON string, constructing the JavaScript value or object described by the string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const json = ‘{“result”:true, “count”:42}’;
const obj = JSON.parse(json);

Logger.debug(JSON.stringify(obj.count));
// Expected output: “42”

Logger.debug(JSON.stringify(obj.result));
// Expected output: “true” </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">stringify</a></td><td valign="top">The <strong>JSON.stringify()</strong> static method converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(JSON.stringify({ x: 5, y: 6 }));
// Expected output: “{"x":5,"y":6}”

Logger.debug(JSON.stringify(Date.datetime('2006-01-02T15:04:05.000Z'));
// Expected output: "2006-01-02T15:04:05.000Z" </code></pre></td></tr></tbody></table>

### XML

<table data-header-hidden="false" data-header-sticky data-first-column-sticky><thead><tr><th width="128" valign="top">Function</th><th width="382" valign="top">Description</th><th width="527" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse">parse</a></td><td valign="top">The <strong>XML.parse()</strong> static method parses a XML string, constructing the JSON value or object described by the string.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">const xml = '&#x3C;root>hello&#x3C;/root>';
const result = XML.parse(xml);

Logger.debug(result\[0].root\[0]\['#text']);
// Expected output: hello </code></pre></td></tr></tbody></table>

### Math

<table data-first-column-sticky><thead><tr><th width="128" valign="top">Function</th><th width="385" valign="top">Description</th><th width="581.3671875" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs">abs</a></td><td valign="top">The <strong>Math.abs()</strong> static method returns the absolute value of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function difference(a, b) {
  return Math.abs(a - b);
}

Logger.debug(difference(3, 5));
// Expected output: 2

Logger.debug(difference(5, 3));
// Expected output: 2

Logger.debug(difference(1.23456, 7.89012));
// Expected output: 6.6555599999999995 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos">acos</a></td><td valign="top">The <strong>Math.acos()</strong> static method returns the inverse cosine (in radians) of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">// Calculates angle of a right-angle triangle in radians
function calcAngle(adjacent, hypotenuse) {
return Math.acos(adjacent / hypotenuse);
}

Logger.debug(calcAngle(8, 10));
// Expected output: 0.6435011087932843

Logger.debug(calcAngle(5, 3));
// Expected output: NaN </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh">acosh</a></td><td valign="top">The <strong>Math.acosh()</strong> static method returns the inverse hyperbolic cosine of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.acosh(0.999999999999));
// Expected output: NaN

Logger.debug(Math.acosh(1));
// Expected output: 0

Logger.debug(Math.acosh(2));
// Expected output: 1.3169578969248166

Logger.debug(Math.acosh(2.5));
// Expected output: 1.566799236972411 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin">asin</a></td><td valign="top">The <strong>Math.asin()</strong> static method returns the inverse sine (in radians) of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">// Calculates angle of a right-angle triangle in radians
function calcAngle(opposite, hypotenuse) {
return Math.asin(opposite / hypotenuse);
}

Logger.debug(calcAngle(6, 10));
// Expected output: 0.6435011087932844

Logger.debug(calcAngle(5, 3));
// Expected output: NaN </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh">asinh</a></td><td valign="top">The <strong>Math.asinh()</strong> static method returns the inverse hyperbolic sine of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.asinh(1));
// Expected output: 0.881373587019543

Logger.debug(Math.asinh(0));
// Expected output: 0

Logger.debug(Math.asinh(-1));
// Expected output: -0.881373587019543

Logger.debug(Math.asinh(2));
// Expected output: 1.4436354751788103 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan">atan</a></td><td valign="top">The <strong>Math.atan()</strong> static method returns the inverse tangent (in radians) of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap" data-full-width="false"><code class="lang-javascript">// Calculates angle of a right-angle triangle in radians
function calcAngle(opposite, adjacent) {
return Math.atan(opposite / adjacent);
}

Logger.debug(calcAngle(8, 10));
// Expected output: 0.6747409422235527

Logger.debug(calcAngle(5, 3));
// Expected output: 1.0303768265243125 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2">atan2</a></td><td valign="top">The <strong>Math.atan2()</strong> static method returns the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (x, y), for Math.atan2(y, x).</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function calcAngleDegrees(x, y) {
return (Math.atan2(y, x) \* 180) / Math.PI;
}

Logger.debug(calcAngleDegrees(5, 5));
// Expected output: 45

Logger.debug(calcAngleDegrees(10, 10));
// Expected output: 45

Logger.debug(calcAngleDegrees(0, 10));
// Expected output: 90 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh">atanh</a></td><td valign="top">The <strong>Math.atanh()</strong> static method returns the inverse hyperbolic tangent of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.atanh(-1));
// Expected output: -Infinity

Logger.debug(Math.atanh(0));
// Expected output: 0

Logger.debug(Math.atanh(0.5));
// Expected output: 0.549306144334055 (approximately)

Logger.debug(Math.atanh(1));
// Expected output: Infinity </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt">cbrt</a></td><td valign="top">The <strong>Math.cbrt()</strong> static method returns the cube root of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.cbrt(-1));
// Expected output: -1

Logger.debug(Math.cbrt(1));
// Expected output: 1

Logger.debug(Math.cbrt(Infinity));
// Expected output: Infinity

Logger.debug(Math.cbrt(64));
// Expected output: 4 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil">ceil</a></td><td valign="top">The <strong>Math.ceil()</strong> static method always rounds up and returns the smallest integer greater than or equal to a given number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.ceil(0.95));
// Expected output: 1

Logger.debug(Math.ceil(4));
// Expected output: 4

Logger.debug(Math.ceil(7.004));
// Expected output: 8

Logger.debug(Math.ceil(-7.004));
// Expected output: -7 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32">clz32</a></td><td valign="top">The <strong>Math.clz32()</strong> static method returns the number of leading zero bits in the 32-bit binary representation of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">// 00000000000000000000000000000001
Logger.debug(Math.clz32(1));
// Expected output: 31

// 00000000000000000000000000000100
Logger.debug(Math.clz32(4));
// Expected output: 29

// 00000000000000000000001111101000
Logger.debug(Math.clz32(1000));
// Expected output: 22 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos">cos</a></td><td valign="top">The <strong>Math.cos()</strong> static method returns the cosine of a number in radians.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function getCircleX(radians, radius) {
return Math.cos(radians) \* radius;
}

Logger.debug(getCircleX(1, 10));
// Expected output: 5.403023058681398

Logger.debug(getCircleX(2, 10));
// Expected output: -4.161468365471424

Logger.debug(getCircleX(Math.PI, 10));
// Expected output: -10 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh">cosh</a></td><td valign="top">The <strong>Math.cosh()</strong> static method returns the hyperbolic cosine of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.cosh(0));
// Expected output: 1

Logger.debug(Math.cosh(1));
// Expected output: 1.543080634815244 (approximately)

Logger.debug(Math.cosh(-1));
// Expected output: 1.543080634815244 (approximately)

Logger.debug(Math.cosh(2));
// Expected output: 3.7621956910836314 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp">exp</a></td><td valign="top">The <strong>Math.exp()</strong> static method returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E">e</a> raised to the power of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.exp(0));
// Expected output: 1

Logger.debug(Math.exp(1));
// Expected output: 2.718281828459 (approximately)

Logger.debug(Math.exp(-1));
// Expected output: 0.36787944117144233

Logger.debug(Math.exp(2));
// Expected output: 7.38905609893065 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1">expm1</a></td><td valign="top">The <strong>Math.expm1()</strong> static method returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E">e</a> raised to the power of a number, subtracted by 1.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.expm1(0));
// Expected output: 0

Logger.debug(Math.expm1(1));
// Expected output: 1.718281828459045

Logger.debug(Math.expm1(-1));
// Expected output: -0.6321205588285577

Logger.debug(Math.expm1(2));
// Expected output: 6.38905609893065 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor">floor</a></td><td valign="top">The <strong>Math.floor()</strong> static method always rounds down and returns the largest integer less than or equal to a given number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.floor(5.95));
// Expected output: 5

Logger.debug(Math.floor(5.05));
// Expected output: 5

Logger.debug(Math.floor(5));
// Expected output: 5

Logger.debug(Math.floor(-5.05));
// Expected output: -6 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround">fround</a></td><td valign="top">The <strong>Math.fround()</strong> static method returns the nearest <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">32-bit single precision</a> float representation of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.fround(5.5));
// Expected output: 5.5

Logger.debug(Math.fround(5.05));
// Expected output: 5.050000190734863

Logger.debug(Math.fround(5));
// Expected output: 5

Logger.debug(Math.fround(-5.05));
// Expected output: -5.050000190734863 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot">hypot</a></td><td valign="top">The <strong>Math.hypot()</strong> static method returns the square root of the sum of squares of its arguments.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.hypot(3, 4));
// Expected output: 5

Logger.debug(Math.hypot(5, 12));
// Expected output: 13

Logger.debug(Math.hypot(3, 4, 5));
// Expected output: 7.0710678118654755

Logger.debug(Math.hypot(-5));
// Expected output: 5 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul">imul</a></td><td valign="top">The <strong>Math.imul()</strong> static method returns the result of the C-like 32-bit multiplication of the two parameters.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.imul(3, 4));
// Expected output: 12

Logger.debug(Math.imul(-5, 12));
// Expected output: -60

Logger.debug(Math.imul(0xffffffff, 5));
// Expected output: -5

Logger.debug(Math.imul(0xfffffffe, 5));
// Expected output: -10 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log">log</a></td><td valign="top">The <strong>Math.log()</strong> static method returns the natural logarithm (base <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E">e</a>) of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
}

// 2 x 2 x 2 = 8
Logger.debug(getBaseLog(2, 8));
// Expected output: 3

// 5 x 5 x 5 x 5 = 625
Logger.debug(getBaseLog(5, 625));
// Expected output: 4 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10">log10</a></td><td valign="top">The <strong>Math.log10()</strong> static method returns the base 10 logarithm of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.log10(100000));
// Expected output: 5

Logger.debug(Math.log10(2));
// Expected output: 0.3010299956639812

Logger.debug(Math.log10(1));
// Expected output: 0

Logger.debug(Math.log10(0));
// Expected output: -Infinity </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p">log1p</a></td><td valign="top">The <strong>Math.log1p()</strong> static method returns the natural logarithm (base <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E">e</a>) of 1 + x, where x is the argument.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.log1p(1));
// Expected output: 0.6931471805599453

Logger.debug(Math.log1p(0));
// Expected output: 0

Logger.debug(Math.log1p(-1));
// Expected output: -Infinity

Logger.debug(Math.log1p(-2));
// Expected output: NaN </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2">log2</a></td><td valign="top">The <strong>Math.log2()</strong> static method returns the base 2 logarithm of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.log2(3));
// Expected output: 1.584962500721156

Logger.debug(Math.log2(2));
// Expected output: 1

Logger.debug(Math.log2(1));
// Expected output: 0

Logger.debug(Math.log2(0));
// Expected output: -Infinity </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max">max</a></td><td valign="top">The <strong>Math.max()</strong> static method returns the largest of the numbers given as input parameters, or -<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a> if there are no parameters.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.max(1, 3, 2));
// Expected output: 3

Logger.debug(Math.max(-1, -3, -2));
// Expected output: -1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min">min</a></td><td valign="top">The <strong>Math.min()</strong> static method returns the smallest of the numbers given as input parameters, or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a> if there are no parameters.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.min(2, 3, 1));
// Expected output: 1

Logger.debug(Math.min(-2, -3, -1));
// Expected output: -3 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow">pow</a></td><td valign="top">The <strong>Math.pow()</strong> static method returns the value of a base raised to a power.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.pow(7, 3));
// Expected output: 343

Logger.debug(Math.pow(4, 0.5));
// Expected output: 2

Logger.debug(Math.pow(7, -2));
// Expected output: 0.02040816326530612
// (1/49)

Logger.debug(Math.pow(-7, 0.5));
// Expected output: NaN </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random">random</a></td><td valign="top">The <strong>Math.random()</strong> static method returns a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function getRandomInt(max) {
return Math.floor(Math.random() \* max);
}

Logger.debug(getRandomInt(3));
// Expected output: 0, 1 or 2

Logger.debug(getRandomInt(1));
// Expected output: 0

Logger.debug(Math.random());
// Expected output: a number from 0 to <1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round">round</a></td><td valign="top">The <strong>Math.round()</strong> static method returns the value of a number rounded to the nearest integer.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.round(0.9));
// Expected output: 1

Logger.debug(Math.round(5.95), Math.round(5.5), Math.round(5.05));
// Expected output: 6 6 5

Logger.debug(Math.round(-5.05), Math.round(-5.5), Math.round(-5.95));
// Expected output: -5 -5 -6 </code></pre></td></tr><tr><td valign="top"><strong>secureRandom</strong></td><td valign="top">The <strong>Math.secureRandom()</strong> static method returns a floating-point, random number generated using a <strong>CSPRNG</strong> (Cryptographically Secure Pseudo-Random Number Generator) that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">function getRandomInt(max) {
return Math.floor(Math.secureRandom() \* max);
}

Logger.debug(getRandomInt(3));
// Expected output: 0, 1 or 2

Logger.debug(getRandomInt(1));
// Expected output: 0

Logger.debug(Math.secureRandom());
// Expected output: a number from 0 to <1 </code></pre></td></tr><tr><td valign="top"><strong>secureRandomInt</strong></td><td valign="top">The <strong>Math.secureRandomInt()</strong> static method returns an integer in <code>\[min, max]</code>, inclusive at both ends, random number generated using a <strong>CSPRNG</strong> (Cryptographically Secure Pseudo-Random Number Generator).</td><td valign="top"><pre class="language-javascript"><code class="lang-javascript">return Math.secureRandomInt(7, 7);

// Expected output: 7 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign">sign</a></td><td valign="top">The <strong>Math.sign()</strong> static method returns 1 or -1, indicating the sign of the number passed as argument. If the input is 0 or -0, it will be returned as-is.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.sign(3));
// Expected output: 1

Logger.debug(Math.sign(-3));
// Expected output: -1

Logger.debug(Math.sign(0));
// Expected output: 0

Logger.debug(Math.sign('-3'));
// Expected output: -1 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin">sin</a></td><td valign="top">The <strong>Math.sin()</strong> static method returns the sine of a number in radians.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function getCircleY(radians, radius) {
return Math.sin(radians) \* radius;
}

Logger.debug(getCircleY(1, 10));
// Expected output: 8.414709848078965

Logger.debug(getCircleY(2, 10));
// Expected output: 9.092974268256818

Logger.debug(getCircleY(Math.PI, 10));
// Expected output: 1.2246467991473533e-15 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh">sinh</a></td><td valign="top">The <strong>Math.sinh()</strong> static method returns the hyperbolic sine of a number.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.sinh(0));
// Expected output: 0

Logger.debug(Math.sinh(1));
// Expected output: 1.1752011936438014

Logger.debug(Math.sinh(-1));
// Expected output: -1.1752011936438014

Logger.debug(Math.sinh(2));
// Expected output: 3.626860407847019 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt">sqrt</a></td><td valign="top">The <strong>Math.sqrt()</strong> static method returns the square root of a number. </td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function calcHypotenuse(a, b) {
return Math.sqrt(a \* a + b \* b);
}

Logger.debug(calcHypotenuse(3, 4));
// Expected output: 5

Logger.debug(calcHypotenuse(5, 12));
// Expected output: 13

Logger.debug(calcHypotenuse(0, 0));
// Expected output: 0 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan">tan</a></td><td valign="top">The <strong>Math.tan()</strong> static method returns the tangent of a number in radians.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function getTanFromDegrees(degrees) {
return Math.tan((degrees \* Math.PI) / 180);
}

Logger.debug(getTanFromDegrees(0));
// Expected output: 0

Logger.debug(getTanFromDegrees(45));
// Expected output: 0.9999999999999999

Logger.debug(getTanFromDegrees(90));
// Expected output: 16331239353195370 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh">tanh</a></td><td valign="top">The <strong>Math.tanh()</strong> static method returns the hyperbolic tangent of a number. </td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.tanh(-1));
// Expected output: -0.7615941559557649

Logger.debug(Math.tanh(0));
// Expected output: 0

Logger.debug(Math.tanh(Infinity));
// Expected output: 1

Logger.debug(Math.tanh(1));
// Expected output: 0.7615941559557649 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc">trunc</a></td><td valign="top"><p>The <strong>Math.trunc()</strong> static method returns the integer part of a number by removing any fractional digits.</p><p> </p><p> </p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Math.trunc(13.37));
// Expected output: 13

Logger.debug(Math.trunc(42.84));
// Expected output: 42

Logger.debug(Math.trunc(0.123));
// Expected output: 0

Logger.debug(Math.trunc(-0.123));
// Expected output: -0 </code></pre></td></tr></tbody></table>

### Numbers

<table data-first-column-sticky><thead><tr><th width="127.859375" valign="top">Function</th><th width="387.05078125" valign="top">Description</th><th width="555.078125" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite">isFinite</a></td><td valign="top">The <strong>Number.isFinite()</strong> static method determines whether the passed value is a finite number — that is, it checks that a given value is a number, and the number is neither positive <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a>, negative Infinity, nor <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN">NaN</a>.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Number.isFinite(1 / 0));
// Expected output: false

Logger.debug(Number.isFinite(10 / 5));
// Expected output: true

Logger.debug(Number.isFinite(0 / 0));
// Expected output: false </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger">isInteger</a></td><td valign="top"><p>The <strong>Number.isInteger()</strong> static method determines whether the passed value is an integer.</p><p> </p><p> </p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function fits(x, y) {
if (Number.isInteger(y / x)) {
return 'Fits!';
}
return 'Does NOT fit!';
}

Logger.debug(fits(5, 10));
// Expected output: "Fits!"

Logger.debug(fits(5, 11));
// Expected output: "Does NOT fit!" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN">isNaN</a></td><td valign="top">The <strong>Number.isNaN()</strong> static method determines whether the passed value is the number value <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN">NaN</a>, and returns false if the input is not of the Number type. It is a more robust version of the original, global <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN">isNaN()</a> function.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function typeOfNaN(x) {
if (Number.isNaN(x)) {
return 'Number NaN';
}
}

Logger.debug(typeOfNaN('100F'));
// Expected output: "NaN" </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger">isSafeInteger</a></td><td valign="top">The <strong>Number.isSafeInteger()</strong> static method determines whether the provided value is a number that is a <em>safe integer</em>.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function warn(x) {
if (Number.isSafeInteger(x)) {
return 'Precision safe.';
}
return 'Precision may be lost!';
}

Logger.debug(warn(Math.pow(2, 53)));
// Expected output: "Precision may be lost!"

Logger.debug(warn(Math.pow(2, 53) - 1));
// Expected output: "Precision safe." </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat">parseFloat</a></td><td valign="top">The <strong>Number.parseFloat()</strong> static method parses an argument and returns a floating point number. If a number cannot be parsed from the argument, it returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN">NaN</a>.</td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function circumference(r) {
if (Number.isNaN(Number.parseFloat(r))) {
return 0;
}
return Number.parseFloat(r) \* 2.0 \* Math.PI;
}

Logger.debug(circumference('4.567abcdefgh'));
// Expected output: 28.695307297889173

Logger.debug(circumference('abcdefgh'));
// Expected output: 0 </code></pre></td></tr><tr><td valign="top"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt">parseInt</a></td><td valign="top"><p>The <strong>Number.parseInt()</strong> static method parses a string argument and returns an integer of the specified radix or base.</p><p> </p><p> </p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function roughScale(x, base) {
const parsed = Number.parseInt(x, base);
if (Number.isNaN(parsed)) {
return 0;
}
return parsed \* 100;
}

Logger.debug(roughScale(' 0xF', 16));
// Expected output: 1500

Logger.debug(roughScale('321', 2));
// Expected output: 0 </code></pre></td></tr></tbody></table>

### Util

<table data-first-column-sticky><thead><tr><th width="131.375" valign="top">Function</th><th width="387.796875" valign="top">Description</th><th width="551.61328125" valign="top">Example</th></tr></thead><tbody><tr><td valign="top"><strong>randomId</strong></td><td valign="top"><p>The <strong>Util.randomId()</strong> method returns a cryptographically secure random value, encoded using a supported encoding.</p><p></p><p>The first parameter must be a string specifying one of the following encodings:</p><ul><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">base64</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">base32</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">base32hex</a></li><li><a href="https://datatracker.ietf.org/doc/html/rfc4648#section-8">base16</a></li><li><a href="https://rfc.zeromq.org/spec/32/">Z85</a></li></ul><p>The second parameter must be a number which specifies how many bytes the random value should be.</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Util.randomId('base64'));
// Expected output format: yC8tpqBMl2j3zxp7tsO3zdHsLcRCY1wyU44Djc5pCWI=

Logger.debug(Util.randomId('base64', 5)); <strong>// Expected output format: 1GRl4lI= </strong><strong> </strong>Logger.debug(Util.randomId('base64url'));
// Expected output format: lRnhwySDycxXBYz2C2q\_MPyPKP51FExtyiJAwurz47Q=

Logger.debug(Util.randomId('base32'));
// Expected output format: C4GZRB7LHO4GXMV7HFR7263FVHVHPKQNQAVHWXUOHLZJJTOGOXWQ====

Logger.debug(Util.randomId('base32hex'));
// Expected output format: 72Q94N0LGT4FULVV87G0VCUJOOBLCPOJU0SO2TLN1B647501SIM0====

Logger.debug(Util.randomId('base16'));
// Expected output format: 31302C51572B49743F366A67294C785152267D3566555859643753446C35614C

Logger.debug(Util.randomId('Z85'));
// Expected output format: @YD$z\&i\[oD%i/M:@3CO(P<8\[(OSer)\[ge]cK\[uia </code></pre></td></tr><tr><td valign="top"><strong>uuidv4</strong></td><td valign="top"><p>The <strong>Util.uuidv4()</strong> method returns a <a href="https://datatracker.ietf.org/doc/html/rfc4122">Universally Unique IDentifier (UUID)</a> also known as a GUID (Globally Unique IDentifier).</p><p></p><p>A UUID is 128 bits long, and can guarantee uniqueness across space and time.</p></td><td valign="top"><pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">Logger.debug(Util.uuidv4());
// Expected output format: b591fc9a-3c5d-43b1-9dfd-246a1e5d4941 </code></pre></td></tr></tbody></table>

&#x20;

## Regular Expressions

You can test with [Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) (RegExp) using the following syntax:

{% code overflow="wrap" %}

```javascript
const myVariable = "WEB-APP-027458";

// Test if myVariable ends with six-digits
if (myVariable =~ m/\d{6}$/) {
  return "myVariable has a numbered code applied";
}
```

{% endcode %}

## Context

When interacting with AFScript, each supported use will have a context which is automatically injected and made available for you to use.

The context is made up of data which is contextually relevant for the purpose for which you are applying AFScript.

For example, when updating the logic for project status calculations – the context will include data about the project, which can be used in your logic to derive the intended behaviour and result.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2Fd0QFR5tc3lx2diXrnmIw%2Fcontext.png?alt=media&amp;token=bb8de455-20d2-4b95-9dec-c5880f837e0b" alt=""><figcaption></figcaption></figure>

When building scripts and debugging, you can update the context to ensure the robustness of your logic and code.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2Fmd4G2f1xxCq1jGkGUq2H%2Fcontext%202.png?alt=media&amp;token=33c31825-2d42-4565-b952-966be8b2481d" alt=""><figcaption></figcaption></figure>

## Code Editing

AttackForge has a built-in lightweight code editor to help you to use AFScript.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FJDTlPNAv6b6VGt9DiTrE%2Feditor.png?alt=media&amp;token=b33dedf0-3f74-4ef9-8c44-2b8b881da582" alt=""><figcaption></figcaption></figure>

The editor supports autocomplete which also maps to the context.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2Fjglc36if5AiuySAtWzSQ%2FScreenshot%202024-09-22%20at%206.00.14%E2%80%AFPM.png?alt=media&amp;token=7480ae43-c028-4ce6-975f-b82c41fb7f7a" alt=""><figcaption></figcaption></figure>

You can also easily access [Built-in Functions](https://support.attackforge.com/attackforge-enterprise/afscript#built-in-functions).

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FpJ6SCvHzmrmv9k3Rxq0J%2FScreenshot%202024-09-22%20at%206.03.12%E2%80%AFPM.png?alt=media&amp;token=a051754a-a470-488d-ac4b-d1fe3f17c45b" alt=""><figcaption></figcaption></figure>

## Output

You can test the output of your script and logic. This helps to provide assurance that your script is working as expected prior to rolling it into the application.

The output can be viewed at the bottom of the page.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FFwqDfavQ8LMabpl2pyb3%2Foutput.png?alt=media&amp;token=3b922361-34a2-403d-bb37-bcc1f6df1601" alt=""><figcaption></figcaption></figure>

Clicking `Run` will run your code and show the output.

Clicking `Preserve log` will preserve logging from multiple runs in the output.

> **TIP**: you can use [Logging](https://support.attackforge.com/attackforge-enterprise/afscript#logging) to help you debug your code.

## Supported Use Cases

### Workflow Automation and Custom Integrations

You can use AFScript in your [Actions](https://support.attackforge.com/attackforge-enterprise/modules/flows#actions) using [Flows](https://support.attackforge.com/attackforge-enterprise/modules/flows).

### Project Status Calculations

You can use AFScript to change the logic for how project status is calculated.

To get started, click on **`Administration -> Projects -> Status -> Configure`**

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FLosvbv19J0TQlsM7sWy1%2F1.png?alt=media&amp;token=9b0b663b-200a-4065-8c59-74877c5ef010" alt=""><figcaption></figcaption></figure>

The default calculations for project status are included below.

> **IMPORTANT**: Project statuses relying on [Date.datetime()](https://support.attackforge.com/attackforge-enterprise/afscript#dates) will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

{% code overflow="wrap" %}

```javascript
if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const past24hours = Date.datetime('now', '-1 days', 'epoch');
   const endDateTime = Date.datetime(project.end_date, 'epoch');
   const overrun = endDateTime < past24hours;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0 && overrun) || (project.total_testcases > 0 && completedCounter === 0 && overrun)){
      status = 'Overrun';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (completedCounter === project.total_testcases) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}
```

{% endcode %}

The return value must be one of the following values:

* **"On Hold"**
* **"Overrun"**
* **"Retest"**
* **"Completed"**
* **"Testing"**
* **"Waiting to Start"**

Your code does not need to handle all statuses above if you do not intend to use all statuses.

#### Example 1: Project Completed based on Project End Date

This example will automatically show the project as completed if now (the time you are viewing the project status in the application) is any time after the project end date.

> **NOTE:** Overrun status has been removed as it logically does not apply under this example use.
>
> **IMPORTANT**: Project statuses relying on [Date.datetime()](https://support.attackforge.com/attackforge-enterprise/afscript#dates) will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

{% code overflow="wrap" %}

```javascript
if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const now = Date.datetime('now', 'epoch');
   const endDatePlus24Hours = Date.datetime(project.end_date, '1 days', 'epoch');
  // Check if project is completed. Allow for 24-hour grace period.
  const projectCompleted = now > endDatePlus24Hours || completedCounter === project.total_testcases;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (projectCompleted) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}
```

{% endcode %}

#### Example 2: Project Completed based on Custom Field

This example will automatically show the project as completed if a project custom field ‘project\_completed’ has a value of ‘Yes’.

> **NOTE:** This example requires configuration of a project custom field. For more information on how to do this, see [Custom Fields & Forms](https://support.attackforge.com/attackforge-enterprise/getting-started/custom-fields-and-forms).

> **NOTE:** Overrun status has been removed as it logically does not apply under this example use.
>
> **IMPORTANT**: Project statuses relying on [Date.datetime()](https://support.attackforge.com/attackforge-enterprise/afscript#dates) will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

{% code overflow="wrap" %}

```javascript
if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const now = Date.datetime('now', 'epoch');
   const endDatePlus24Hours = Date.datetime(project.end_date, '1 days', 'epoch');
  // Check if project is completed based on a project custom field "project_completed"
  const projectCompleted = (project.project_custom_fields?.project_completed === "Yes");
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if (completedCounter === project.total_testcases && project.total_retest_vulnerabilities > 0){
      status = 'Retest';
   }
   else if (projectCompleted) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}
```

{% endcode %}

#### Example 3: Project Retest based on Retesting Rounds

This example will automatically show the project as retest if there is at least one outstanding retesting round.

> **IMPORTANT**: Project statuses relying on [Date.datetime()](https://support.attackforge.com/attackforge-enterprise/afscript#dates) will be automatically updated every 5 minutes - it is not a live calculation. This is to ensure performance, especially when re-calculating all projects and comparing to 'now'.

{% code overflow="wrap" %}

```javascript
if (Number.isInteger(project.total_not_tested_testcases)
   && Number.isInteger(project.total_in_progress_testcases)
   && Number.isInteger(project.total_tested_testcases)
   && Number.isInteger(project.total_testcases)
   && Number.isInteger(project.total_retest_vulnerabilities)
   && Number.isInteger(project.total_not_applicable_testcases)
   && project.end_date !== undefined
){
   const waitingCounter = project.total_not_tested_testcases;
   const initiatedCounter = project.total_in_progress_testcases;
   const completedCounter = project.total_tested_testcases + project.total_not_applicable_testcases;
   const past24hours = Date.datetime('now', '-1 days', 'epoch');
   const endDateTime = Date.datetime(project.end_date, 'epoch');
   const overrun = endDateTime < past24hours;
   const isRetest = project.total_retests_completed < project.total_retests_requested;
 
   let status;
 
   if (project.on_hold) {
      status = 'On Hold';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0 && overrun) || (project.total_testcases > 0 && completedCounter === 0 && overrun)){
      status = 'Overrun';
   }
   else if (isRetest){
      status = 'Retest';
   }
   else if (completedCounter === project.total_testcases) {
      status = 'Completed';
   }
   else if ((completedCounter < project.total_testcases && completedCounter > 0) || (completedCounter < project.total_testcases && initiatedCounter > 0)){
      status = 'Testing';
   }
   else if (waitingCounter === project.total_testcases) {
      status = 'Waiting to Start';
   }
   return status;
}
```

{% endcode %}

### Suggested Values

You can use AFScript to suggest values for fields.

Suggestions can help to guide users into completing forms, based on your own logic.

For example:

* Suggest a project code or vulnerability code on a project
* Suggest a custom score for a vulnerability
* Suggest a budget for a project/test, based on how scoping questions have been answered
* Suggest missing evidence for a vulnerability
* Suggest execution flows for a test case

> **!IMPORTANT:** Suggested values are only supported on the Project form at present. This feature is expected to be widely supported in Q4, 2025.

#### Suggestion Formats

**Input fields**

Must return a string, for example:

{% code overflow="wrap" %}

```javascript
return "This is some text";
```

{% endcode %}

**Text Area fields**

Must return a string, for example:

{% code overflow="wrap" %}

```javascript
return "This is some text.\nThis is some more text.";
```

{% endcode %}

**Rich-Text fields**

Must return a string. Can be HTML, for example:

{% code overflow="wrap" %}

```javascript
return "<h1>This is some heading</h1>";
```

{% endcode %}

**Select fields**

Must return a string, for example:

{% code overflow="wrap" %}

```javascript
return "Yes";
```

{% endcode %}

**Multi-Select fields**

Must return a string array, for example:

{% code overflow="wrap" %}

```javascript
return ["Yes","Maybe"];
```

{% endcode %}

**Date fields**

Must return a string in ISO 8601 UTC format (YYYY-MM-DDThh:mm:ssZ), for example:

{% code overflow="wrap" %}

```javascript
return "2024-09-18T21:12:16.478Z";
```

{% endcode %}

**Table fields**

Must return an array of objects. Each object must include the key for the column field and appropriate values, for example:

{% code overflow="wrap" %}

```javascript
return [
    {
        "name": "Bruce Wayne",
        "role": "Defender of Gotham"
    }
];
```

{% endcode %}

**List fields**

Must return a string array, for example:

{% code overflow="wrap" %}

```javascript
return ["Tag 1","Tag 2"];
```

{% endcode %}

**User Select fields**

Must return a string array with each string as an Object Id, for example:

{% code overflow="wrap" %}

```javascript
return ["63cb153fedc40abef76bf991"];
```

{% endcode %}

**User Multi-Select fields**

Must return a string array with each string as an Object Id, for example:

{% code overflow="wrap" %}

```javascript
return ["63cb153fedc40abef76bf991"];
```

{% endcode %}

**Group Select fields**

Must return a string array with each string as an Object Id, for example:

{% code overflow="wrap" %}

```javascript
return ["63cb153fedc40abef76bf991"];
```

{% endcode %}

**Group Multi-Select fields**

Must return a string array with each string as an Object Id, for example:

{% code overflow="wrap" %}

```javascript
return ["63cb153fedc40abef76bf991"];
```

{% endcode %}

#### Project Code and Vulnerability Code&#x20;

You can suggest a custom project code and a vulnerability code prefix when creating or editing a project.

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FXybTLqefwAYu31rxnZc2%2FScreenshot%202024-10-21%20at%208.16.59%E2%80%AFpm.png?alt=media&amp;token=dedccff7-73cc-42ae-92e5-9d74eeeda25e" alt=""><figcaption></figcaption></figure>

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2F6ITCJL8mnzTyw0Y1mENW%2FScreenshot%202024-10-21%20at%208.17.31%E2%80%AFpm.png?alt=media&amp;token=43ad7ff3-0906-495d-ab0e-f99ab73c7c18" alt=""><figcaption></figcaption></figure>

To get started with **`Project Code`**, click on **`Administration -> Projects -> Fields (Code) -> Suggested Value (Configure)`**

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FQDlqZexTPT1Ev4bgKv9Y%2FScreenshot%202024-10-21%20at%208.13.42%E2%80%AFpm.png?alt=media&amp;token=6631927a-fdbf-457a-b335-1c2f9b9448b6" alt=""><figcaption></figcaption></figure>

To get started with **`Vulnerability Code`**, click on **`Administration -> Projects -> Fields (Vulnerability Code)`**

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2Fv0qPBtAcrQnitG1sxg0v%2FScreenshot%202024-10-21%20at%208.21.19%E2%80%AFpm.png?alt=media&amp;token=db606318-9f6e-4602-8d5f-5bb4db1605ee" alt=""><figcaption></figcaption></figure>

#### Example 1: Suggest A Code Based on the Customer

This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for).&#x20;

Example input: `ACME Corp.`

Example project code: `ACME`

> **PREREQUSITIES:**&#x20;
>
> * You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.

{% code overflow="wrap" %}

```javascript
const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];

    return projectCode;
}
else {
    return project?.last_project_code;
}
```

{% endcode %}

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FyNEhSppSGhce8bdLftot%2FScreenshot%202024-10-21%20at%208.31.15%E2%80%AFpm.png?alt=media&amp;token=968c6b29-079d-42fc-bf89-3093b05a5241" alt=""><figcaption></figcaption></figure>

Selecting the customer:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FiJisWq0Ga3MHH3OQEleP%2FScreenshot%202024-10-21%20at%208.29.28%E2%80%AFpm.png?alt=media&amp;token=8669b7ba-88f8-4b06-8213-a2bf4950f923" alt=""><figcaption></figcaption></figure>

Suggested project code:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FTIvJHLo9WOrKomX4k7UM%2FScreenshot%202024-10-21%20at%208.29.43%E2%80%AFpm.png?alt=media&amp;token=eb115fe9-38ad-4beb-96b3-b30a4aebfab1" alt=""><figcaption></figcaption></figure>

#### Example 2: Suggest A Code Based on the Customer and Testing Types

This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for) as well as the testing types assigned to the project.

Example input: `ACME Corp. + Web App`

Example project code: `ACME-WEBAPP`

> **PREREQUSITIES:**&#x20;
>
> * You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.
> * You must have a MULTI-SELECT type project custom field which is used to select the testing types assigned to the project. This example uses a custom field with a key 'testing\_types'.

{% code overflow="wrap" %}

```javascript
const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

const testingTypeMap = {
    "Web App": "WEBAPP",
    "API": "API",
    "Red Team": "REDTEAM",
    "Bug Bounty": "BB" 
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
    && project.project_custom_fields.testing_types[0]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];

    for (let x=0; x < project.project_custom_fields.testing_types.length; x++) {
        if (testingTypeMap[project.project_custom_fields.testing_types[x]]) {
            projectCode = projectCode + "-";
            projectCode = projectCode + testingTypeMap[project.project_custom_fields.testing_types[x]];
        }
    }

    return projectCode;
}
else {
    return project?.last_project_code;
}
```

{% endcode %}

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FxUz2FXbJt0X8VXwxGdYc%2FScreenshot%202024-10-21%20at%208.36.37%E2%80%AFpm.png?alt=media&amp;token=504c514d-5386-406c-a234-c931e2a53644" alt=""><figcaption></figcaption></figure>

Selecting the customer and testing types:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FpuRAmwFJxoT772iR0Edq%2FScreenshot%202024-10-21%20at%208.39.33%E2%80%AFpm.png?alt=media&amp;token=4f10c4b4-c598-4e7a-9606-42386692ae89" alt=""><figcaption></figcaption></figure>

Suggested project code:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2F1KmDeO4LVvYkHli3Ebwx%2FScreenshot%202024-10-21%20at%208.39.43%E2%80%AFpm.png?alt=media&amp;token=9bca6dab-c603-4977-a76f-8a146412c8ec" alt=""><figcaption></figcaption></figure>

#### Example 3: Suggest A Code Based on the Customer, Testing Types and a Generated Random Number

This example will suggest a code which is made up of the customer name (or a short name for the customer you can create a mapping for) as well as the testing types assigned to the project, and a random number.

Example input: `ACME Corp. + Web App`

Example project code: `ACME-WEBAPP-192834`

> **PREREQUSITIES:**&#x20;
>
> * You must have a SELECT type project custom field which is used to select the customer on the project. This example uses a custom field with a key 'customer'.
> * You must have a MULTI-SELECT type project custom field which is used to select the testing types assigned to the project. This example uses a custom field with a key 'testing\_types'.

{% code overflow="wrap" %}

```javascript
const customerMap = {
    "ACME Corp.": "ACME",
    "Red Team": "REDTEAM",
    "Pentesters": "PEN",
    "Globex Corp.": "GLOBEX-CORP"
};

const testingTypeMap = {
    "Web App": "WEBAPP",
    "API": "API",
    "Red Team": "REDTEAM",
    "Bug Bounty": "BB" 
};

if (project?.project_custom_fields?.customer?.name
    && customerMap[project.project_custom_fields.customer.name]
    && project.project_custom_fields.testing_types[0]
) {
    let projectCode = customerMap[project.project_custom_fields.customer.name];
    const randomNumber = Math.floor(Math.random() * 999999);

    for (let x=0; x < project.project_custom_fields.testing_types.length; x++) {
        if (testingTypeMap[project.project_custom_fields.testing_types[x]]) {
            projectCode = projectCode + "-";
            projectCode = projectCode + testingTypeMap[project.project_custom_fields.testing_types[x]];
        }
    }

    projectCode = projectCode + "-";
    projectCode = projectCode + randomNumber;

    return projectCode;
}
else {
    return project?.last_project_code;
}
```

{% endcode %}

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FIPxdQlZZIbbfUHdx9HVd%2FScreenshot%202024-10-21%20at%209.25.31%E2%80%AFpm.png?alt=media&amp;token=86fbbc2f-e7d3-41c7-9cce-1ce1f8a76288" alt=""><figcaption></figcaption></figure>

Selecting the customer and testing types:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2FwjPZwXHzJyPY5qo584CE%2FScreenshot%202024-10-21%20at%209.29.03%E2%80%AFpm.png?alt=media&amp;token=177e6f07-44d1-49c3-887b-ccaaa250e9ef" alt=""><figcaption></figcaption></figure>

Suggested project code:

<figure><img src="https://372186556-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M8s1QY2Q6YTHB4a6DMu%2Fuploads%2Fd1AaCoMvzJImJUGHMp4Y%2FScreenshot%202024-10-21%20at%209.29.11%E2%80%AFpm.png?alt=media&amp;token=36450f0b-1940-456d-9059-8e76f05e5f83" alt=""><figcaption></figcaption></figure>

## Standard library reference

Every built-in AFScript offers is a member of a namespace object in the global scope — `Array`, `String`, `Object`, `JSON`, `XML`, `Date`, `Math`, `Number`, `Util` and `Logger` — plus the two bare globals `Infinity` and `NaN`. All ten namespaces are `const`: a script can read them, and cannot replace them.

There is **no method call syntax** in AFScript, so nothing here is invoked on a value. The receiver is the first argument:

```javascript
Array.length([1, 2, 3]);            // 3
String.toUpperCase('abc');          // 'ABC'
Array.map([1, 2, 3], (n) => n * 2); // [2, 4, 6]
```

`Array.isArray`, `Array.of` and `String.from` are the exceptions — they are statics in JavaScript too, and take no receiver.

### `Array`

Thirty-five members. Every one but `Array.isArray` and `Array.of` takes the array as its first argument and throws if it is handed anything else — there is no array-like coercion anywhere in this namespace. The members that mutate their receiver (`fill`, `pop`, `push`, `reverse`, `shift`, `sort`, `splice`, `unshift`) mutate it in place and answer what their JavaScript counterparts answer.

`Array.entries`, `Array.keys` and `Array.values` answer **arrays, not iterators**: AFScript has no iterator protocol, so an iterator would be a value no script could consume. `for…of` walks an array or a string, so a pair is taken apart by destructuring the header.

#### `Array.at(array, index)`

Answers the element at `index`, counting from the end when `index` is negative.

| Parameter | Type   | Notes                             |
| --------- | ------ | --------------------------------- |
| `array`   | array  | the receiver                      |
| `index`   | number | negative counts back from the end |

**Returns** the element, or `undefined` when `index` is out of range.

**Throws** if `array` is not an array, or `index` is not a number.

```
Array.at([5, 12, 8, 130, 44], 2);    // 8
Array.at([5, 12, 8, 130, 44], -2);   // 130
Array.at([5, 12, 8, 130, 44], 100);  // undefined
```

#### `Array.concat(array, ...values)`

Answers a new array holding `array`'s elements followed by each of `values`; an argument that is itself an array is spread one level deep.

| Parameter   | Type  | Notes                          |
| ----------- | ----- | ------------------------------ |
| `array`     | array | the receiver                   |
| `...values` | any   | arrays are flattened one level |

**Returns** a new array. `array` is unchanged.

**Throws** if `array` is not an array.

```
Array.concat(['a', 'b'], ['c', 'd']);      // ['a', 'b', 'c', 'd']
Array.concat([1], [2, 3], 4);              // [1, 2, 3, 4]
Array.concat([1], [2, [3]]);               // [1, 2, [3]]
```

#### `Array.entries(array)`

Answers the index/element pairs.

| Parameter | Type  | Notes        |
| --------- | ----- | ------------ |
| `array`   | array | the receiver |

**Returns** an array of two-element `[index, element]` arrays.

**Throws** if `array` is not an array.

```
Array.entries(['a', 'b', 'c']);   // [[0, 'a'], [1, 'b'], [2, 'c']]
```

**Differs from JavaScript**, which answers an iterator. The array it answers walks the same way, and the pair destructures:

```
const rows = ['a', 'b'];
const out = [];

for (const [index, row] of Array.entries(rows)) {
  Array.push(out, `${index}: ${row}`);
}

out;   // ['0: a', '1: b']
```

#### `Array.every(array, callback)`

Answers whether `callback` is truthy for every element. Stops at the first falsy answer.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** a boolean. An empty array answers `true`.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.every([1, 30, 39, 29, 10, 13], (n) => n < 40);   // true
Array.every([12, 5, 8, 130, 44], (n) => n >= 10);      // false
```

#### `Array.fill(array, value, start?, end?)`

Overwrites the elements from `start` up to but not including `end` with `value`, **in place**.

| Parameter | Type   | Notes                                                  |
| --------- | ------ | ------------------------------------------------------ |
| `array`   | array  | the receiver; mutated                                  |
| `value`   | any    | written into every slot in range                       |
| `start?`  | number | integer; defaults to `0`, negative counts from the end |
| `end?`    | number | integer; defaults to the array's length                |

**Returns** the same array.

**Throws** if `array` is not an array, or `start`/`end` is present and not an integer.

```
Array.fill([1, 2, 3], 4);         // [4, 4, 4]
Array.fill([1, 2, 3], 4, 1);      // [1, 4, 4]
Array.fill([1, 2, 3], 4, 1, 2);   // [1, 4, 3]
```

#### `Array.filter(array, callback)`

Answers a new array of the elements for which `callback` is truthy.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** a new array, possibly empty. `array` is unchanged.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.filter([12, 5, 8, 130, 44], (n) => n >= 10);   // [12, 130, 44]

const vulns = [
  { title: 'SQL injection', severity: 'Critical' },
  { title: 'Verbose banner', severity: 'Low' },
];

Array.length(Array.filter(vulns, (v) => v.severity === 'Critical'));   // 1
```

#### `Array.find(array, callback)`

Answers the first element for which `callback` is truthy.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** the element, or `undefined` if none matches.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.find([5, 12, 8, 130, 44], (n) => n > 10);    // 12
Array.find([5, 12, 8, 130, 44], (n) => n > 500);   // undefined
```

#### `Array.findIndex(array, callback)`

Answers the index of the first element for which `callback` is truthy.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** the index, or `-1` if none matches.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.findIndex([5, 12, 8, 130, 44], (n) => n > 13);   // 3
Array.findIndex([5, 12, 8], (n) => n > 500);           // -1
```

#### `Array.findLast(array, callback)`

Answers the last element for which `callback` is truthy, searching from the end.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** the element, or `undefined` if none matches.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.findLast([5, 12, 50, 130, 44], (n) => n > 45);   // 130
```

#### `Array.findLastIndex(array, callback)`

Answers the index of the last element for which `callback` is truthy.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** the index, or `-1` if none matches.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.findLastIndex([5, 12, 50, 130, 44], (n) => n > 45);   // 3
```

#### `Array.flat(array, depth?)`

Answers a new array with nested arrays spliced into it, down to `depth` levels.

| Parameter | Type   | Notes                                                  |
| --------- | ------ | ------------------------------------------------------ |
| `array`   | array  | the receiver                                           |
| `depth?`  | number | defaults to `1`; pass `Infinity` to flatten completely |

**Returns** a new array. `array` is unchanged.

**Throws** if `array` is not an array, or `depth` is present and not a number.

```
Array.flat([0, 1, [2, [3, [4, 5]]]]);             // [0, 1, 2, [3, [4, 5]]]
Array.flat([0, 1, [2, [3, [4, 5]]]], 2);          // [0, 1, 2, 3, [4, 5]]
Array.flat([0, 1, [2, [3, [4, 5]]]], Infinity);   // [0, 1, 2, 3, 4, 5]
```

#### `Array.flatMap(array, callback)`

Maps every element and flattens the result by one level — `Array.map` followed by `Array.flat(_, 1)`, in a single pass.

| Parameter  | Type                               | Notes                          |
| ---------- | ---------------------------------- | ------------------------------ |
| `array`    | array                              | the receiver                   |
| `callback` | function `(element, index, array)` | may answer a value or an array |

**Returns** a new array. `array` is unchanged.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.flatMap([1, 2, 3, 4], (n) => [n * 2]);         // [2, 4, 6, 8]
Array.flatMap([1, 2, 3, 4], (n) => [[n * 2]]);       // [[2], [4], [6], [8]]
```

#### `Array.forEach(array, callback)`

Calls `callback` once for each element, for its effect.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** `undefined`, deliberately — use `Array.map` when you want the answers.

**Throws** if `array` is not an array, or `callback` is not a function.

```
const out = [];

Array.forEach(['a', 'b', 'c'], (letter) => Array.push(out, letter));

out;   // ['a', 'b', 'c']
```

#### `Array.from(value, callback?)`

Copies an array, or splits a string into its characters.

| Parameter   | Type                        | Notes                             |
| ----------- | --------------------------- | --------------------------------- |
| `value`     | array or string             | a string splits by **code point** |
| `callback?` | function `(element, index)` | maps each element on the way out  |

**Returns** a new array.

**Throws** if `value` is neither an array nor a string, or `callback` is present and not a function.

```
Array.from([1, 2, 3]);                  // [1, 2, 3]
Array.from('abc');                      // ['a', 'b', 'c']
Array.from('a😀b');                     // ['a', '😀', 'b']
Array.from([1, 2, 3], (n) => n * 2);    // [2, 4, 6]
```

**Differs from JavaScript**, which answers `[]` for anything it cannot iterate — `Array.from(5)` throws here rather than answering an empty array, because a silent empty array is the harder thing to debug. Array-like objects are not accepted. Splitting by code point means an astral character is one element, where `String.length` would count two UTF-16 units.

#### `Array.includes(array, searchElement, fromIndex?)`

Answers whether `searchElement` is in `array`, compared with `===` (except that `NaN` matches `NaN`).

| Parameter       | Type   | Notes                                                  |
| --------------- | ------ | ------------------------------------------------------ |
| `array`         | array  | the receiver                                           |
| `searchElement` | any    |                                                        |
| `fromIndex?`    | number | integer; defaults to `0`, negative counts from the end |

**Returns** a boolean.

**Throws** if `array` is not an array, or `fromIndex` is present and not an integer.

```
Array.includes([1, 2, 3], 2);                  // true
Array.includes(['a', 'b', 'c'], 'c', 3);       // false
Array.includes(['a', 'b', 'c'], 'c', -1);      // true
```

#### `Array.indexOf(array, searchElement, fromIndex?)`

Answers the first index at which `searchElement` is found, compared with `===`.

| Parameter       | Type   | Notes                                                  |
| --------------- | ------ | ------------------------------------------------------ |
| `array`         | array  | the receiver                                           |
| `searchElement` | any    |                                                        |
| `fromIndex?`    | number | integer; defaults to `0`, negative counts from the end |

**Returns** the index, or `-1`.

**Throws** if `array` is not an array, or `fromIndex` is present and not an integer.

```
Array.indexOf(['ant', 'bison', 'camel'], 'bison');      // 1
Array.indexOf(['ant', 'bison', 'camel'], 'giraffe');    // -1
Array.indexOf(['ant', 'bison', 'ant'], 'ant', 1);       // 2
```

#### `Array.isArray(value)`

Answers whether `value` is an array. A static — there is no receiver, and nothing to throw on.

| Parameter | Type | Notes |
| --------- | ---- | ----- |
| `value`   | any  |       |

**Returns** a boolean.

```
Array.isArray([1, 2, 3]);   // true
Array.isArray('abc');       // false
Array.isArray({ a: 1 });    // false
```

#### `Array.join(array, separator?)`

Concatenates the elements into a string.

| Parameter    | Type   | Notes             |
| ------------ | ------ | ----------------- |
| `array`      | array  | the receiver      |
| `separator?` | string | defaults to `','` |

**Returns** a string. `undefined` and `null` elements become the empty string.

**Throws** if `array` is not an array, or `separator` is present and not a string.

```
Array.join(['Fire', 'Air', 'Water']);         // 'Fire,Air,Water'
Array.join(['Fire', 'Air', 'Water'], '');     // 'FireAirWater'
Array.join(['Fire', 'Air', 'Water'], '-');    // 'Fire-Air-Water'
```

#### `Array.keys(array)`

Answers the indices.

| Parameter | Type  | Notes        |
| --------- | ----- | ------------ |
| `array`   | array | the receiver |

**Returns** an array of numbers.

**Throws** if `array` is not an array.

```
Array.keys(['a', 'b', 'c']);   // [0, 1, 2]
```

**Differs from JavaScript**, which answers an iterator.

#### `Array.lastIndexOf(array, searchElement, fromIndex?)`

Answers the last index at which `searchElement` is found, searching backwards.

| Parameter       | Type   | Notes                               |
| --------------- | ------ | ----------------------------------- |
| `array`         | array  | the receiver                        |
| `searchElement` | any    |                                     |
| `fromIndex?`    | number | integer; defaults to the last index |

**Returns** the index, or `-1`.

**Throws** if `array` is not an array, or `fromIndex` is present and not an integer.

```
Array.lastIndexOf(['ant', 'bison', 'ant'], 'ant');      // 2
Array.lastIndexOf(['ant', 'bison', 'ant'], 'ant', 1);   // 0
```

#### `Array.length(array)`

Answers the number of elements — AFScript has no property read for it.

| Parameter | Type  | Notes        |
| --------- | ----- | ------------ |
| `array`   | array | the receiver |

**Returns** a number.

**Throws** if `array` is not an array.

```
Array.length([1, 2, 3]);   // 3
Array.length([]);          // 0
```

#### `Array.map(array, callback)`

Answers a new array of `callback`'s answer for each element.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** a new array of the same length. `array` is unchanged.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.map([1, 4, 9, 16], (n) => n * 2);                    // [2, 8, 18, 32]
Array.map(['a', 'b'], (s, i) => `${i}:${s}`);              // ['0:a', '1:b']
```

#### `Array.of(...values)`

Answers an array of its arguments. A static, like its JavaScript original — **there is no receiver**.

| Parameter   | Type | Notes                              |
| ----------- | ---- | ---------------------------------- |
| `...values` | any  | every argument becomes one element |

**Returns** a new array.

```
Array.of(7);           // [7]
Array.of(1, 2, 3);     // [1, 2, 3]
Array.of();            // []
```

**Differs from JavaScript's `new Array(7)`**, not from `Array.of(7)`: this answers a one-element array holding `7`, never an array of length seven.

#### `Array.pop(array)`

Removes the last element, **in place**.

| Parameter | Type  | Notes                 |
| --------- | ----- | --------------------- |
| `array`   | array | the receiver; mutated |

**Returns** the removed element, or `undefined` if the array was empty.

**Throws** if `array` is not an array.

```
const plants = ['broccoli', 'cauliflower', 'kale'];

Array.pop(plants);   // 'kale'
plants;              // ['broccoli', 'cauliflower']
```

#### `Array.push(array, ...values)`

Appends `values`, **in place**.

| Parameter   | Type  | Notes                 |
| ----------- | ----- | --------------------- |
| `array`     | array | the receiver; mutated |
| `...values` | any   | appended in order     |

**Returns** the array's new length.

**Throws** if `array` is not an array.

```
const animals = ['pigs', 'goats'];

Array.push(animals, 'cows');            // 3
Array.push(animals, 'chickens', 'cats');// 5
animals;                                // ['pigs', 'goats', 'cows', 'chickens', 'cats']
```

#### `Array.reduce(array, callback, initialValue?)`

Folds the array left to right into a single value.

| Parameter       | Type                                            | Notes                                          |
| --------------- | ----------------------------------------------- | ---------------------------------------------- |
| `array`         | array                                           | the receiver                                   |
| `callback`      | function `(accumulator, element, index, array)` |                                                |
| `initialValue?` | any                                             | when omitted, the first element seeds the fold |

**Returns** the final accumulator.

**Throws** if `array` is not an array, or `callback` is not a function. An empty array with no `initialValue` throws from the host.

**Note.** An `initialValue` of `undefined` is indistinguishable from omitting it, so a fold cannot be seeded with `undefined`.

```
Array.reduce([1, 2, 3, 4], (a, b) => a + b);        // 10
Array.reduce([1, 2, 3, 4], (a, b) => a + b, 5);     // 15
Array.reduce([[0, 1], [2, 3]], (a, b) => Array.concat(a, b), []);   // [0, 1, 2, 3]
```

#### `Array.reduceRight(array, callback, initialValue?)`

Folds the array right to left.

| Parameter       | Type                                            | Notes                                         |
| --------------- | ----------------------------------------------- | --------------------------------------------- |
| `array`         | array                                           | the receiver                                  |
| `callback`      | function `(accumulator, element, index, array)` |                                               |
| `initialValue?` | any                                             | when omitted, the last element seeds the fold |

**Returns** the final accumulator.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.reduceRight([[0, 1], [2, 3], [4, 5]], (a, b) => Array.concat(a, b));   // [4, 5, 2, 3, 0, 1]
```

#### `Array.reverse(array)`

Reverses the order of the elements, **in place**.

| Parameter | Type  | Notes                 |
| --------- | ----- | --------------------- |
| `array`   | array | the receiver; mutated |

**Returns** the same array.

**Throws** if `array` is not an array.

```
Array.reverse(['one', 'two', 'three']);   // ['three', 'two', 'one']
```

#### `Array.shift(array)`

Removes the first element, **in place**.

| Parameter | Type  | Notes                 |
| --------- | ----- | --------------------- |
| `array`   | array | the receiver; mutated |

**Returns** the removed element, or `undefined` if the array was empty.

**Throws** if `array` is not an array.

```
const numbers = [1, 2, 3];

Array.shift(numbers);   // 1
numbers;                // [2, 3]
```

#### `Array.slice(array, start?, end?)`

Answers a shallow copy of the elements from `start` up to but not including `end`.

| Parameter | Type   | Notes                                         |
| --------- | ------ | --------------------------------------------- |
| `array`   | array  | the receiver                                  |
| `start?`  | number | defaults to `0`, negative counts from the end |
| `end?`    | number | defaults to the array's length                |

**Returns** a new array. `array` is unchanged.

**Throws** if `array` is not an array.

**Note.** This member counts its arguments, so `Array.slice(xs)`, `Array.slice(xs, undefined)` and `Array.slice(xs, undefined, undefined)` are three different calls — pass real indices rather than `undefined` placeholders.

```
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

Array.slice(animals, 2);      // ['camel', 'duck', 'elephant']
Array.slice(animals, 2, 4);   // ['camel', 'duck']
Array.slice(animals, -2);     // ['duck', 'elephant']
Array.slice(animals);         // ['ant', 'bison', 'camel', 'duck', 'elephant']
```

#### `Array.some(array, callback)`

Answers whether `callback` is truthy for at least one element. Stops at the first truthy answer.

| Parameter  | Type                               | Notes        |
| ---------- | ---------------------------------- | ------------ |
| `array`    | array                              | the receiver |
| `callback` | function `(element, index, array)` |              |

**Returns** a boolean. An empty array answers `false`.

**Throws** if `array` is not an array, or `callback` is not a function.

```
Array.some([1, 2, 3, 4, 5], (n) => n % 2 === 0);   // true
Array.some([1, 3, 5], (n) => n % 2 === 0);         // false
```

#### `Array.sort(array, compare?)`

Sorts the elements **in place**.

| Parameter  | Type              | Notes                                   |
| ---------- | ----------------- | --------------------------------------- |
| `array`    | array             | the receiver; mutated                   |
| `compare?` | function `(a, b)` | must declare **exactly two** parameters |

**Returns** the same array, so a sort can be written inline in a larger expression.

**Throws** if `array` is not an array, or `compare` is given and is not a two-parameter function. Omitting `compare` sorts by string comparison, as in JavaScript.

```
Array.sort(['b', 'd', 'c', 'a']);                    // ['a', 'b', 'c', 'd']
Array.sort([1, 30, 4, 21], (a, b) => a - b);         // [1, 4, 21, 30]
Array.sort([1, 30, 4, 21]);                          // [1, 21, 30, 4]
```

```
Array.sort([3, 1, 2], (a) => a);   // throws — the comparator declares one parameter
```

#### `Array.splice(array, start, deleteCount?, ...items)`

Removes `deleteCount` elements from `start` and inserts `items` there, **in place**.

| Parameter      | Type   | Notes                                            |
| -------------- | ------ | ------------------------------------------------ |
| `array`        | array  | the receiver; mutated                            |
| `start`        | number | negative counts from the end                     |
| `deleteCount?` | number | when omitted, everything from `start` is removed |
| `...items`     | any    | inserted at `start`                              |

**Returns** an array of the removed elements.

**Throws** if `array` is not an array.

**Note.** Like `Array.slice`, this counts its arguments: `Array.splice(xs, 1)` removes the tail, while `Array.splice(xs, 1, undefined)` removes nothing.

```
const months = ['Jan', 'March', 'April', 'June'];

Array.splice(months, 1, 0, 'Feb');   // []
months;                              // ['Jan', 'Feb', 'March', 'April', 'June']

Array.splice(months, 4, 1, 'May');   // ['June']
months;                              // ['Jan', 'Feb', 'March', 'April', 'May']

Array.splice(months, 2);             // ['March', 'April', 'May']
months;                              // ['Jan', 'Feb']
```

#### `Array.unshift(array, ...elements)`

Inserts `elements` at the front, **in place**.

| Parameter     | Type  | Notes                       |
| ------------- | ----- | --------------------------- |
| `array`       | array | the receiver; mutated       |
| `...elements` | any   | inserted in the order given |

**Returns** the array's new length.

**Throws** if `array` is not an array.

```
const numbers = [3, 4, 5];

Array.unshift(numbers, 1, 2);   // 5
numbers;                        // [1, 2, 3, 4, 5]
```

#### `Array.values(array)`

Answers a copy of the elements.

| Parameter | Type  | Notes        |
| --------- | ----- | ------------ |
| `array`   | array | the receiver |

**Returns** a new array.

**Throws** if `array` is not an array.

```
Array.values(['a', 'b', 'c']);   // ['a', 'b', 'c']
```

**Differs from JavaScript**, which answers an iterator.

### `String`

Thirty-two members: twenty-six for text, and six for encoding, hashing and signing. Every one takes the string as its first argument except `String.from`, which is the namespace's coercion function and takes the value to convert.

Nothing here coerces. `String.concat('n: ', 1)` throws where JavaScript would quietly answer `'n: 1'` — write `String.concat('n: ', String.from(1))` when that is what you mean.

The locale-dependent members (`localeCompare`, `toLocaleLowerCase`, `toLocaleUpperCase`) are deliberately absent: their answers vary with the host's ICU build, which makes a script's behaviour depend on where it runs.

Members that take a **pattern** — `match`, `matchAll`, `search`, `split`, `replace`, `replaceAll` — accept a regular-expression literal (`m/…/flags`) or a plain string. A string pattern is screened with `safe-regex` for catastrophic backtracking before it is compiled, and a fresh `RegExp` is built for every use because `lastIndex` is observable.

Encoding formats and hash algorithms are listed in the [appendices](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats).

#### `String.at(str, index)`

Answers the UTF-16 code unit at `index`, counting from the end when negative.

| Parameter | Type   | Notes              |
| --------- | ------ | ------------------ |
| `str`     | string | the receiver       |
| `index`   | number | must be an integer |

**Returns** a one-character string, or `undefined` when `index` is out of range.

**Throws** if `str` is not a string, or `index` is not an integer.

```
String.at('hello', 1);    // 'e'
String.at('hello', -1);   // 'o'
String.at('hello', 99);   // undefined
```

#### `String.charAt(str, index?)`

Answers the UTF-16 code unit at `index`.

| Parameter | Type   | Notes                                                                   |
| --------- | ------ | ----------------------------------------------------------------------- |
| `str`     | string | the receiver                                                            |
| `index?`  | number | integer; defaults to `0`. Negative values do **not** count from the end |

**Returns** a one-character string, or `''` when `index` is out of range.

**Throws** if `str` is not a string, or `index` is not an integer.

```
String.charAt('hello', 1);    // 'e'
String.charAt('hello', 10);   // ''
```

#### `String.concat(str, ...strings)`

Joins strings end to end.

| Parameter    | Type   | Notes                                       |
| ------------ | ------ | ------------------------------------------- |
| `str`        | string | the receiver                                |
| `...strings` | string | **every** argument must already be a string |

**Returns** a new string.

**Throws** if any argument is not a string — unlike JavaScript, which coerces.

```
String.concat('a', 'b', 'c');                    // 'abc'
String.concat('n: ', String.from(1));            // 'n: 1'
```

#### `String.decode(str, format)`

Decodes text that was encoded with `String.encode`.

| Parameter | Type   | Notes                                                                                                                                                  |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `str`     | string | the encoded text                                                                                                                                       |
| `format`  | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats); **no default** |

**Returns** the decoded string, read back as UTF-8.

**Throws** if either argument is not a string, or `format` is not a supported format.

```
String.decode('aGVsbG8=', 'base64');   // 'hello'
String.decode('NBSWY3DP', 'base32');   // 'hello'
```

#### `String.digest(str, algorithm?, format?)`

Hashes a string.

| Parameter    | Type   | Notes                                                                                                                                                          |
| ------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `str`        | string | hashed as UTF-8                                                                                                                                                |
| `algorithm?` | string | one of the [hash algorithms](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-hash-algorithms); defaults to `'SHA256'`   |
| `format?`    | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats); defaults to `'base16'` |

**Returns** the digest, encoded in `format`. Base16 output is upper-case.

**Throws** if any argument is not a string, or the algorithm or format is unsupported.

```
String.digest('hello');                        // '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824'
String.digest('hello', 'MD5', 'base16');       // '5D41402ABC4B2A76B9719D911017C592'
```

#### `String.encode(str, format)`

Encodes a string's UTF-8 bytes.

| Parameter | Type   | Notes                                                                                                                                                  |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `str`     | string | the receiver                                                                                                                                           |
| `format`  | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats); **no default** |

**Returns** the encoded string.

**Throws** if either argument is not a string, `format` is not supported, or `format` is `'Z85'` and the input is not a whole number of four-byte groups.

```
String.encode('hello', 'base64');      // 'aGVsbG8='
String.encode('hello', 'base32');      // 'NBSWY3DP'
String.encode('hello', 'base16');      // '68656C6C6F'
String.encode('ÿþ?', 'base64');        // 'w7/Dvj8='
String.encode('ÿþ?', 'base64url');     // 'w7_Dvj8='
```

#### `String.endsWith(str, searchString, endPosition?)`

Answers whether `str` ends with `searchString`.

| Parameter      | Type   | Notes                                                                          |
| -------------- | ------ | ------------------------------------------------------------------------------ |
| `str`          | string | the receiver                                                                   |
| `searchString` | string |                                                                                |
| `endPosition?` | number | integer; treat the string as if it were this long. Defaults to its real length |

**Returns** a boolean.

**Throws** if `str` or `searchString` is not a string, or `endPosition` is present and not an integer.

```
String.endsWith('Cats are best', 'best');        // true
String.endsWith('Cats are best', 'are', 8);      // true
```

#### `String.from(value?)`

Converts a value to its string form — the one member that coerces, and the way to opt into coercion everywhere else.

| Parameter | Type | Notes                    |
| --------- | ---- | ------------------------ |
| `value?`  | any  | omitting it answers `''` |

**Returns** a string. Never throws.

```
String.from();        // ''
String.from(42);      // '42'
String.from(true);    // 'true'
```

**Note.** This member counts its arguments: `String.from()` is `''`, while `String.from(undefined)` is `'undefined'`.

#### `String.hmac(str, key, algorithm?, format?)`

Computes a keyed hash (HMAC).

| Parameter    | Type   | Notes                                                                                                                                                          |
| ------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `str`        | string | the message                                                                                                                                                    |
| `key`        | string | the secret                                                                                                                                                     |
| `algorithm?` | string | one of the [hash algorithms](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-hash-algorithms); defaults to `'SHA256'`   |
| `format?`    | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats); defaults to `'base16'` |

**Returns** the MAC, encoded in `format`.

**Throws** if any argument is not a string, or the algorithm or format is unsupported.

```
String.hmac('message', 'secret');                            // '8B5F48702995C1598C573DB1E21866A9B825D4A794D169D7060A03605796360B'
String.hmac('message', 'secret', 'SHA256', 'base64');        // 'i19IcCmVwVmMVz2x4hhmqbgl1KeU0WnXBgoDYFeWNgs='
```

#### `String.includes(str, searchString, position?)`

Answers whether `searchString` occurs in `str`.

| Parameter      | Type   | Notes                                            |
| -------------- | ------ | ------------------------------------------------ |
| `str`          | string | the receiver                                     |
| `searchString` | string |                                                  |
| `position?`    | number | integer; where to start looking. Defaults to `0` |

**Returns** a boolean.

**Throws** if `str` or `searchString` is not a string, or `position` is not an integer.

```
String.includes('Blue Whale', 'Whale');     // true
String.includes('Blue Whale', 'whale');     // false
```

#### `String.indexOf(str, searchString, position?)`

Answers the first index at which `searchString` occurs.

| Parameter      | Type   | Notes                                            |
| -------------- | ------ | ------------------------------------------------ |
| `str`          | string | the receiver                                     |
| `searchString` | string |                                                  |
| `position?`    | number | integer; where to start looking. Defaults to `0` |

**Returns** the index, or `-1`.

**Throws** if `str` or `searchString` is not a string, or `position` is not an integer.

```
String.indexOf('Blue Whale', 'Whale');      // 5
String.indexOf('Blue Whale', 'Whales');     // -1
```

#### `String.lastIndexOf(str, searchString, position?)`

Answers the last index at which `searchString` occurs, searching backwards.

| Parameter      | Type   | Notes                                                                             |
| -------------- | ------ | --------------------------------------------------------------------------------- |
| `str`          | string | the receiver                                                                      |
| `searchString` | string |                                                                                   |
| `position?`    | number | integer; the last index the match may start at. Defaults to the end of the string |

**Returns** the index, or `-1`.

**Throws** if `str` or `searchString` is not a string, or `position` is present and not an integer.

**Note.** Unlike `String.indexOf`, an omitted `position` is *not* defaulted to `0` — this member searches backwards, so a `0` default would only ever find a match at the very start.

```
String.lastIndexOf('canal', 'a');        // 3
String.lastIndexOf('canal', 'a', 2);     // 1
```

#### `String.length(str)`

Answers the number of UTF-16 code units — AFScript has no property read for it.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a number.

**Throws** if `str` is not a string.

```
String.length('hello');   // 5
String.length('a😀b');    // 4
```

**Note.** An astral character counts as two units. `Array.length(Array.from('a😀b'))` is `3`, because `Array.from` splits by code point.

#### `String.match(str, pattern)`

Matches `pattern` once against `str`.

| Parameter | Type                         | Notes                                                             |
| --------- | ---------------------------- | ----------------------------------------------------------------- |
| `str`     | string                       | the receiver                                                      |
| `pattern` | regular expression or string | a string is screened by `safe-regex`, then compiled with no flags |

**Returns** an array holding the whole match followed by each capture group, or `null` if the pattern does not match.

**Throws** if `str` is not a string, `pattern` is neither a string nor a regular expression, or a string pattern is unsafe or will not compile.

```
String.match('The quick brown fox', m/[A-Z]/);   // ['T']
String.match('cat, bat, sat', m/([a-z])at/);     // ['cat', 'c']
String.match('abc', m/\d/);                      // null
```

**Differs from JavaScript**, which answers a match object carrying `index`, `input` and `groups`. This answers a plain array of strings — those extra properties are not reachable in AFScript anyway.

#### `String.matchAll(str, pattern)`

Matches `pattern` against `str` as many times as it occurs.

| Parameter | Type                         | Notes                                    |
| --------- | ---------------------------- | ---------------------------------------- |
| `str`     | string                       | the receiver                             |
| `pattern` | regular expression or string | the `g` flag is added when it is missing |

**Returns** an array of match arrays, each holding the whole match followed by its capture groups. An empty array means no match.

**Throws** on the same conditions as `String.match`.

```
String.matchAll('cat bat sat', m/([a-z])at/);   // [['cat', 'c'], ['bat', 'b'], ['sat', 's']]
String.matchAll('abc', m/\d/);                  // []
```

**Differs from JavaScript**, which refuses a non-global pattern outright. AFScript adds the flag to the pattern the call will use instead — the literal handed in is never modified.

#### `String.padEnd(str, targetLength, padString?)`

Pads the end of `str` until it is `targetLength` long.

| Parameter      | Type   | Notes                                                      |
| -------------- | ------ | ---------------------------------------------------------- |
| `str`          | string | the receiver                                               |
| `targetLength` | number | must be an integer                                         |
| `padString?`   | string | repeated as needed and truncated to fit; defaults to `' '` |

**Returns** a new string; the original when it is already long enough.

**Throws** if `str` or `padString` is not a string, or `targetLength` is not an integer.

```
String.padEnd('abc', 6);          // 'abc   '
String.padEnd('abc', 6, '.');     // 'abc...'
String.padEnd('abc', 2);          // 'abc'
```

#### `String.padStart(str, targetLength, padString?)`

Pads the front of `str` until it is `targetLength` long.

| Parameter      | Type   | Notes                                                      |
| -------------- | ------ | ---------------------------------------------------------- |
| `str`          | string | the receiver                                               |
| `targetLength` | number | must be an integer                                         |
| `padString?`   | string | repeated as needed and truncated to fit; defaults to `' '` |

**Returns** a new string.

**Throws** if `str` or `padString` is not a string, or `targetLength` is not an integer.

```
String.padStart('5', 3, '0');           // '005'
String.padStart('abc', 6);              // '   abc'
```

#### `String.repeat(str, count)`

Repeats `str`.

| Parameter | Type   | Notes                    |
| --------- | ------ | ------------------------ |
| `str`     | string | the receiver             |
| `count`   | number | integer, zero or greater |

**Returns** a new string; `''` when `count` is `0`.

**Throws** if `str` is not a string, or `count` is negative or not an integer. The check is made here rather than in the host, so the message names `String.repeat`.

```
String.repeat('-', 10);       // '----------'
String.repeat('abc', 0);      // ''
```

#### `String.replace(str, pattern, replacement)`

Replaces the first match of `pattern` — or every match, if `pattern` carries the `g` flag.

| Parameter     | Type                                                 | Notes                                            |
| ------------- | ---------------------------------------------------- | ------------------------------------------------ |
| `str`         | string                                               | the receiver                                     |
| `pattern`     | string or regular expression                         | a plain string matches literally, once           |
| `replacement` | string or function `(match, ...groups, offset, str)` | `$&`, `$1`… are honoured in a string replacement |

**Returns** a new string.

**Throws** if `str` is not a string, `pattern` is falsy, or `replacement` is neither a string nor a function.

```
String.replace('Hello world', 'world', 'there');                                    // 'Hello there'
String.replace('a1b2', m/\d/g, (d) => String.from(Number.parseInt(d) * 2));         // 'a2b4'
```

#### `String.replaceAll(str, pattern, replacement)`

Replaces every match of `pattern`.

| Parameter     | Type                                                 | Notes                                        |
| ------------- | ---------------------------------------------------- | -------------------------------------------- |
| `str`         | string                                               | the receiver                                 |
| `pattern`     | string or regular expression                         | a regular expression must carry the `g` flag |
| `replacement` | string or function `(match, ...groups, offset, str)` |                                              |

**Returns** a new string.

**Throws** on the same conditions as `String.replace`.

```
String.replaceAll('a-b-c', '-', '+');   // 'a+b+c'
```

#### `String.search(str, pattern)`

Finds where `pattern` first matches.

| Parameter | Type                         | Notes                                        |
| --------- | ---------------------------- | -------------------------------------------- |
| `str`     | string                       | the receiver                                 |
| `pattern` | regular expression or string | screened by `safe-regex` when it is a string |

**Returns** the index of the first match, or `-1`.

**Throws** if `str` is not a string, `pattern` is neither a string nor a regular expression, or a string pattern is unsafe or will not compile.

```
String.search('Hello world 42!', m/\d+/);   // 12
String.search('Hello world', m/\d+/);       // -1
```

#### `String.sign(str, privateKeyPem, algorithm?, encoding?)`

Signs a string with a PEM-encoded private key.

| Parameter       | Type   | Notes                                                                                                                                                          |
| --------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `str`           | string | the payload                                                                                                                                                    |
| `privateKeyPem` | string | PEM private key                                                                                                                                                |
| `algorithm?`    | string | one of the [hash algorithms](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-hash-algorithms); defaults to `'SHA256'`   |
| `encoding?`     | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats); defaults to `'base64'` |

**Returns** the signature, encoded in `encoding`.

**Throws** if any argument is not a string, the algorithm or encoding is unsupported, or the host rejects the key.

**Note.** The argument order is *not* the mirror of `String.verify`: signing takes `(algorithm, encoding)`, verifying takes `(encoding, algorithm)`.

```
const signature = String.sign(payload, privateKeyPem);                 // defaults: SHA256, base64
const sha512 = String.sign(payload, privateKeyPem, 'SHA512', 'base16');
```

#### `String.slice(str, indexStart, indexEnd?)`

Answers the substring from `indexStart` up to but not including `indexEnd`.

| Parameter    | Type   | Notes                                               |
| ------------ | ------ | --------------------------------------------------- |
| `str`        | string | the receiver                                        |
| `indexStart` | number | integer, **required**; negative counts from the end |
| `indexEnd?`  | number | integer; defaults to the end of the string          |

**Returns** a new string; `''` when the range is empty.

**Throws** if `str` is not a string, or either index is present and not an integer.

```
String.slice('The quick brown fox', 4, 9);    // 'quick'
String.slice('The quick brown fox', -3);      // 'fox'
```

#### `String.split(str, separator, limit?)`

Splits `str` into an array.

| Parameter   | Type                         | Notes                                                 |
| ----------- | ---------------------------- | ----------------------------------------------------- |
| `str`       | string                       | the receiver                                          |
| `separator` | string or regular expression | `''` splits into UTF-16 code units                    |
| `limit?`    | number                       | integer, zero or greater; stop after this many pieces |

**Returns** an array of strings.

**Throws** if `str` is not a string, `separator` is neither a string nor a regular expression, or `limit` is present and is not a non-negative integer.

```
String.split('a, b, c', ', ');    // ['a', 'b', 'c']
String.split('abc', '');          // ['a', 'b', 'c']
String.split('a1b2c', m/\d/);     // ['a', 'b', 'c']
String.split('a,b,c', ',', 2);    // ['a', 'b']
```

#### `String.startsWith(str, searchString, position?)`

Answers whether `str` starts with `searchString`.

| Parameter      | Type   | Notes                                               |
| -------------- | ------ | --------------------------------------------------- |
| `str`          | string | the receiver                                        |
| `searchString` | string |                                                     |
| `position?`    | number | integer; start the comparison here. Defaults to `0` |

**Returns** a boolean.

**Throws** if `str` or `searchString` is not a string, or `position` is not an integer.

```
String.startsWith('Saturday night', 'Sat');       // true
String.startsWith('Saturday night', 'night', 9);  // true
```

#### `String.substring(str, indexStart, indexEnd?)`

Answers the substring between two indices, swapping them if they are the wrong way round.

| Parameter    | Type   | Notes                                      |
| ------------ | ------ | ------------------------------------------ |
| `str`        | string | the receiver                               |
| `indexStart` | number | integer, **required**                      |
| `indexEnd?`  | number | integer; defaults to the end of the string |

**Returns** a new string.

**Throws** if `str` is not a string, or either index is present and not an integer.

**Note.** Negative indices clamp to `0` here, where `String.slice` counts them from the end.

```
String.substring('Mozilla', 1, 3);   // 'oz'
String.substring('Mozilla', 3, 1);   // 'oz'
String.substring('Mozilla', 4);      // 'lla'
```

#### `String.toLowerCase(str)`

Answers `str` in lower case.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a new string.

**Throws** if `str` is not a string.

```
String.toLowerCase('ALPHABET');   // 'alphabet'
```

#### `String.toUpperCase(str)`

Answers `str` in upper case.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a new string.

**Throws** if `str` is not a string.

```
String.toUpperCase('alphabet');   // 'ALPHABET'
```

#### `String.trim(str)`

Removes whitespace from both ends.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a new string.

**Throws** if `str` is not a string.

```
String.trim('   hello   ');   // 'hello'
```

#### `String.trimEnd(str)`

Removes trailing whitespace.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a new string.

**Throws** if `str` is not a string.

```
String.trimEnd('   hello   ');   // '   hello'
```

#### `String.trimStart(str)`

Removes leading whitespace.

| Parameter | Type   | Notes        |
| --------- | ------ | ------------ |
| `str`     | string | the receiver |

**Returns** a new string.

**Throws** if `str` is not a string.

```
String.trimStart('   hello   ');   // 'hello   '
```

#### `String.verify(str, publicKeyPem, signature, encoding?, algorithm?)`

Checks a signature made by `String.sign`.

| Parameter      | Type   | Notes                                                                                                                                                        |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `str`          | string | the payload that was signed                                                                                                                                  |
| `publicKeyPem` | string | PEM public key                                                                                                                                               |
| `signature`    | string | as produced by `String.sign`                                                                                                                                 |
| `encoding?`    | string | how `signature` is encoded; defaults to `'base64'`                                                                                                           |
| `algorithm?`   | string | one of the [hash algorithms](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-hash-algorithms); defaults to `'SHA256'` |

**Returns** `true` when the signature is valid, `false` when it is not.

**Throws** if any argument is not a string, or the encoding or algorithm is unsupported.

**Note.** `encoding` comes **before** `algorithm` here and after it in `String.sign`. Passing `String.sign`'s argument order to `String.verify` throws rather than answering `false`.

```
const signature = String.sign(payload, privateKeyPem, 'SHA512', 'base16');

String.verify(payload, publicKeyPem, signature, 'base16', 'SHA512');   // true
```

### `Object`

Three members. All three accept any non-null object — including an array, whose keys are its indices as strings — and throw on anything else, so `Object.keys('abc')` is an error rather than `['0', '1', '2']`.

Own properties only: AFScript refuses every inherited member read, so there is nothing else to see. There is no `assign`, `freeze`, `fromEntries` or `hasOwn`.

#### `Object.entries(obj)`

Answers the object's own key/value pairs.

| Parameter | Type            | Notes |
| --------- | --------------- | ----- |
| `obj`     | object or array |       |

**Returns** an array of two-element `[key, value]` arrays, in insertion order.

**Throws** if `obj` is not an object, or is `null`.

```
Object.entries({ a: 1, b: 2 });   // [['a', 1], ['b', 2]]
Object.entries({});               // []
```

#### `Object.keys(obj)`

Answers the object's own keys.

| Parameter | Type            | Notes |
| --------- | --------------- | ----- |
| `obj`     | object or array |       |

**Returns** an array of strings.

**Throws** if `obj` is not an object, or is `null`.

```
Object.keys({ a: 1, b: 2 });   // ['a', 'b']
Object.keys([7, 8]);           // ['0', '1']
```

#### `Object.values(obj)`

Answers the object's own values.

| Parameter | Type            | Notes |
| --------- | --------------- | ----- |
| `obj`     | object or array |       |

**Returns** an array of values, in the same order as `Object.keys`.

**Throws** if `obj` is not an object, or is `null`.

```
Object.values({ a: 1, b: 2 });   // [1, 2]
```

### `JSON`

#### `JSON.parse(text)`

Parses JSON text.

| Parameter | Type   | Notes |
| --------- | ------ | ----- |
| `text`    | string |       |

**Returns** the parsed value, or **`undefined` if `text` is not valid JSON**.

**Never throws** — a script that cares about the difference should test the answer.

```
JSON.parse('{"a":1}');       // { 'a': 1 }
JSON.parse('[1,2,3]');       // [1, 2, 3]
JSON.parse('nope');          // undefined
```

**Differs from JavaScript**, which throws a `SyntaxError`. There is no `reviver` argument.

#### `JSON.stringify(value, space?)`

Renders a value as JSON text.

| Parameter | Type             | Notes                                 |
| --------- | ---------------- | ------------------------------------- |
| `value`   | any              |                                       |
| `space?`  | number or string | indentation; anything else is ignored |

**Returns** a JSON string, or `undefined` for a value JSON cannot represent (`undefined`, or a function).

```
JSON.stringify({ a: 1, b: [2, 3] });   // '{"a":1,"b":[2,3]}'
JSON.stringify([1, 2]);                // '[1,2]'
```

**Differs from JavaScript**: there is no `replacer` argument, so `space` is the second argument rather than the third.

### `XML`

#### `XML.parse(xml)`

Parses an XML document into plain arrays and objects.

| Parameter | Type   | Notes |
| --------- | ------ | ----- |
| `xml`     | string |       |

**Returns** an array of nodes in document order, or **`undefined` if the document is malformed**. Attributes are gathered under `':@'` with an `@_` prefix on each name; text is a `#text` node.

**Throws** only if `xml` is not a string.

```
XML.parse('<a x="1">hi</a>');   // [{ 'a': [{ '#text': 'hi' }], ':@': { '@_x': '1' } }]
XML.parse('<<<');               // undefined
```

**Hardened**: `DOCTYPE` declarations are stripped and entity processing is off, so XXE and billion-laughs inputs are inert. Processing instructions and the XML declaration are dropped, and the result is copied before it reaches the script. Parser errors are swallowed rather than reported — answering `undefined` tells a script everything it can act on, and a stack trace would disclose the host's paths. There is no `XML.stringify`.

### `Date`

Two members. Both accept the same `timeValue`: the string `'now'`, an ISO-ish string, or an integer epoch in milliseconds. The accepted string forms are

* `YYYY-MM-DD`
* `YYYY-MM-DD HH:MM` or `YYYY-MM-DDTHH:MM`, optionally with a trailing `Z`
* `YYYY-MM-DD HH:MM:SS` or `YYYY-MM-DDTHH:MM:SS`, optionally with a trailing `Z`
* `YYYY-MM-DD HH:MM:SS.mmm` or `YYYY-MM-DDTHH:MM:SS.mmm`, optionally with a trailing `Z`

and every one of them is read as UTC. Anything else throws. There is no `Date` value type in AFScript: a timestamp is a string or a number, and these two members are how you move between them.

#### `Date.datetime(timeValue?, ...modifiers)`

Normalises a timestamp and applies modifiers to it, left to right, in UTC.

| Parameter      | Type             | Notes                                                                                                                                                       |
| -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `timeValue?`   | string or number | `'now'`, an accepted date string, or an integer epoch. Omitted means now                                                                                    |
| `...modifiers` | string           | applied in order; see the [modifier grammar](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-datedatetime-modifiers) |

**Returns** an ISO-8601 string — or a number, if the last modifier reached was `'epoch'`.

**Throws** if `timeValue` is neither an accepted string nor an integer. An unrecognised **modifier** is silently ignored.

```
Date.datetime('2020-06-01');                                   // '2020-06-01T00:00:00.000Z'
Date.datetime(1590969600000);                                  // '2020-06-01T00:00:00.000Z'
Date.datetime('2020-06-01T12:34:56.789Z', '-7 days');          // '2020-05-25T12:34:56.789Z'
Date.datetime('2020-06-01', '+1 months', 'start of month');    // '2020-07-01T00:00:00.000Z'
Date.datetime('2024-01-31', '+1 months');                      // '2024-02-29T00:00:00.000Z'
Date.datetime('2020-06-01', '+1.500 seconds');                 // '2020-06-01T00:00:01.500Z'
Date.datetime('2020-06-15', 'start of year');                  // '2020-01-01T00:00:00.000Z'
Date.datetime('2020-06-01', 'epoch');                          // 1590969600000
```

```
Date.datetime('now');            // e.g. '2026-09-01T02:14:07.512Z'
Date.datetime('now', 'epoch');   // e.g. 1788221647512
Date.datetime('now', '-7 days'); // e.g. '2026-08-25T02:14:07.512Z'
```

**Note.** Month and year arithmetic clamps to the last valid day of the target month rather than overflowing into the next one: 31 January plus one month is 29 February in a leap year, not 2 March.

#### `Date.format(timeValue, mask, tz?)`

Renders a timestamp through a `dateformat`-style mask.

| Parameter   | Type             | Notes                                                                    |
| ----------- | ---------------- | ------------------------------------------------------------------------ |
| `timeValue` | string or number | as for `Date.datetime`                                                   |
| `mask`      | string           | a token mask, or the name of a built-in mask                             |
| `tz?`       | string           | a timezone name, e.g. `'UTC'`. Defaults to the **host's** local timezone |

**Returns** a string.

**Throws** if `mask` is not a string, `tz` is present and not a string, or `timeValue` is not an accepted form.

**Note.** With no `tz`, output is rendered in whatever timezone the host runs in — the same script can answer differently on two machines. Pass `'UTC'`, or prefix the mask with `UTC:`, when the answer must be stable.

```
Date.format('2020-06-01', 'UTC:yyyy-mm-dd');                 // '2020-06-01'
Date.format('2020-06-01T13:05:00Z', 'HH:MM', 'UTC');         // '13:05'
Date.format('2020-06-09T13:05:07Z', 'UTC:dddd, mmmm dS');    // 'Tuesday, June 9th'
Date.format('2020-06-09T13:05:07Z', 'isoUtcDateTime');       // '2020-06-09T13:05:07Z'
```

Mask tokens, shown for `2020-06-09T13:05:07.089Z` in UTC:

| Token        | Answers               |   | Token             | Answers                   |
| ------------ | --------------------- | - | ----------------- | ------------------------- |
| `d` `dd`     | `9` `09`              |   | `h` `hh`          | `1` `01` (12-hour)        |
| `ddd` `dddd` | `Tue` `Tuesday`       |   | `H` `HH`          | `13` `13` (24-hour)       |
| `m` `mm`     | `6` `06`              |   | `M` `MM`          | `5` `05` (minutes)        |
| `mmm` `mmmm` | `Jun` `June`          |   | `s` `ss`          | `7` `07`                  |
| `yy` `yyyy`  | `20` `2020`           |   | `l` `L`           | `089` `08` (milliseconds) |
| `S`          | `th` (ordinal suffix) |   | `t` `tt` `T` `TT` | `p` `pm` `P` `PM`         |
| `N`          | `2` (ISO weekday)     |   | `Z` `o`           | `UTC` `+0000`             |
| `W`          | `24` (ISO week)       |   |                   |                           |

Built-in mask names: `default`, `shortDate`, `paddedShortDate`, `mediumDate`, `longDate`, `fullDate`, `shortTime`, `mediumTime`, `longTime`, `isoDate`, `isoTime`, `isoDateTime`, `isoUtcDateTime`, `expiresHeaderFormat`.

### `Math`

Eight constants and thirty-seven functions. All but `Math.secureRandom` and `Math.secureRandomInt` are the host's own `Math` intrinsics, passed through unwrapped — they behave exactly as they do in JavaScript, including its coercion rules, and answer `NaN` for arguments they cannot use rather than throwing. Their entries are written in short form.

`Math.random()` is the platform PRNG: fast, seeded per realm, and **not** a security primitive. When unpredictability matters, use `Math.secureRandom()` or `Math.secureRandomInt()`.

#### `Math.E`

Euler's number, the base of the natural logarithm.

```
Math.E;   // 2.718281828459045
```

#### `Math.LN10`

The natural logarithm of 10.

```
Math.LN10;   // 2.302585092994046
```

#### `Math.LN2`

The natural logarithm of 2.

```
Math.LN2;   // 0.6931471805599453
```

#### `Math.LOG10E`

The base-10 logarithm of `E`.

```
Math.LOG10E;   // 0.4342944819032518
```

#### `Math.LOG2E`

The base-2 logarithm of `E`.

```
Math.LOG2E;   // 1.4426950408889634
```

#### `Math.PI`

The ratio of a circle's circumference to its diameter.

```
Math.PI;   // 3.141592653589793
```

#### `Math.SQRT1_2`

The square root of ½.

```
Math.SQRT1_2;   // 0.7071067811865476
```

#### `Math.SQRT2`

The square root of 2.

```
Math.SQRT2;   // 1.4142135623730951
```

#### `Math.abs(x)`

Answers the absolute value of `x`.

```
Math.abs(-5);   // 5
```

#### `Math.acos(x)`

Answers the arc cosine of `x`, in radians. `NaN` outside `[-1, 1]`.

```
Math.acos(0.5);   // 1.0471975511965979
```

#### `Math.acosh(x)`

Answers the hyperbolic arc cosine of `x`. `NaN` below `1`.

```
Math.acosh(2);   // 1.3169578969248166
```

#### `Math.asin(x)`

Answers the arc sine of `x`, in radians. `NaN` outside `[-1, 1]`.

```
Math.asin(0.5);   // 0.5235987755982989
```

#### `Math.asinh(x)`

Answers the hyperbolic arc sine of `x`.

```
Math.asinh(1);   // 0.881373587019543
```

#### `Math.atan(x)`

Answers the arc tangent of `x`, in radians, between `-π/2` and `π/2`.

```
Math.atan(1);   // 0.7853981633974483
```

#### `Math.atan2(y, x)`

Answers the angle in radians between the positive x-axis and the point `(x, y)`. Note that `y` comes first.

```
Math.atan2(90, 15);   // 1.4056476493802699
```

#### `Math.atanh(x)`

Answers the hyperbolic arc tangent of `x`. `NaN` outside `(-1, 1)`.

```
Math.atanh(0.5);   // 0.5493061443340548
```

#### `Math.cbrt(x)`

Answers the cube root of `x`.

```
Math.cbrt(64);   // 4
```

#### `Math.ceil(x)`

Answers the smallest integer greater than or equal to `x`.

```
Math.ceil(0.95);      // 1
Math.ceil(-7.004);    // -7
```

#### `Math.clz32(x)`

Answers the number of leading zero bits in the 32-bit binary form of `x`.

```
Math.clz32(1);      // 31
Math.clz32(1000);   // 22
```

#### `Math.cos(x)`

Answers the cosine of `x`, which is in radians.

```
Math.cos(0);   // 1
```

#### `Math.cosh(x)`

Answers the hyperbolic cosine of `x`.

```
Math.cosh(1);   // 1.5430806348152437
```

#### `Math.exp(x)`

Answers `E` raised to the power `x`.

```
Math.exp(1);   // 2.718281828459045
```

#### `Math.expm1(x)`

Answers `Math.exp(x) - 1`, accurately for small `x`.

```
Math.expm1(1);   // 1.718281828459045
```

#### `Math.floor(x)`

Answers the largest integer less than or equal to `x`.

```
Math.floor(5.95);     // 5
Math.floor(-5.05);    // -6
```

#### `Math.fround(x)`

Answers the nearest 32-bit single-precision float to `x`.

```
Math.fround(5.5);    // 5.5
Math.fround(5.05);   // 5.050000190734863
```

#### `Math.hypot(...values)`

Answers the square root of the sum of the squares of its arguments.

```
Math.hypot(3, 4);   // 5
```

#### `Math.imul(a, b)`

Answers the result of 32-bit integer multiplication, with C-like wrapping.

```
Math.imul(3, 4);      // 12
Math.imul(-5, 12);    // -60
```

#### `Math.log(x)`

Answers the natural logarithm of `x`. `NaN` for negative `x`, `-Infinity` for `0`.

```
Math.log(1);   // 0
```

#### `Math.log10(x)`

Answers the base-10 logarithm of `x`.

```
Math.log10(100000);   // 5
```

#### `Math.log1p(x)`

Answers `Math.log(1 + x)`, accurately for small `x`.

```
Math.log1p(1);   // 0.6931471805599453
```

#### `Math.log2(x)`

Answers the base-2 logarithm of `x`.

```
Math.log2(8);   // 3
```

#### `Math.max(...values)`

Answers the largest of its arguments.

```
Math.max(1, 3, 2);   // 3
Math.max();          // -Infinity
```

**Note.** The largest element of an array is found by spreading it:

```
const xs = [4, 11, 7];

Math.max(...xs);   // 11
```

#### `Math.min(...values)`

Answers the smallest of its arguments.

```
Math.min(1, 3, 2);   // 1
Math.min();          // Infinity
```

#### `Math.pow(base, exponent)`

Answers `base` raised to the power `exponent` — the same as the `**` operator.

```
Math.pow(7, 3);   // 343
```

#### `Math.random()`

Answers a pseudo-random float in `[0, 1)` from the platform PRNG.

**Not a security primitive** — see `Math.secureRandom`.

```
Math.random();                                     // e.g. 0.5387253...
Math.floor(Math.random() * 6) + 1;                 // e.g. 4
```

#### `Math.round(x)`

Answers `x` rounded to the nearest integer; a half rounds towards `+Infinity`.

```
Math.round(20.49);    // 20
Math.round(20.5);     // 21
Math.round(-20.5);    // -20
```

#### `Math.secureRandom()`

Answers a float in `[0, 1)` drawn from the platform CSPRNG.

**Returns** a number in the same interval `Math.random()` uses — never `1` — so it drops straight into the usual index idiom. Fifty-three bits of randomness, the most a double can carry.

```
Math.secureRandom();                                        // e.g. 0.7342118...
Math.floor(Math.secureRandom() * 6) + 1;                    // e.g. 4
```

#### `Math.secureRandomInt(min, max)`

Answers an integer in `[min, max]`, **inclusive at both ends**, drawn from the platform CSPRNG by rejection sampling so that no value is favoured.

| Parameter | Type   | Notes                               |
| --------- | ------ | ----------------------------------- |
| `min`     | number | a safe integer; may be negative     |
| `max`     | number | a safe integer, not less than `min` |

**Returns** a number.

**Throws** if either argument is not a safe integer, if `min > max`, or if `max - min + 1` is not itself a safe integer.

```
Math.secureRandomInt(1, 6);      // e.g. 4 — a die roll
Math.secureRandomInt(5, 5);      // 5
```

```
Math.secureRandomInt(9, 2);      // throws — min is greater than max
```

```
Math.secureRandomInt(1.5, 6);    // throws — not an integer
```

#### `Math.sign(x)`

Answers `1`, `0` or `-1` according to the sign of `x`.

```
Math.sign(-3);   // -1
Math.sign(0);    // 0
```

#### `Math.sin(x)`

Answers the sine of `x`, which is in radians.

```
Math.sin(0);   // 0
```

#### `Math.sinh(x)`

Answers the hyperbolic sine of `x`.

```
Math.sinh(1);   // 1.1752011936438014
```

#### `Math.sqrt(x)`

Answers the square root of `x`. `NaN` for negative `x`.

```
Math.sqrt(9);   // 3
```

#### `Math.tan(x)`

Answers the tangent of `x`, which is in radians.

```
Math.tan(0);   // 0
```

#### `Math.tanh(x`

Answers the hyperbolic tangent of `x`.

```
Math.tanh(1);   // 0.7615941559557649
```

#### `Math.trunc(x)`

Answers the integer part of `x`, dropping any fractional digits.

```
Math.trunc(13.37);     // 13
Math.trunc(-13.37);    // -13
```

### `Number`

Eight constants and six functions, all of them the host's `Number` intrinsics passed through unwrapped. There is no `Number(...)` conversion function — use `Number.parseInt` or `Number.parseFloat` — and no `prototype` members, so there is no `toFixed`.

`Number.NaN` and the global `NaN` are the same value; so are `Number.POSITIVE_INFINITY` and the global `Infinity`.

#### `Number.EPSILON`

The difference between `1` and the smallest float greater than `1` — the usual tolerance for comparing two floats.

```
Number.EPSILON;                            // 2.220446049250313e-16
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;   // true
```

#### `Number.MAX_SAFE_INTEGER`

The largest integer that can be represented exactly, 2⁵³ − 1.

```
Number.MAX_SAFE_INTEGER;   // 9007199254740991
```

#### `Number.MAX_VALUE`

The largest representable positive number.

```
Number.MAX_VALUE;   // 1.7976931348623157e+308
```

#### `Number.MIN_SAFE_INTEGER`

The smallest integer that can be represented exactly, −(2⁵³ − 1).

```
Number.MIN_SAFE_INTEGER;   // -9007199254740991
```

#### `Number.MIN_VALUE`

The smallest representable positive number.

```
Number.MIN_VALUE;   // 5e-324
```

#### `Number.NaN`

Not-a-Number. Equal to nothing, including itself — test for it with `Number.isNaN`.

```
Number.NaN;                  // NaN
Number.NaN === Number.NaN;   // false
```

#### `Number.NEGATIVE_INFINITY`

Negative infinity.

```
Number.NEGATIVE_INFINITY;   // -Infinity
```

#### `Number.POSITIVE_INFINITY`

Positive infinity.

```
Number.POSITIVE_INFINITY;   // Infinity
```

#### `Number.isFinite(value)`

Answers whether `value` is a number and is neither infinite nor `NaN`. Does **not** coerce, so a numeric string answers `false`.

```
Number.isFinite(1);        // true
Number.isFinite(1 / 0);    // false
Number.isFinite('1');      // false
```

#### `Number.isInteger(value)`

Answers whether `value` is a number with no fractional part.

```
Number.isInteger(5);      // true
Number.isInteger(5.1);    // false
```

#### `Number.isNaN(value)`

Answers whether `value` is exactly `NaN`. Does not coerce, so a non-numeric string answers `false`.

```
Number.isNaN(0 / 0);     // true
Number.isNaN('abc');     // false
```

#### `Number.isSafeInteger(value)`

Answers whether `value` is an integer that can be represented exactly.

```
Number.isSafeInteger(9007199254740991);   // true
Number.isSafeInteger(9007199254740992);   // false
```

#### `Number.parseFloat(string)`

Reads a decimal number from the front of a string.

**Returns** the number, or `NaN` if the string does not start with one. Trailing text is ignored.

```
Number.parseFloat('3.14 more');   // 3.14
Number.parseFloat('abc');         // NaN
```

#### `Number.parseInt(string, radix?)`

Reads an integer from the front of a string.

| Parameter | Type   | Notes                                                       |
| --------- | ------ | ----------------------------------------------------------- |
| `string`  | string | leading whitespace is skipped                               |
| `radix?`  | number | 2 to 36; always pass it when the input may be user-supplied |

**Returns** the integer, or `NaN`.

```
Number.parseInt('42px');       // 42
Number.parseInt('ff', 16);     // 255
Number.parseInt('abc');        // NaN
```

### `Util`

Two members, both backed by the platform CSPRNG.

#### `Util.randomId(format, nbytes?)`

Answers a random identifier.

| Parameter | Type   | Notes                                                                                                                                  |
| --------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `format`  | string | one of the [encoding formats](https://github.com/Launchrock-aus/afscript/blob/main/docs/standard-library.md#appendix-encoding-formats) |
| `nbytes?` | number | how many random bytes to draw; a positive integer, defaults to `32`                                                                    |

**Returns** the encoded bytes as a string. The string is longer than `nbytes` — base64 answers about 4 characters per 3 bytes, base16 exactly 2 per byte.

**Throws** if `format` is unsupported, `nbytes` is not a positive integer, or `format` is `'Z85'` and `nbytes` is not divisible by 4.

```
Util.randomId('base64');           // e.g. 'ZLOSdOxTBs4qOd0IB7T2ecUcYTEHrxNUx1sK+1uSSA0='
Util.randomId('base16', 8);        // e.g. '3F0A9C41B27E5D08'
String.length(Util.randomId('base16', 8));   // 16
```

```
Util.randomId('Z85', 6);   // throws — Z85 needs a byte count divisible by 4
```

#### `Util.uuidv4()`

Answers a random (version 4) UUID.

**Returns** a 36-character lower-case string in the canonical `8-4-4-4-12` form.

```
Util.uuidv4();                       // e.g. '1f9e6a3c-4b21-4d0e-9f6a-2c8b7d5e1a04'
String.length(Util.uuidv4());        // 36
```

### `Logger`

Six members, one per level, from most to least severe: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. They are a script's only output — there is no `console`, and the interpreter never writes to the host's own.

Each takes any number of values, converts each to a string (`undefined` and `null` become `'undefined'` and `'null'`), joins them with a single space, and emits the result at its level. A message below the configured `logLevel` is dropped. All six answer `undefined`.

Where the message goes is the host's business: the `AFScript` class emits a `message` event carrying `{ logLevel, message }`.

#### `Logger.fatal(...values)`

Emits at level `FATAL` (1) — the level that is never filtered out.

```
Logger.fatal('cannot continue:', 'no project in scope');
```

#### `Logger.error(...values)`

Emits at level `ERROR` (2).

```
Logger.error('failed to parse finding', 42);
```

#### `Logger.warn(...values)`

Emits at level `WARN` (3).

```
Logger.warn('missing severity, defaulting to Low');
```

#### `Logger.info(...values)`

Emits at level `INFO` (4).

```
Logger.info('scanning', 'Acme Q3');
```

#### `Logger.debug(...values)`

Emits at level `DEBUG` (5). Objects and arrays are worth wrapping in `JSON.stringify` — plain conversion answers `[object Object]`, as it does in JavaScript.

```
Logger.debug('rows:', JSON.stringify([1, 2, 3]));
```

#### `Logger.trace(...values)`

Emits at level `TRACE` (6), the noisiest level.

```
Logger.trace('entering loop with', 3, 'rows');
```

### Global values

#### `Infinity`

Positive infinity — the same value as `Number.POSITIVE_INFINITY`.

```javascript
Infinity;         // Infinity
1 / 0;            // Infinity
-Infinity;        // -Infinity
```

#### `NaN`

Not-a-Number — the same value as `Number.NaN`. It compares equal to nothing, itself included.

```javascript
NaN;              // NaN
NaN === NaN;      // false
Number.isNaN(NaN);// true
```

These two are the only global values the standard library defines. Everything else a script sees in the global scope is a namespace object, or a symbol the host passed in.

### Appendix: encoding formats

The same six formats are accepted by `String.encode`, `String.decode`, `String.digest`, `String.hmac`, `String.sign`, `String.verify` and `Util.randomId`. They are matched exactly — note the capitals on `Z85`.

| Format        | `String.encode('abcd', …)` | Notes                                                                                          |
| ------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
| `'base64'`    | `YWJjZA==`                 | RFC 4648 §4, padded with `=`                                                                   |
| `'base64url'` | `YWJjZA==`                 | RFC 4648 §5 — `-` and `_` in place of `+` and `/`, so the result is URL- and filename-safe     |
| `'base32'`    | `MFRGGZA=`                 | RFC 4648 §6, upper-case                                                                        |
| `'base32hex'` | `C5H66P0=`                 | RFC 4648 §7 — the extended-hex alphabet, which sorts in the same order as the bytes it encodes |
| `'base16'`    | `61626364`                 | Hexadecimal, **upper-case**                                                                    |
| `'Z85'`       | `vpA.S`                    | ZeroMQ Z85 — the most compact of the six, at 5 characters per 4 bytes                          |

**`Z85` only accepts whole four-byte groups.** `String.encode('hello', 'Z85')` throws, because five bytes is not a multiple of four; so does `Util.randomId('Z85', 6)`. Decoding it needs a whole number of five-character groups. Hash digests are always a multiple of four bytes, so `String.digest` and `String.hmac` can use `'Z85'` with any algorithm.

```javascript
String.encode('abcd', 'base64');            // 'YWJjZA=='
String.decode('YWJjZA==', 'base64');        // 'abcd'
String.encode('abcd', 'Z85');               // 'vpA.S'
```

### Appendix: hash algorithms

Accepted by `String.digest`, `String.hmac`, `String.sign` and `String.verify`, matched exactly:

| Algorithm  | Digest size | Notes                                                                                     |
| ---------- | ----------- | ----------------------------------------------------------------------------------------- |
| `'MD5'`    | 128 bits    | Broken. Checksums only, never signatures or passwords                                     |
| `'RMD160'` | 160 bits    | RIPEMD-160                                                                                |
| `'SHA1'`   | 160 bits    | Collision-broken. Legacy interoperability only                                            |
| `'SHA224'` | 224 bits    |                                                                                           |
| `'SHA256'` | 256 bits    | The default everywhere, and the right choice unless something external dictates otherwise |
| `'SHA384'` | 384 bits    |                                                                                           |
| `'SHA512'` | 512 bits    |                                                                                           |

Defaults: `String.digest` and `String.hmac` answer `SHA256` in `base16`; `String.sign` and `String.verify` use `SHA256` and `base64`.

Neither hashing nor signing is a password primitive — there is no key-derivation function here, and `String.digest` is not one.

### Appendix: `Date.datetime` modifiers

`Date.datetime` applies its modifiers left to right, in UTC, to an immutable value. A modifier it does not recognise is **silently ignored**.

| Modifier                        | Effect                                                                           |
| ------------------------------- | -------------------------------------------------------------------------------- |
| `'+N days'` / `'-N days'`       | Adds or subtracts whole days. `N` is 1 to 6 digits                               |
| `'+N hours'` / `'-N hours'`     | Hours                                                                            |
| `'+N minutes'` / `'-N minutes'` | Minutes                                                                          |
| `'+N seconds'` / `'-N seconds'` | Seconds. May carry milliseconds as `'+1.500 seconds'` — exactly three digits     |
| `'+N months'` / `'-N months'`   | Months, clamping to the last valid day of the target month                       |
| `'+N years'` / `'-N years'`     | Years, clamping the same way                                                     |
| `'start of day'`                | Truncates to `00:00:00.000`                                                      |
| `'start of month'`              | Truncates to the first of the month                                              |
| `'start of year'`               | Truncates to 1 January                                                           |
| `'isostring'`                   | **Terminal.** Answers an ISO-8601 string immediately. This is the default anyway |
| `'epoch'`                       | **Terminal.** Answers the epoch in milliseconds, as a number, immediately        |

The sign is required on the arithmetic modifiers — `'1 days'` is not recognised, and being unrecognised, does nothing. A terminal modifier ends the chain, so anything after `'epoch'` is never applied.

```javascript
Date.datetime('2020-06-15T10:30:00Z', 'start of day');                 // '2020-06-15T00:00:00.000Z'
Date.datetime('2020-06-15', '+1 years', '-2 months', 'start of month');// '2021-04-01T00:00:00.000Z'
Date.datetime('2020-01-31', '+1 months');                              // '2020-02-29T00:00:00.000Z'
Date.datetime('2020-06-15', '1 days');                                 // '2020-06-15T00:00:00.000Z'
Date.datetime('2020-06-15', 'epoch', '+1 days');                       // 1592179200000
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://support.attackforge.com/app/afscript.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
