fstnetwork
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 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`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully