# Functions Runtime

Functions execute within a sandboxed Node.js environment, supporting all language features, including async/await syntax.
See details below:

<TOCInline />


## Fetch API

The Fetch function is provided as a property of the `context` object. It implements the 
standard [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) interface.

```typescript
export default async function(event, { log, fetch, store }) {
  const response = await fetch("https://example.com");
  if (response.ok) {
    const json = await response.json();
  }
}
```

## Persistent Storage

Persistent data storage between function calls is achievable using a key-value storage system, 
which is accessible through the `store` property of the `context` object.

The `store` object is defined by the following interface (in TypeScript notation):

```typescript
interface Store {
  get(key: string): Promise<any>;

  del(key: string): Promise<void>;

  // set value for 'key' with default or provided TTL (in number of seconds or string like '7d', '12h', '1m')
  set(key: string, value: any, opts?: number | string): Promise<void>;
  
  // returns value for 'key' or sets it to 'value' if key doesn't exist. TTL can be set with default or provided value (in number of seconds or string like '7d', '12h', '1m')
  getOrSet(key: string, value: any, opts?: number | string): Promise<any>;

  // returns remaining TTL for 'key' in seconds or -2 if key doesn't exist
  ttl(key: string): Promise<number>;
}
```

### Data isolation

Data in key-value storage is isolated on Workspace level.
If you want to keep similar set of keys for multiple sites or destinations isolated form each other, you need to use that entity id (source, destination, connection) as a prefix for the key.

```javascript
export default async function(event, context) {
  await store.set(`${context.destination.id}:myKey`, "myValue")
}
```

### Time to live (TTL)

Default TTL: **31 days**

It means that if you don't update data for a particular key for 31 days, that key will be deleted.

You can change TTL for a particular key by passing third argument to `set` method as number in seconds or as short [human-readable string](https://www.npmjs.com/package/parse-duration#available-unit-types-are), e.g.: `14`, `"14d"`

```javascript
// set TTL to 7 days for 'myKey'
await store.set("myKey", "myValue", "7d")
```

Disable TTL: to disable TTL and keep value forever pass `"inf"` or `-1` as third argument to `set` method:

```javascript
// disable TTL for 'myKey' to keep it forever
await store.set("myKey", "myValue", "inf")
```

Data for a particular key can be updated to refresh TTL. See [the example](/docs/functions/runtime#example).

**Check TTL**

You can check remaining TTL for specific key:

```javascript
// returns remaining TTL for 'myKey' in seconds or -2 if key doesn't exist
const remainingSec = await store.ttl("myKey")
if (remainingSec < 60 * 60 * 24) {
  // less than 1 day left. refresh TTL
  await store.set("myKey", "myValue", "7d")
}
```

### Example

"User sign up webhook" example:

```javascript
const webHookURL = "https://hooks.slack.com/services/.../.../..."

export default async function(event, { log, fetch, store }) {
  if (event.type === "identify") {
    // check if user already signed up
    if (await store.get(`signup/${event.traits.email}`)) {
      log.info(`User ${event.traits.email} already signed up`);
      // store to refresh ttl to the next 30 days
      await store.set(`signup/${event.traits.email}`, true, "30d");
    } else {
      // store signup flag for user with 30 days TTL
      await store.set(`signup/${event.traits.email}`, true, "30d");
      await fetch(webHookURL, {
        method: "POST",
        body: JSON.stringify({
          text: `Horray! We have new user ${event.traits.email}`
        })
      });
    }
  }
}
```

## Warehouse API

Warehouse API is provided as a the `getWarehouse` property of the `context` object. It allows you to query your data warehouses.

Pass destination ID or connection ID to `getWarehouse` method to get a Warehouse instance.

:::note
Only <b>ClickHouse</b> destination currently supports the Warehouse API.<br/>
HTTPS port ( <code>8443</code> by default ) should be open in your ClickHouse server.

:::

**Warehouse type:**
```typescript
interface Warehouse {
  query: (sql: string, params?: Record<string, any>) => Promise<any[]>;
}
```
where:

* `query` - method to execute SQL query. It returns an array of rows.
  * `sql` - SQL query with optional placeholders for named parameters. Placeholders should be in the form of `@paramName` or `:paramName`.
  * `params` - object with named parameters.

**Example:**
```typescript
export default async function(event, { log, getWarehouse }) {
  
  const warehouse = getWarehouse("destinationId");
  
  const row = await warehouse.query("SELECT value FROM table WHERE id = @id LIMIT 1", { id: event.properties.id });
  
  event.properties.value = row[0]?.value
  
  return event
}
```

## Logging

Logging may be helpful for debugging functions and also for monitoring.
To log something, use `log` property of the `context` object.

`log` object has the following interface (in TypeScript notation):

```typescript
interface Log {
  info(message: string, ...args: any[]);

  warn(message: string, ...args: any[]);

  debug(message: string, ...args: any[]);

  error(message: string, ...args: any[]);
}
```

**Examples** with logging: [Enrich](/docs/functions#enrich), [User sign up webhook](/docs/functions#example)

In Function editor to see logs from the last Run press **Show logs** button.

Logs of Functions that are already attached to Connections can be seen in the [Live Events](/docs/features/live-events) section of the main menu.

## Environment variables

You can use environment variables in your functions. They are accessible via `process.env` object.

```javascript
export default async function(event, { log }) {
  const apiKey = process.env.API_KEY;
  log.info("API key: " +apiKey)
  // function logic
}
```

Environment variable values are set at the Connection level, allowing a single function to operate with different configurations seamlessly.


## Crypto


Jitsu offers shortened version of node `crypto` module which supports following methods: 


[hash](https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-outputencoding), [randomUUID](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions), [randomBytes](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback), [randomInt](https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback)


```javascript

import { hash } from 'crypto';


export default async function(event, { log, geo }) {

  const h = hash("sha256", JSON.stringify(event))

  log.info("Event hash: " + h)

}

```

## Using external libraries

If you need to use any external libraries, besides core libraries, you must create a Jitsu project with [SDK](/docs/functions/sdk) and bundle your function with all dependencies.

## GEO Location


**Jitsu Cloud** provides IP geolocation information for each event. It's available in `geo` property of the `context`.

```javascript
export default async function(event, { log, geo }) {
  log.info(JSON.stringify(geo))
}
```

`geo` object structure

```json
{
  "country": {
    "code": "US",
    "isEU": false
  },
  "city": {
    "name": "New York"
  },
  "region": {
    "code": "NY"
  },
  "location": {
    "latitude": 40.6808,
    "longitude": -73.9701
  },
  "postalCode": {
    "code": "11238"
  }
}
```

## User Agent

Jitsu parses User-Agent header and provides its data. It's available in `ua` property of the `context`.:

```javascript
export default async function(event, { log, ua }) {
  console.log(JSON.stringify(ua, null, 2))
}
```

`ua` object structure

```json
{
  "browser": {
    "name": "Firefox",
    "version": "111.0",
    "major": "111"
  },
  "engine": {
    "name": "Gecko",
    "version": "109.0"
  },
  "os": {
    "name": "Mac OS",
    "version": "10.15"
  },
  "device": {
    "vendor": "Apple",
    "model": "Macintosh",
    "type": "desktop"
  },
  "cpu": {},
  "bot": false
}
```