Payload
export async function run(ctx) {
const payload = await ctx.payload();
}
Payload is the data and metadata of a trigger as initial input and is accessible from the context object. Currently there are several payload types:
- HTTP request
- Message queue (MQ) message
- Data event (not yet implemented)
Do not confuse logic context payloads with HTTP payloads.
Availability
- ✓ Generic logic
- ✓ Aggregator logic
Loading Payload
async payload(): Promise<Payload>
The payload would be lazy-loaded only after calling payload()
in a logic.
The returned Payload
is a union type, which means it can be one of the following types at runtime:
Payload type | Contains |
---|---|
{ http: HttpPayload; } | HttpPayload |
{ messageQueue: MessagePayload; } | MessageQueuePayload |
{ event: EventPayload; } | EventPayload |
Example
- JavaScript
- TypeScript
export async function run(ctx) {
// load payload
const payload = await ctx.payload();
// read HTTP payload body
const data = payload.http.request.data;
// ...
}
export async function run(ctx) {
// load payload
const payload = await ctx.payload();
// read message queue body
const data = payload.messageQueue.data;
// ...
}
import { ..., HttpPayload } from "@fstnetwork/loc-logic-sdk";
export async function run(ctx) {
// load payload as HTTP payload
const payload = await ctx.payload() as { "http": HttpPayload };
// read HTTP payload body
const data = payload.http.request.data;
// ...
}
import { ..., MessagePayload } from "@fstnetwork/loc-logic-sdk";
export async function run(ctx) {
// load payload as MQ payload
const payload = await ctx.payload() as { "messageQueue": MessagePayload };
// read message queue body
const data = payload.messageQueue.data;
// ...
}
Using Type Guards or JSDoc Annotations for Safer Payload Typing
TypeScript Type Guards (Type Narrowing)
What we did above in the TypeScript example is the so-called type assertion, which means you take the responsibility of a type and tell the TypeScript compiler to shut up. This is handy when you are absolutely sure what kind of payload would be received in one logic.
However, type assertions cannot prevent payload
getting a wrong type of object at runtime and may cause errors. While this is handy for the sake of demostration, you should use it at your own risk.
A much better way is to use a type guard to narrow down the union type:
const payload = await ctx.payload();
let data: number[];
if ("http" in payload) {
// check if it's HTTP payload
// payload will be narrowed down to { http: HttpPayload; } here
data = payload.http.request.data;
} else if ("messageQueue" in payload) {
// check if it's MQ payload
// payload will be narrowed down to { messageQueue: MessagePayload; } here
data = payload.messageQueue.data;
} else {
// if none of above, throw an error
// payload will be narrowed down to { event: EventPayload; } here
// however since it is not yet implemented, we throw an error
// to stop the data process:
throw new Error("this logic only accepts http/mq payload");
}
// data will have the payload content
The if...else
(or using switch
for the same effect) is the so-called type guards. They check on conditions that only certain types can satisfy, for example, if the object has a specific field that only one certain type would have. TypeScript compiler would thus be able to "narrow" down the type and allows us to operate it saftly.
JSDoc Annotations
If you are developing JavaScript data processes in editors with intellisense feature like VS Code, you can also use JSDoc annotations like @param
or @type
to indicate types:
import { LoggingAgent, HttpPayload } from "@fstnetwork/loc-logic-sdk";
/** @param {import('@fstnetwork/loc-logic-sdk').GenericContext} ctx */
export async function run(ctx) {
// mark ctx as GenericContext type
/** @type { { http: HttpPayload } } */
const payload = ctx.payload(); // marked payload as { http: HttpPayload } type
const data = payload.http.request.data; // inferred as string
}
/**
* @param {import('@fstnetwork/loc-logic-sdk').GenericContext} ctx
* @param {import('@fstnetwork/loc-logic-sdk').RailwayError} error
*/
export async function handleError(ctx, error) {
LoggingAgent.error(error.message);
}
Move your mouse over payload
and you should see VS Code does mark it as { http: HttpPayload }
type.
Of you can add a check to filter out non-HTTP payloads:
/** @param {import('@fstnetwork/loc-logic-sdk').GenericContext} ctx */
export async function run(ctx) {
const payload = ctx.payload();
if (!("http" in payload)) throw new Error("this logic only accepts http payload");
// payload are inferred as { http: HttpPayload } since other types won't exist
const data = payload.http.request.data; // inferred as string
}
...
Again, this doesn't affect how the code runs or prevent potential errors, but it will be useful to discover type errors early in the development process.
HTTP Payload
Type: HttpPayload
Member | Type | Description |
---|---|---|
apiGatewayIdentityContext | IdentityContextFor_Uuid , which is { id: string, name: string } | API gateway permanent ID and name |
apiIdentityContext | IdentityContextFor_Uuid | API route permanent ID and name |
requestId | string | Request ID |
request | HttpRequest | Request content |
source? | Peer , which is { address: Address; } | null | Request source |
destination? | Peer | null | Request destination |
Type Address
:
= { socketAddr: SocketAddress; } | { pipe: Pipe; }
= { socketAddr: { address: string; protocol: "tcp" | "udp"; }} | { pipe: { mode: number; path: string; }}
HTTP Request
Type: HttpRequest
This type contains content of the actual HTTP request:
Member | Type | Description | Example |
---|---|---|---|
host | string | Request host name | |
path | string | API route | /api/path |
scheme | string | HTTP scheme | http or https |
method | string | HTTP method | GET , POST , etc. |
version | "HTTP/0.9" | "HTTP/1.0" | "HTTP/1.1" | "HTTP/2.0" | "HTTP/3.0" | HTTP version | |
headers | { [k: string]: unknown; } | Request headers | { "content-type": "application/json" } |
query | string | URL query string | param1=value1 (no question mark) |
data | number[] | Request body |
Example: read API route path and headers
- JavaScript
- TypeScript
const payload = await ctx.payload();
const path = payload.http.request.path;
const method = payload.http.request.method;
const headers = payload.http.request.headers;
// read specific headers
const contentType = headers["content-type"];
const authorization = headers["authorization"];
const payload = (await ctx.payload()) as { http: HttpPayload };
const path = payload.http.request.path;
const headers = payload.http.request.headers;
// read specific headers
const contentType = headers["content-type"];
const authorization = headers["authorization"];
Some header fields may be null
if they are not sent with the HTTP request.
Example: parsing GET querystring
- JavaScript
- TypeScript
const payload = await ctx.payload();
const query = payload.http.request.query;
const searchParams = new URLSearchParams(query);
// assume the querystring is ?name=<name>&age=<age>
// access parsed fields (return null if not exist)
const name = searchParams.get("name");
const age = searchParams.get("age");
const payload = (await ctx.payload()) as { http: HttpPayload };
const query = payload.http.request.query;
const searchParams = new URLSearchParams(query);
// assume the querystring is ?name=<name>&age=<age>
// access parsed fields (return null if not exist)
const name = searchParams.get("name") as string;
const age = searchParams.get("age") as number;
URLSearchParams
is from the Web API which is supported in LOC's logic runtime.
Example: parsing POST JSON body
- JavaScript
- TypeScript
const payload = await ctx.payload();
const data = payload.http.request.data;
// decode to string then parse to JSON
const parsed = JSON.parse(new TextDecoder().decode(new Uint8Array(data)));
// assume the payload is { "name": <name>, "age": <age> }
// access parsed fields using optional chaining (return undefined if not exist)
const name = parsed?.name;
const age = parsed?.age;
const payload = (await ctx.payload()) as { http: HttpPayload };
const data = payload.http.request.data;
// decode to string then parse to JSON
const parsed: { name: string; age: number } = JSON.parse(
new TextDecoder().decode(new Uint8Array(data)),
);
// assume the payload is { "name": <name>, "age": <age> }
// access parsed fields using optional chaining (return undefined if not exist)
const name = parsed?.name;
const age = parsed?.age;
The question mark (?
) we've used with payload
is the optional chaining operator. It prevents JavaScript throwing error if we try to access a non-existant attribute or sub-attribute in a object, which may very well happen if the JSON payload is incorrect in the first place.
TextDecoder().decode
will throw an error if payload data is empty.
JSON.parse
will throw an error if the payload is not valid JSON.
Message Queue Payload
Type: MessageQueuePayload
Member | Type | Description |
---|---|---|
clientIdentityContext | IdentityContextFor_Uuid (see HTTP payload) | MQ client permanent ID and name |
subscriber | Subscriber | MQ subscriber |
data | number[] | MQ data |
Subscriber Types
- Apache Kafka
Subscriber type: KafkaSubscriber
Member | Type | Description |
---|---|---|
brokers | string[] | Broker names in the Kafka cluster |
groupId | string | MQ group ID |
topic | string | MQ topic |
partition | number | MQ partition |
offset | number | MQ offset |