owned this note
owned this note
Published
Linked with GitHub
# Agent List
:::info
This API document is designed for **JavaScript** developers using ++[LOC Studio](https://hackmd.io/R8uxDVYvQAGhJtqoTWXy6A)++ and/or ++[LOC CLI](https://hackmd.io/4LIsNIuWQnuR70JMeuBAEA?view)++.
:::
LOC provides a range of *agents* (built-in JavaScript drivers) for data processes so that you can access internal session storage data, events or external APIs, databases at run-time.
- [Generic Logic](#Generic-Logic)
- [HTTP Request and Response](#HTTP-Agent)
- [Session Storage](#Session-Storage)
- [Event Store](#Event-Store)
- [Database](#Database)
- [File Storage](#File-Storage)
- [SMTP](#SMTP)
- [Local Storage](#Local-Storage)
- [Error Logging](#Error-Logging)
- [Aggregator Logic](#Aggregator-Logic)
- [Result](#Result)
- [Session Storage](#Session-Storage)
- [Error Logging](#Error-Logging)
:::info
Be noted that most LOC agents are **asynchronous**, so it is recommended to use ```await``` to tackle them properly.
:::
## ```ctx``` and ```ctx.task```
```ctx``` is the context object injected by LOC into generic/aggregator logics at run-time.
You can access task-related information from ```ctx.task```:
| Attributes | Type |
| -------------------- | ------------------------------------------------------------- |
| ```currentLogic``` | { permanentIdentity: name; name: string; revision: number } |
| ```dataProcess``` | { permanentIdentity: name; name: string; revision: number } |
| ```executedLogics``` | { permanentIdentity: name; name: string; revision: number }[] |
| ```startAt``` | string (Date) |
| ```taskId``` | { executionId: string; id: string } |
Example:
```javascript
const taskId = ctx.task.taskId.id;
const executionId = ctx.task.taskId.executionId;
```
## Generic Logic
### Parse Input
#### ++```ctx.payload.http```++
```ctx.payload.http``` has the following attributes:
| Attributes | Type | Example |
| ------------------------------- | ---------------------------- | ----------------------------------------- |
| ```apiGatewayIdentityContext``` | { id: string; name: string } | |
| ```apiIdentityContext``` | { id: string; name: string } | |
| ```body``` | number[] | |
| ```headers``` | { [name: string]: unknown; } | ```{"content-type":"application/json"}``` |
| ```host``` | string | |
| ```method``` | string | ```"POST"``` |
| ```path``` | string | ```/hello/greeting``` |
| ```query``` | string | ```?param=data...``` |
| ```requestId``` | string | |
| ```scheme``` | string | ```https``` |
| ```version``` | string | ```"HTTP/1.1"``` |
#### Parse POST JSON Payload
You can read the request body from API routes like this:
```javascript
// a helper function to convert byte array to string
const UTF8ArrToStr = (aBytes) => {
const utf8decoder = new TextDecoder();
return utf8decoder.decode(new Uint8Array(aBytes));
}
// read request body and header
const body = JSON.parse(UTF8ArrToStr(ctx.payload.http.body));
const headers = ctx.payload.http.headers;
```
For instance, if you have this JSON input that is going to be inserted into the request body,
```json
{
// JSON body
"name": "Arthur",
"age": 42
}
```
After using the code snippets mentioned above, you can access them like this:
```javascript
// a helper function to convert byte array to string
const UTF8ArrToStr = (aBytes) => {
const utf8decoder = new TextDecoder();
return utf8decoder.decode(new Uint8Array(aBytes));
}
const body = JSON.parse(UTF8ArrToStr(ctx.payload.http.body));
let name = body?.name; // extract name from JSON body
let age = body?.age; // extract age from JSON body
```
The JSON input will be stored in a constant variable `body` after you invoke the API route with that request body. Hence, whilst querying `body.name` and `body.age`, you can expect the returns to be *"Arthur"* as a string and *42* as a number, respectively.
#### Parse GET QueryString
For example, if you send a GET request with the following QueryString:
```
https://api.fst.xxx/<api_route>?name=Arthur&age=42
```
You can extract the parameters like this:
```javascript
const query = ctx.payload.http.query;
const searchParams = new URLSearchParams(query);
let name = searchParams.get("name"); // extract name from QueryString
let age = searchParams.get("age"); // extract age from QueryString
```
### HTTP Agent
#### ++```ctx.agents.http.get(url, headers, contentType, body, config=null)```++
Send a HTTP GET request.
| Parameters | Type | Description |
| ----------------------- | ------------------------ | ------------------------------------------------------------- |
| ```url``` | string | URL |
| ```headers``` | { header1: value1; ... } | HTTP Headers |
| ```contentType``` | string | ```"None"```, ```"PlantText"```, ```"Json"``` or ```"Form"``` |
| ```body``` | Uint8Array or null | Request body (optional only for ```get()```) |
| ```config``` (optional) | object | ```{ acceptInvalidCerts: true/false }``` |
There are a series of agents corresponding to HTTP methods (the parameters are exactly the same):
| Agents | HTTP Methods |
| ------------------------------ | ------------ |
| ```ctx.agents.http.get()``` | GET |
| ```ctx.agents.http.post()``` | POST |
| ```ctx.agents.http.patch()``` | PATCH |
| ```ctx.agents.http.put()``` | PUT |
| ```ctx.agents.http.delete()``` | DELETE |
All of the HTTP agents will return a ```Http.Response``` object with the following attributes:
| Attributes | Type | Description |
| ------------- | ------------------------ | ---------------- |
| ```status``` | number | HTTP status code |
| ```headers``` | { header1: value1; ... } | Response headers |
| ```body``` | Uint8Array | Response body |
Example:
```javascript
const data = {
key1: "data1",
key2: "data2",
...
}
// send a HTTP POST request and get the response
const response = await ctx.agents.http.post(
"https://myserver/myservice", // URL
{}, // no headers needed to be set
"Json", // content type
new TextEncoder().encode(JSON.stringify(data)) // body
);
// assuming the service return JSON as well;
// this is as same as using UTF8ArrToStr in the previous section
const body = JSON.parse(new TextDecoder().decode(response.body));
if (body.status === 200) { // if success
ctx.agents.logging.info(body?.name); // extract name from JSON
// ...
}
```
With the code snippets above, you are allowed to store the data from HTTP into a constant variable `body` for future use.
See [**HTTP Agent Request Examples**](https://hackmd.io/xWdMYLD5RXiV8wt5cGVwJA) for more code examples.
### Session Storage
Read and write temporary data between logics during the same execution task of a data process.
:::info
Please note that the data kept via this way (session storage) can *only* be accessed by the same data process. The data would also be cleaned up once the data process finishes the task.
If you want to keep data between tasks, use ++[**local storage**](https://hackmd.io/Uj-tC7l9Q82VyGr8R5PL2Q?view#Local-Storage)++ instead.
If you feel like exchanging the data between other data processes, we suggest you use ++[**event emission**](#ctxagentseventStoreemitevents)++ to keep the data and ++[**event search**](#ctxagentseventStoresearchrequest)++ to query the events.
:::
#### ++```ctx.agents.sessionStorage.putJson(key, value)```++
Put JSON data in the session storage.
| Parameters | Type | Description |
| ----------- | ------ | ------------------- |
| ```key``` | string | key of session data |
| ```value``` | any | JSON object |
Example:
```javascript
const data = [
{
field1: "field1",
field2: "field2",
// ...
},
// ...
];
// store the data in session storage in JSON format
await ctx.agents.sessionStorage.putJson("mydata", data);
```
#### ++```ctx.agents.sessionStorage.putString(key, value)```++
Put a string in the session storage.
| Parameters | Type | Description |
| ----------- | ------ | ------------------- |
| ```key``` | string | key of session data |
| ```value``` | string | string data |
Example:
```javascript
const name = "Author";
const age = 42;
// store the data in session storage as string
await ctx.agents.sessionStorage.putString("name", name);
await ctx.agents.sessionStorage.putString("age", String(age));
```
#### ++```ctx.agents.sessionStorage.putByteArray(key, value)```++
Put a byte array in the session storage.
| Parameters | Type | Description |
| ----------- | ---------- | ------------------- |
| ```key``` | string | key of session data |
| ```value``` | Uint8Array | byte array |
#### ++```ctx.agents.sessionStorage.get(key)```++
Read data by key from the session storage.
| Parameter | Type | Description |
| --------- | ------ | ------------------- |
| ```key``` | string | key of session data |
Example:
```javascript
const data = await ctx.agents.sessionStorage.get("mydata");
```
```data``` will be the same type as what you put in the session storage.
#### ++```ctx.agents.sessionStorage.remove(key)```++
Remove (delete) a data from session storage.
| Parameter | Type | Description |
| --------- | ------ | ----------- |
| ```key``` | string | Data key |
### Event Store
Send and query events between data processes.
:::info
See ++[**concepts**](https://hackmd.io/mwTxDdBjSuiKrAmainQZAA#Event)++ to know more about LOC events.
Also, you CANNOT use event agents in Aggregator logics. Instead, you can only use them in generic logics.
:::
#### ++```ctx.agents.eventStore.emit(events)```++
Emit event(s) into the event store.
| Parameter | Type | Description |
| ------------ | ------------ | ----------- |
| ```events``` | object array | events |
Example:
```javascript
const events = [
// event 1
{
sourceDID: "source name xxx",
targetDID: "target name xxx",
labelName: "event name xxx",
meta: "", // data payload
type: "default", // event group
},
// event 2
{
// ...
}
];
await ctx.agents.eventStore.emit(events);
```
The maximum length of ```meta``` is 2^15^ bytes. You can convert a JSON object to a string using ```JSON.stringify()``` and store it in ```meta```:
```javascript
meta: JSON.stringify(data),
```
#### ++```ctx.agents.eventStore.search(request)```++
Search and read event(s) from the event store.
| Parameter | Type | Description |
| ------------- | ------------ | ------------------ |
| ```request``` | object array | request parameters |
Example:
```javascript
// a helper function to create a match condition
const createMatch = (field, value) => {
return { Match: { field, value } };
};
// search for events that event name equals to "event name"
const requests = {
// you can search multiple fields
queries: [
createMatch(
"label_name",
"event name xxx"
),
createMatch(
"source_digital_identity",
"Source DID xxx"
),
// ...
],
excludes: [],
filters: [],
from: 0,
size: 1000,
sorts: [],
};
const query = await ctx.agents.eventStore.search(requests);
const events = query?.events
// iterate through events
events.forEach(event => {
ctx.agents.logging.info(event.executionId);
ctx.agents.logging.info(event.label.name);
ctx.agents.logging.info(event.meta);
...
});
```
:::info
The query field name here is different with the ones you have used in ```ctx.agents.eventStore.emit()```. See ++[**Event Field Comparison Table**](https://hackmd.io/N2Km6-8kRI2tMR5X4tRHeQ)++ for reference.
:::
```events``` is an array of ```Event``` objects. Each ```Event``` has the following attributes:
| Attributes | Type | Description |
| --------------------------------- | --------------------------------------------------------------- | ----------------- |
| ```idataProcessIdentityContext``` | { name: string; permanentIdentity: string; revision: number; } | |
| ```executionId``` | string | Execution ID |
| ```label``` | { id: number; name: string } | Event ID and name |
| ```logicIdentityContext``` | { id: number; name: string } | |
| ```meta``` | string | Meta data |
| ```sequence``` | number | Sequence Number |
| ```sourceDigitalIdentity``` | string | Source DID |
| ```targetDigitalIdentity``` | string | Target DID |
| ```taskId``` | string | Task ID |
| ```timestamp``` | string | |
| ```type``` | string | Event group |
#### ++```ctx.agents.eventStore.searchWithPattern(request)```++
Search sequences of events, for which 1^st^ event has to fit 1^st^ condition, 2^nd^ event has to fit 2^nd^ condition...so on.
| Parameter | Type | Description |
| ------------- | ------------ | ------------------ |
| ```request``` | object array | request parameters |
Example:
```javascript
// a helper function for creating a search condition
const createCondition = (field, value, type) => {
const operators = ["Eq", "NotEq"];
if (!operators.includes(type)) type = "Eq"; // default operator: Eq (equal)
return { [type]: { field, value } };
}
// create sequence search pattern
const requests = {
sequences: [
// sequence 1 event
{
conditions: [
// label name equals "event name xxx"
createCondition(
"label_name",
"event name xxx",
"Eq"
),
],
sharedFields: [],
type: "any",
},
// sequence 2 event
{
conditions: [
// source DID equals "source DID xxx"
createCondition(
"source_digital_identity",
"source DID xxx",
"Eq"
),
// and target DID do not equal "target DID xxx"
createCondition(
"target_digital_identity",
"target DID xxx",
"NotEq"
),
],
sharedFields: [],
type: "any",
},
],
}
const query = await ctx.agents.eventStore.searchWithPattern(requests);
const sequences = query?.sequences;
// iterate through sequences
sequences.forEach(sequence => {
// each sequence has multiple events
sequence.events?.forEach(event => {
ctx.agents.logging.info(event.taskId);
ctx.agents.logging.info(event.label.name);
ctx.agents.logging.info(event.meta);
// ...
});
});
```
:::info
Since we are search **sequences** of events, you MUST provide **at least two sequence patterns** (each pattern contains at least one condition).
:::
The ```events``` above is an array similar to the one in the ```ctx.agents.eventStore.search()``` example.
#### Search Pattern Operators
| Operators | Description |
| ------------- | ----------------------------- |
| ```"Eq"``` | equal to (==) |
| ```"NotEq"``` | not equal to (!=) |
<!--
| ```"Gt"``` | greater than (>) |
| ```"Lt"``` | less than (<) |
| ```"Gte"``` | greater than or equal to (>=) |
| ```"Lte"``` | less than or equal to (<=) |
-->
### Database
#### ++```ctx.agents.database.connect({databaseDriver, connection=null})```++
Create a database object.
| Parameters | Type | Description |
| -------------------- | ------ | -------------------------------------------- |
| ```databaseDriver``` | string | Name of the driver. See ```DB Driver List``` |
| ```connection``` | object | DB Connection parameters |
:::spoiler MySQL Example (click to see more)
```javascript
// import database definitions
const database = Saffron.Database;
// MySQL database connection parameters
const connectionInfo = {
host: "127.0.0.1",
port: 3306,
username: "admin",
password: "xxxx",
database: "myDB",
};
// connect to database
const db = await ctx.agents.database.connect({
databaseDriver: "MySQL",
connection: new database.MySqlParameters(connectionInfo),
});
// querying db...
// disconnect when done
await db?.disconnect();
```
:::
:::spoiler MSSQL Example (click to see more)
```javascript
// import database definitions
const database = Saffron.Database;
// MySQL database connection parameters
const connectionInfo = {
host: "127.0.0.1",
port: 3306,
username: "admin",
password: "xxxx",
database: "myDB",
trustCert: true,
};
// connect to database
const db = await ctx.agents.database.connect({
databaseDriver: "MSSQL",
connection: new database.MssqlParameters(connectionInfo),
});
// querying db
let sql = `SELECT * FROM {table name}`;
let resp = await db?.query(sql, []);
await ctx.agents.sessionStorage.putJson("resp", resp);
// disconnect when done
await db?.disconnect();
```
:::
For more examples of connecting with other databases, please refer to [Local Database Testing with Simple Runtime.
](https://hackmd.io/l2p0PF5yTC2UUnRtXnp5Qw?view#Other-Supported-Databases)
#### DB Driver List (```database.connect()``` Parameters)
LOC currently provides the following built-in database drivers:
| Database | databaseDriver | connection | |
| ------------- | -------------------------------------------------- | ----------------------------------------------------- | --- |
| MySQL | ```"MySQL"``` or ```database.Driver.MySql``` | ```new database.MySqlParameters (connectionInfo)``` | |
| PostgresSQL | ```"Postgres"``` or ```database.Driver.Postgres``` | ```new database.PostgresParameters(connectionInfo)``` | |
| MS SQL Server | ```"MSSQL"``` or ```database.Driver.Mssql``` | ```new database.MssqlParameters(connectionInfo)``` | |
#### ```connectionInfo``` Parameters
| Parameter | Type | DB |
| ------------------------------- | ------- | ---------------------------------------------------------------------------- |
| ```host``` | string | All (default: ```lodalhost```) |
| ```port``` | number | All (default: MySQL - ```3306```, Postgres - ```1521```, MSSQL - ```1433```) |
| ```database``` | string | All |
| ```username``` | string | All |
| ```password``` | string | All |
| ```trustCert``` (optional) | boolean | MSSQL |
| ```options``` (optional) | string | Postgres |
| ```connectTimeout``` (optional) | number | Postgres |
| ```keepalives``` (optional) | boolean | Postgres |
| ```keepalivesIdle``` (optional) | number | Postgres |
:::info
Please be noted that ```database.connect()``` will check the ```connection``` object. If you use an connection object which contains incorrect or unsupported parameters, an ```"missing connection information"``` error will be thrown.
:::
#### ++```db.query(rawSql, params)```++
Execute a SQL query.
| Parameter | Type | Description |
| ------------ | ------ | ---------------------- |
| ```rawSql``` | string | SQL prepared statement |
| ```params``` | array | SQL query parameters |
Example:
```javascript
// query with SQL prepared statement (MSSQL)
const resp = await db.query(
"SELECT * FROM table1 WHERE col_1 = ?;",
[12345]
); // "?" will be replaced with "12345"
```
:::info
SQL prepared statement separates parameters from the SQL statement in order to stop ++[SQL injection](https://en.wikipedia.org/wiki/SQL_injection)++. Please be noted that SQL syntax in prepared statement may differ on different databases.
:::
```db.query()``` returns a ```Database.QueryResults``` objcet with the following attributes:
| Attributes | Type | Description |
| ------------- | -------------------------------- | ----------------------------------- |
| ```columns``` | { name: string; type: string }[] | Selected columns name and data type |
| ```rows``` | { key1: any; key2 : any; ... }[] | Selected rows |
#### ++```db.execute(rawSql, params)```++
Execute a SQL statement.
Example:
```javascript
// insert a new row into table with SQL prepared statement
await db.execute(
"INSERT INTO table1 (id, col_1...) VALUES (?, ?...);",
[12345, 'value 1'...]
);
```
#### ++```db.disconnect()```++
Disconnect the database.
#### ++```db.beginTransaction()```++
#### ++```db.commitTransaction()```++
#### ++```db.rollbackTransaction()```++
Execute/rollback a SQL statement in a transaction.
:::info
These transaction functions are based on ++[Transact-SQL](https://en.wikipedia.org/wiki/Transact-SQL)++ thus can only be used for MSSQL.
:::
Example:
```javascript
try {
await db.beginTransaction(); // begin transaction
// update table with SQL prepared statement
await db.execute(
"UPDATE table1 SET status = ? WHERE id = ?;",
['Accepted', 12345]
);
// ...other SQL actions
await db.commitTransaction(); // commit transcation
} catch (err) {
// rollback transaction if there are error
await db.rollbackTransaction()
}
```
### File Storage
Access a remote file via HTTP, FTP (File Transfer Protocol) or SMB (Server Message Block) protocols.
:::info
**Note:** the file storage agent *does not* invoke SMB protocol directly. Instead, you need to provide the connection info of your SMB service (including Amazon S3) to the FST Network team. These info will be configured in your Kubernetes environment. The LOC core will handle the login and serves as a file broker between data processes and the SMB server.
A S3 URL looks like this:
```
s3://<access-key-id>:<secret-access-key>@<bucket>/<key>?region=<region>&endpoint-url=<endpoint>`
```
Where ```<secret-access-key>``` should be percent-encoding.
For more information, please contact FST Network.
:::
#### ++```ctx.agents.fileStorage.simpleGet(url)```++
Read a remote file. (For HTTP it's a GET request.)
| Parameter | Type | Description |
| --------- | ------ | ---------------- |
| ```url``` | string | HTTP/FTP/SMB URL |
The returned value is a ```Uint8Array```.
Example:
```javascript
// a FTP url
const url = "ftp://myuser:mypass@<host>/<dir>/test_file.txt";
const receivedData = await ctx.agents.fileStorage.simpleGet(url);
// decode to unicode string
const data = new TextDecoder().decode(receivedData);
```
#### ++```ctx.agents.fileStorage.simplePut(url, data)```++
Write data into a remote file. (For HTTP it's a PUT request.)
| Parameters | Type | Description |
| ---------- | ---------- | ----------------------------------- |
| ```url``` | string | HTTP/FTP/SMB URL |
| ```data``` | Uint8Array | Content to be written into the file |
Example:
```javascript
const url = "ftp://myuser:mypass@<host>/<dir>/test_file.txt";
const data = "...";
// encode data to Uint8Array
const encodedData = new TextEncoder().encode(data);
await ctx.agents.fileStorage.simplePut(url, encodedData);
```
#### ++```ctx.agents.fileStorage.delete(url)```++
Delete a remote file. (For HTTP it's a DELETE request.)
| Parameter | Type | Description |
| --------- | ------ | ---------------- |
| ```url``` | string | HTTP/FTP/SMB URL |
#### ++```ctx.agents.fileStorage.list(url)```++
List all files under a directory. (Not available for HTTP file requests.)
| Parameter | Type | Description |
| --------- | ------ | ----------- |
| ```url``` | string | FTP/SMB URL |
Returns ```Array<FileStorage.FileType>```. A ```FileType``` has the following attributes:
| Attributes | Type | Description |
| ---------- | ------ | ------------------------------------------------------- |
| ```type``` | string | ```"file"```, ```"directory"``` or ```"symbolicLink"``` |
| ```file``` | string | name of the file/dir/symbolic link |
Example:
```javascript
const url = "ftp://myuser:mypass@<host>/<dir>/test_file.txt";
const fileList = await ctx.agents.fileStorage.list(url);
// iterate through all items in the list
fileList.forEach((item) => {
ctx.agents.logging.info(`${item.name}: ${item.type}`);
});
```
#### ++```ctx.agents.fileStorage.createDirAll(url)```++
Create a new directory at specified location.
| Parameter | Type | Description |
| --------- | ------ | ----------- |
| ```url``` | string | FTP/SMB URL |
### SMTP
Send a email via a SMTP server.
#### ++```ctx.agents.smtp.connect(host, username, password)```++
Connect to a SMTP server and returns a smtp object.
| Parameter | Type | Description |
| -------------- | ------ | ------------- |
| ```host``` | string | SMTP host URL |
| ```username``` | string | SMTP username |
| ```password``` | string | SMTP password |
Example:
```javascript
const host = "smtp.xxx.com";
const usermail = "user@example.com";
const password = "xxxxx";
const smtp = await ctx.agents.smtp.connect(host, usermail, password);
```
#### ++```smtp.send(mail)```++
Send a email with the SMTP object.
```javascript
const mail_body = `U get my mail???
Reply asap pls`;
// create a new mail object
let mail = new Saffron.Smtp.Mail();
// required fields
mail.setSender(usermail, "user name"); // mail sender (your email and name)
mail.setReceivers("receiver1@example.com", "receiver 1 name"); // mail receiver(s)
mail.setReceivers("receiver2@example.com", "receiver 2 name"); // you can add multiple receivers
mail.setSubject("Test mail"); // mail title
mail.setBody(mail_body); // mail body
// optional fields
mail.setReplyTo("reply@example.com", "reply name"); // reply mail address
mail.setCC("cc@example.com", "cc name"); // cc
mail.setBCC("bcc@example.com", "bcc name"); // blind cc
// send the mail
await smtp.send(mail);
```
:::info
For ```setSender()```, ```setReceivers()``` and similar methods, the second name parameter can be omitted:
```javascript
mail.setSender(usermail);
mail.setReceivers("receiver1@example.com");
// ...
```
:::
### Local Storage
Local Storage is very similar to session storage. The difference is that the data can be persistent *between* tasks of the same data process (session data will be purged after each task).
:::info
Just like session storage, a local storage data saved by one data process cannot be accessed by other data processes. The data would also be purged if you re-deploy the data process.
Local Storage **can ONLY be used in generic logics** (not available in aggregator logics).
:::
#### ++```ctx.agents.localStorage.putJson(key, value, timeout)```++
Write a JSON data into local storage.
| Parameters | Type | Description |
| ------------------------ | ------ | ----------------------------------------------------------------------------- |
| ```key``` | string | Data key |
| ```value``` | object | JSON data |
| ```timeout``` (optional) | number | Time limit (seconds) for persisting the data. Default: ```null``` (unlimited) |
Example:
```javascript
const data = {
field1: "field1",
field2: "field2",
// ...
};
await ctx.agents.localStorage.putJson("mydata", data);
```
#### ++```ctx.agents.localStorage.putString(key, value, timeout)```++
Write a string into local storage.
| Parameters | Type | Description |
| ------------------------ | ------ | ----------------------------------------------------------------------------- |
| ```key``` | string | Data key |
| ```value``` | string | String |
| ```timeout``` (optional) | number | Time limit (seconds) for persisting the data. Default: ```null``` (unlimited) |
#### ++```ctx.agents.localStorage.putByteArray(key, value, timeout)```++
Write a byte array into local storage.
| Parameters | Type | Description |
| ------------------------ | ---------- | ----------------------------------------------------------------------------- |
| ```key``` | string | Data key |
| ```value``` | Uint8Array | Byte array |
| ```timeout``` (optional) | number | Time limit (seconds) for persisting the data. Default: ```null``` (unlimited) |
#### ++```ctx.agents.localStorage.get(key)```++
Read a data from local storage.
| Parameter | Type | Description |
| --------- | ------ | ----------- |
| ```key``` | string | Data key |
Example:
```javascript
const data = await ctx.agents.localStorage.get("mydata");
```
#### ++```ctx.agents.localStorage.remove(key)```++
Remove (delete) a data from local storage.
| Parameter | Type | Description |
| --------- | ------ | ----------- |
| ```key``` | string | Data key |
### Error Logging
#### ++```ctx.agents.logging.error(value)```++
#### ++```ctx.agents.logging.info(value)```++
Log a error or general message:
| Parameter | Type | Description |
| ----------- | ---------------- | ----------- |
| ```value``` | string of object | Log message |
The message can either be a string or a JSON object.
Example:
```javascript
ctx.agents.logging.error("error message");
ctx.agents.logging.error({
errorMessage: "error message",
});
```
Be warned that ```ctx.agents.logging``` cannot parse non-JSON objects and will throw error. You can convert the object to string first:
```javascript
ctx.agents.logging.error(JSON.stringify(some_object);
```
#### Log level
Generally ```error()``` (for reporting errors) and ```info()``` (for general messages) are enough for most situations. But you can also use ```warn()``` for something that is not an error but still requires attention:
| Agent | Log level/severity |
| ------------------------------------- | ------------------ |
| ```ctx.agents.logging.error(value)``` | Highest |
| ```ctx.agents.logging.warn(value)``` | |
| ```ctx.agents.logging.info(value)``` | Lowest |
:::info
Log outputs can only be seen from the LOC Kubernetes environment or via ++[**Local Simple Runtime Execution**](https://hackmd.io/JhAMB49rS4CrpNdhHed7YA)++.
:::
## Aggregator Logic
### Result
#### ++```ctx.agents.result.finalize(value)```++
Write a customised result of the data process task, and the API route will aggregate the task results of the execution this time.
| Parameter | Type | Description |
| ----------- | ------ | --------------------- |
| ```value``` | object | string or JSON object |
Example:
```javascript
ctx.agents.result.finalize({
status: "ok", // this is NOT the HTTP status code but a custom field
taskId: ctx.task.taskId,
message: "xxx",
// ...
});
```
The result would be included as part of the actual API response:
```json
{
"_status":200,
"_metadata":{
"executionId":"Ynth0FrP6mbq21bfUahtLg",
"status":"success",
"expiration":"2022-05-14T07:12:16.729755958Z"
},
"data":{ // the results outputed by finalize()
"status":"ok",
"taskId":{
"executionId":"Ynth0FrP6mbq21bfUahtLg",
"id":"_3xVQoFQ7l5dCW03vbPZOw"
},
"message": "xxx",
// ...
}
}
```
:::info
The data process would always return ```"_status":200``` even though there are errors occurred.
If you see ```"_status":202```, it may be that the data process/API route are just deployed and are not ready yet. If you see this repeatly, it may be something went wrong in the LOC itself so the data process can never finish its task.
:::
### Session Storage
See: [Session Storage](#Session-Storage)
### Error Logging
See: [Error Logging](#Error-Logging)
---
###### tags: `LOC Studio` `LOC CLI`