Skip to main content
Version: LOC v0.10 (legacy)

Payload

export async function run(ctx) {
const payload = await ctx.payload();
}

Payload is a lazy-loaded object which contains metadata and payload data (if any) from a trigger as the initial input of the data process and for determining how the data process is triggered.

Currently there are two payload types:

  • HTTP request (from API route triggers)
  • Message queue (MQ) message (from MQ triggers)
note

Do not confuse logic context payloads with HTTP payloads.

Availability

  • ✓ Generic logic
  • ✓ Aggregator logic

Loading Payload

async payload(): Promise<Payload>

The returned Payload is a union type, which means it can be one of the following types at runtime:

TriggerPayload typeContains
API route{ http: HttpPayload }HttpPayload
Message queue{ messageQueue: MessagePayload }MessageQueuePayload
Event{ event: EventPayload }Event payload (not yet implemented)

The schedule triggers have no payload and only returns an empty object (see below).

Example

HTTP payload
export async function run(ctx) {
// load payload
const payload = await ctx.payload();

// read HTTP payload body
const data = payload.http.request.data;

// ...
}
Messqge queue payload
export async function run(ctx) {
// load payload
const payload = await ctx.payload();

// 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

MemberTypeDescription
apiGatewayIdentityContextIdentityContextFor_Uuid, which is { id: string, name: string }API gateway permanent ID and name
apiIdentityContextIdentityContextFor_UuidAPI route permanent ID and name
requestIdstringRequest ID
requestHttpRequestRequest content
source?Peer, which is { address: Address } | nullRequest source
destination?Peer | nullRequest destination
note

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:

MemberTypeDescriptionExample
hoststringRequest host name
pathstringAPI route/api/path
schemestringHTTP schemehttp or https
methodstringHTTP methodGET, 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" }
querystringURL query stringparam1=value1 (no question mark)
datanumber[]Request body

Example: read API route path and headers

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"];
warning

Some header fields may be null if they are not sent with the HTTP request.

Example: parsing GET querystring

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");
note

URLSearchParams is from the Web API which is supported in LOC's logic runtime.

Example: parsing POST JSON body

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;
tip

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.

warning

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

MemberTypeDescription
clientIdentityContextIdentityContextFor_Uuid (see HTTP payload)MQ client permanent ID and name
subscriberSubscriberMQ subscriber
datanumber[]MQ data

Subscriber Types

Subscriber type: KafkaSubscriber

MemberTypeDescription
brokersstring[]Broker names in the Kafka cluster
groupIdstringMQ group ID
topicstringMQ topic
partitionnumberMQ partition
offsetnumberMQ offset

Schedule Payload

Schedule triggers do not have payload. The returned payload object would simply be { event: {} }. This may be changed in future LOC releases.