Build
Serverless functions
Author and run backend logic using the documented function runtime.
@gsb-core/mcp-docs:getServerlessFunctionDocs This document provides a comprehensive guide to building backend serverless functions on the Pikbase platform. It covers the fundamental concepts, available services, best practices, and examples to help developers and AI build robust functions.
1. Introduction to GSB Serverless Functions
GSB Serverless Functions are JavaScript code snippets that run on the GSB backend in response to various events or triggers. They allow developers to extend and customize the platform's functionality without managing server infrastructure.
Functions are typically defined in a JSON structure, which includes:
id: A unique identifier for the function.name: A human-readable name for the function.code: The JavaScript code for the function.references: An array of objects containing library/service definition IDs that the function depends on. Each object has the format{"id": "uuid-of-the-service"}. This tells the runtime which services (likeGsbEntityService,GsbUtil, etc.) to make available to the function's scope.operations(optional): A JSON string defining a sequence of declarative operations that can be part_of the function's execution. These operations can include setting entity properties, running scripts, sending notifications, etc.
Execution Environment
When a GSB serverless function executes, it has access to a specific environment and a set of global objects:
_runtime: An object providing methods to interact with the function's execution context (e.g., ending the function, logging, accessing tenant information)._instance: An object containing instance-specific data relevant to the current function execution, such as the triggering entity or parameters passed to the function._defs: An object that provides access to GSB entity definitions. This allows you to strongly-type entity objects (e.g.,let order = new _defs.GsbPrtOrder();)._enums: An object providing access to various enumerations defined in GSB (e.g.,_enums.OrderStatus.Cancelled).- Service Instances: Services referenced in the function's
referencesarray are available as pre-instantiated objects or classes that you can instantiate (e.g.,GsbEntityService,Utils).
The runtime also injects these values directly into function and library scope. Use them as globals; do not call require() or add package imports for them:
- Execution context:
_runtime,runtime,_instance,instance,tenantCode,_tenantCode. - Platform data:
Big,_defs,_enums,_cache,_settings. - Templates and utilities:
nunjucks,nunjucks_date_filter,nunjucks_comma_filter,axios,xlsx,cheerio,fetch,FormData,multer. - Messaging and storage:
nodemailer,aws_s3,aws_ses,aws_translate,aws_sqs,azure_storage_blob,azure_storage_queue,bullmq,ioredis.
For example, create an SMTP transporter with nodemailer.createTransport(...), not require("nodemailer").
All I/O operations, especially calls to GSB services, are asynchronous. Therefore, functions heavily rely on JavaScript's async/await syntax and Promises.
2. Core Concepts
_runtime Object
The _runtime object is crucial for controlling the function's lifecycle and interacting with the GSB environment. A function can finish through any of these methods:
_runtime.end(code = 200, message = "", result = "", response = undefined, action = undefined)-code: The HTTP response status. This is part of the caller-facing transport contract, not an application-specific error code. -message: An execution message. -result: The workflow result used to select a conditional route. -response: The payload returned to the caller.action: A process-action name such as"BreakActivity"that controls what the workflow engine does next.
_runtime.success(result = "", response = undefined, action = undefined): Ends successfully with HTTP 200.resultstill controls workflow routing; it is not a display message._runtime.error(exception = undefined, message = undefined, userMessage = undefined, action = undefined, code = undefined): Ends with an error. Pass the caught exception separately from the internalmessage, a safe caller-facinguserMessage, the workflowaction, and the HTTP statuscode.
Runtime termination does not make JavaScript control flow unreachable. Always return immediately: return _runtime.end(...), return _runtime.success(...), or return _runtime.error(...). This prevents writes, external calls, or a second termination attempt after the runtime has ended.
Standard response boundary
New and migrated serverless functions should reference serverlessResponseService and create one ServerlessResponseService per execution. It preserves every _runtime.end dimension through named options, validates HTTP statuses, gives expected failures stable application codes, and emits one error envelope:
{ success: false, error: { code, message, issues, details? } }
- Use
return responseService.respond({ httpStatus, message, result, response, action })for a completed outcome. OmittedhttpStatusis 200. - Use
responseService.fail({ code, httpStatus, message, userMessage?, issues?, details?, result?, action? })for an expected domain failure. It throws a typed boundary error; default workflow failure control isresult: "Error"andaction: "BreakFunction". - Use
responseService.validation({ issues, ... })for request validation. Its default code isINVALID_REQUESTand default status is 400. - Catch once at the outer execution boundary and
return responseService.sendError(error, fallback?). Unknown exceptions become the sanitized fallback 500 response. - Do not call
_runtime.errorin migrated functions. It ends with 500 unless its final positional code is explicitly supplied, and positional arguments are easy to misapply.
Example:
const responseService = new ServerlessResponseService(_runtime);
async function runAll() {
try {
if (!order) {
responseService.fail({
code: "ORDER_NOT_FOUND",
httpStatus: 404,
message: "The order was not found.",
});
}
return responseService.respond({
result: "approved",
response: { orderId: order.id },
});
} catch (error) {
return responseService.sendError(error);
}
}
runAll().then();
Pass action as its canonical text name, for example "BreakActivity" or "CancelWorkflow". The runtime also accepts _enums.ProcessAction.BreakActivity and legacy numeric values, but text is clearer in authored code and serialized requests. IntelliSense exposes all supported names and numeric compatibility values through RuntimeProcessAction.
Use proper HTTP statuses. Typical choices are 200 for success, 400 for malformed input, 401 for missing authentication, 403 for denied authorization, 404 for a missing resource, 409 for a state conflict, 422 for a semantically invalid request, 429 for rate limiting, and 500, 502, or 503 for server or dependency failures. Do not return HTTP 200 for an error.
| Process-action name | Engine behavior |
|---|---|
Continue |
Continue normal processing |
BreakOperation |
Stop the current operation |
BreakFunction |
Stop the current function |
BreakActivity |
Stop the current activity |
BreakWorkflow |
Stop the current workflow |
ReRunOperation |
Run the current operation again |
ReRunFunction |
Run the current function again |
ReRunActivity |
Run the current activity again |
RestartWorkflow |
Restart the workflow |
CancelWorkflow |
Cancel the workflow |
_runtime.log(message, operation, exception, type): Logs a message to the GSB logging system. (Often superseded byGsbLogService)._runtime.route(routeName): Used in workflow functions to direct the workflow to a specific route._runtime.token: Accesses the current user's authentication token, useful for making API calls._runtime.tenantCode: Accesses the current tenant's code._runtime.apiUrl: Base API URL for the current execution._runtime.userToken: Current user token when one is available._runtime.instance: The current function instance._runtime.transactionId: Current execution transaction ID._runtime.hostName/_runtime.protocol: API host and protocol for the current execution._runtime.settings: The same module settings exposed byinstance?.moduleSettings._runtime.variation: The same variation exposed byinstance?.variation.
The runtime constructs this context before executing authored code: apiUrl, token,
userToken, instance, transactionId, hostName, protocol, settings, and
variation are therefore execution-owned values. Do not replace them with caller-supplied
parameters or persist tokens from them in entities, logs, or background-job payloads.
Operational capability notes
These paths have different contracts and should not be treated as interchangeable:
- The SMTP-backed
sendEmailfunction usesSmtpServiceand the injectednodemailerglobal. After the EU1 backend upgrade, plain email and an in-memory PDF attachment were both delivered successfully. The attachment arrived with MIME typeapplication/pdfand dispositionattachment. - Declarative notification operation type
13successfully sends ordinary email. A testedGsbFilePDF attachment was omitted, so this path must not be used when attachment delivery is required until its mapping is repaired and revalidated. - The custom SES library is an alternate provider path, but it requires tenant-owned
_settings.aws.sesconfiguration. A missing setting is a configuration failure, not a reason to embed provider credentials in function source. - Task scheduling uses the runtime-provided
bullmqandioredismodules. The currentgroupOperationsmodel schedules the SMTP-backedsendEmailfunction by stable function ID. Re-run schedule, worker, status, retry, cancellation, tenant-isolation, and cleanup tests after backend changes before declaring the complete task path healthy. - Redis connection details and credentials must move out of
TaskSchedulerServicesource into server-owned configuration. Rotate the existing credential during that migration, and do not serialize runtime or user tokens into queue jobs. - PDF document generation currently fails when launching Chromium with
spawn /usr/bin/chromium ENOENT. The backend image or renderer configuration must provide a valid executable path before operation type12document generation can be considered available. SMTP attachment delivery working does not prove PDF rendering works.
_instance Object
The _instance object provides data specific to the current invocation of the serverless function.
_instance.entity: Often represents the primary GSB entity that the function is operating on. Its type can be cast using_defs(e.g.,let order = _instance.entity as _defs.GsbPrtOrder;)._instance.entity_id: The ID of the primary entity._instance.prms: An object to store or pass parameters within a function's execution, especially useful in workflows with multiple steps or when setting up sub-flow instances. For example,_instance.prms.dynamicAssignRole = 'role_id';or_instance.prms.subFlowInstances = [];._instance.response: Can be used to build up a response object during the function's execution._instance.result: Can hold a result message or status._instance.parentResult: In sub-flows, this can be used to set a result for the parent flow.
_defs (Definitions)
The _defs object acts as a namespace for all GSB entity type definitions. This is extremely useful for:
- Strongly typing variables:
let newInvoice = new _defs.GsbPrtInvoice(); - IntelliSense and code completion in supporting IDEs.
- Clarity and maintainability of code.
Example:
let customerOrder = _instance.entity as _defs.GsbPrtOrder;
let newProduct = new _defs.GsbInvProduct({ title: "New Product" });
_enums (Enumerations)
The _enums object provides access to predefined sets of constants (enumerations) used throughout GSB. This helps avoid using "magic strings" or numbers and improves code readability.
Example:
upOrder.status = _enums.OrderStatus.Completed;
if (order.type == _enums.OrderType.Sales) { ... }
payment.paymentOption.paymentType == _enums.PaymentType.BankTransfer
Common _enums include:
OrderStatusInvoiceStatusPaymentStatusOrderQuantityStatusQuerySortTypeQueryFunction(for query conditions)QueryRelation(AND/OR for query conditions)ProcessAction(for workflow control)DataPassTypeOrderSubProcessTypeTriggerGroup
Asynchronous Operations
Nearly all interactions with services (EntityService, ApiService, etc.) are asynchronous and return Promises. Always use async for functions that contain such calls and await to get their results.
async function myAsyncFunction() {
try {
let entityService = new GsbEntityService(_runtime);
let order = await entityService.getById(_defs.GsbPrtOrder, 'some-order-id');
// ... process order ...
} catch (error) {
return _runtime.error(error, "Order lookup failed.", "The order could not be loaded.", undefined, 500);
}
}
3. Available Services (from Code Library)
GSB provides a set of built-in services, defined in the "Code Library," that functions can use by referencing their IDs.
When a code library is referenced in a function, the code library is automatically added to the function's scope.
All you need to do is to reference the code library in the function's references array and instantiate it.
For example if you have entity service in your code library, you can directly instantiate it like this:
let entityService = new GsbEntityService(_runtime);
GsbApiService
Service for making general API calls, either to internal GSB endpoints or external HTTP/HTTPS services.
Definition ID: 57bfbcdd-95a2-4d0f-9efb-67433c5b83fa
Instantiation:
let apiService = new GsbApiService(_runtime);
Key Methods:
callApi(req, endPoint, tenantCode = undefined, token = this.runtime.token): Promise- Calls a GSB API endpoint.
req: The request object.endPoint: The API endpoint path (e.g.,/api/entity/queryJson).tenantCode(optional): Tenant code.token(optional): Bearer token. Defaults to_runtime.token.
// Example: // let response = await apiService.callApi({ someData: 'value' }, '/api/custom/endpoint');httpCall(dto: HttpCallRequest, callback, errorCallback): (Legacy) Calls an HTTP/HTTPS API with callbacks. PreferhttpCallAsync.httpCallAsync(dto: HttpCallRequest): Promise: Calls an HTTP/HTTPS API asynchronously.dto: AnHttpCallRequestobject.
// Example: // let request = new HttpCallRequest(); // request.method = "GET"; // request.protocol = "https"; // request.hostName = "api.example.com"; // request.path = "/data"; // request.bearerToken = "some_token"; // let externalData = await apiService.httpCallAsync(request);convertToRequestData(requestParameters: Object): string: Converts an object to a request data string (likely query string).
HttpCallRequest Class
Used with httpCallAsync and httpCall.
class HttpCallRequest {
method = "POST";
protocol = "https";
hostName;
port = "443";
path;
content; // Body of the request
bearerToken;
contentType = "application/json";
headers; // Object for additional headers
jsonResponse = true;
skipCheckErrorStatus = false;
}
GsbEntityService
The primary service for interacting with GSB entities (CRUD operations, queries, etc.).
Definition ID: 99e4c845-3032-458f-996f-8db3302f4e38
Instantiation:
let entityService = new GsbEntityService(_runtime);
The constructor constructor(_runtime) also initializes this.apiService = new GsbApiService(_runtime); internally.
Key Methods:
query(req: EntityQueryParams, tenantCode?, token?): Promise<GsbQueryResponse>: Queries entities.req: AnEntityQueryParamsobject defining the query.
delete(req: EntityQueryParams, tenantCode?, token?): Promise<GsbDeleteResponse>: Deletes entities matching the query.queryMapped(req: EntityQueryParams, tenantCode?, token?): Promise<GsbQueryResponse>: Queries entities with mapping.get(req: EntityQueryParams, tenantCode?, token?): Promise<GsbGetResponse>: Retrieves a single entity based on query parameters (expects one result).save(req: GsbSaveRequest, tenantCode?, token?): Promise<GsbSaveResponse>: Saves a single entity.req: AGsbSaveRequestobject, which includesentDefNameandentitydata.
// Example: Save // let newCustomer = new _defs.GsbPartyCustomer({ name: "New Co" }); // let saveReq = new GsbSaveRequest(); // saveReq.entDefName = "GsbPartyCustomer"; // saveReq.entity = newCustomer; // let saveResponse = await entityService.save(saveReq); // let newCustomerId = saveResponse.id;saveEnt(entity: any, tenantCode?, token?): Promise<GsbSaveResponse>: A more direct way to save an entity. The entity object should be an instance of a_defsclass. The service infersentDefName.// Example: saveEnt // let productToUpdate = new _defs.GsbInvProduct({ id: 'existing-id', price: 29.99 }); // await entityService.saveEnt(productToUpdate);updateQuery(req: EntityQueryParams, tenantCode?, token?): Promise<GsbSaveResponse>(Note: Response type might beGsbQueryOpResponsebased on library, oftenGsbSaveResponseis used butaffectedRowCountis the key)- Updates entities matching a query. The
reqobject'sentityproperty should contain the fields to update.
// Example: Update entities matching a query // let whQuantitesUpdateReq = new EntityQueryParams(_defs.GsbPrtOrderQuantity); // whQuantitesUpdateReq.filter("order_id", entity.id); // // The 'entity' property of EntityQueryParams holds the update payload // whQuantitesUpdateReq.entity = { status: _enums.OrderQuantityStatus.Cancelled } as _defs.GsbPrtOrderQuantity; // await entityService.updateQuery(whQuantitesUpdateReq);- Updates entities matching a query. The
saveMulti(req: GsbSaveMultiRequest, tenantCode?, token?): Promise<GsbSaveMultiResponse>: Saves multiple entities of the same type.req: AGsbSaveMultiRequestobject withentDefNameand anentitiesarray.
getCode(req: GsbGetCodeRequest, tenantCode?, token?): Promise<GsbGetCodeResponse>: Generates a unique code (e.g., order number).getById<T>(definitionType: (new () => T) | string, id: string): Promise<T | null>: Retrieves an entity by its ID.definitionType: The entity class from_defs(e.g.,_defs.GsbPrtOrder) or its name as a string.id: The entity's ID.
// Example: getById // let order = await entityService.getById(_defs.GsbPrtOrder, 'order-uuid'); // if (order) { /* ... */ }runWorkflow(req, tenantCode?, token?): Promise<any>: Executes a workflow.startWorkflow(req, tenantCode?, token?): Promise<any>: Starts a workflow.runWfFunction(req, tenantCode?, token?): Promise<any>: Runs a workflow function.iterateTask(req, tenantCode?, token?): Promise<any>: Iterates a task in a workflow.getCopy<T>(definitionType: (new () => T) | string, id: string): Promise<T | null>: Retrieves a copy of an entity.
Entity Service Data Models (Requests/Responses)
These classes are typically used as parameters or return types for GsbEntityService methods.
They generally extend GsbResponseBase { message?: string; status?: string; }.
GsbGetCodeRequest { codeGeneratorId?: string; }GsbGetCodeResponse extends GsbResponseBase { code?: string; }GsbDeleteResponse extends GsbResponseBase { deleteCount: number; }GsbSaveRequest { entDefName?: string; entDefId?: string; entityDef?: any; entity?: any; query?: any[]; }GsbSaveResponse extends GsbResponseBase { id?: string; }GsbSaveMultiRequest { entDefName?: string; entDefId?: string; entityDef?: any; entities?: any[]; }GsbSaveMultiResponse extends GsbResponseBase { ids?: string[]; }GsbQueryResponse extends GsbResponseBase { entities?: any[]; }GsbGetResponse extends GsbResponseBase { entity?: any; }GsbQueryOpResponse extends GsbResponseBase { affectedRowCount?: number; }(for update/delete operations)GsbDefinitionResponse extends GsbResponseBase { entityDef?: any; }GsbSaveMappedRequest { entDefName: string; entDefId?: string; entityDef?: any; items?: any; entityId?: string; propName?: string; }
GsbLogService
Provides methods for structured logging.
Definition ID: ca071e22-184e-443b-b7e5-05ad29a1dc13
Instantiation:
let logService = new GsbLogService(_runtime);
Key Methods:
Each method returns a Promise<any>.
log(msg, operation, exception, type): Promise<any>: Generic log method.logError(msg, operation?, exception?): Promise<any>logInfo(msg, operation?, exception?): Promise<any>logWarning(msg, operation?, exception?): Promise<any>logCritical(msg, operation?, exception?): Promise<any>
// Example:
// await logService.logInfo("User logged in", "UserLogin", { userId: user.id });
// try {
// // ... some operation ...
// } catch (e) {
// await logService.logError("Failed to process order", "OrderProcessing", e);
// return _runtime.error(e, "Order processing failed.", "The order could not be processed.", undefined, 500);
// }
GsbUtil (available as Utils)
A collection of utility functions. It's often available directly as a pre-instantiated Utils object if referenced.
Definition ID: 085d21cb-349f-4a99-b5dc-679c8ee7947e
The library defines const Utils = new GsbUtil();, so you can use Utils directly.
Key Methods:
getSettings(): any: Gets system settings.translate(text, langCode): string: Translates text.getMlDictionary(): []: Gets the multilingual dictionary.equalsCaseInsensitive(v1: string, v2: string): boolean: Case-insensitive string comparison.idEquals(v1: string, v2: string): boolean: Compares two GSB IDs (handles nulls/empties gracefully).isIdEmpty(id): boolean: Checks if a GSB ID is null, undefined, or an empty string.// if (!Utils.isIdEmpty(entity.invoice_id)) { /* ... */ }removeNestedObjects(obj, exceptionProps = []): any: Removes nested objects, useful for simplifying objects before saving.newId(): string: Generates a new GSB-compatible unique ID (GUID).// let newItem = new _defs.MyEntity({ id: Utils.newId(), name: "Test" });checkPropId(entity, propName, createIfNotExists = false): Checks if a linked entity property (e.g.,entity.customer_idandentity.customer) has an ID, optionally creating one.sortByOrderNum(arr: any[], prop = undefined): any[]: Sorts an array of objects by anorderNumberproperty (or a custom property).deepCopy(source): any: Creates a deep copy of an object.isAdmin(): boolean: Checks if the current user is an admin.entityHasMoreThanId(entity:any) : boolean: Checks if an entity object has properties other than just itsid.
4. Building Queries with QueryParams
The QueryParams (and its subclass EntityQueryParams) classes are fundamental for fetching data from GSB.
Definition ID (QueryParams): 1c9bc14d-2151-4e14-b6c8-63c0421c1243
Instantiation:
// For a specific entity type:
let eqp = new EntityQueryParams(_defs.GsbPrtOrder);
// or by entity definition name string
let eqpByName = new EntityQueryParams("GsbPrtOrder");
// For includes (nested queries):
let includeQuery = new IncludeQuery("items"); // "items" is the property name
Key Features & Methods:
Defining the Entity:
- The constructor takes the entity definition (
_defs.YourEntityor"YourEntityName"). entDefName: String name of the entity definition.entDefId: ID of the entity definition.
- The constructor takes the entity definition (
Filtering (
filter):filter(propName, value, queryFunction?, relation?)adds aFiltertofiltersand returns the query.- Property names may be strings (including dotted paths) or typed expressions such as
(item) => item.customer.name. - Use
QueryFunctionfor comparisons andQueryRelation.And/QueryRelation.Orto combine filters.eqp .filter("status", OrderStatus.Pending, QueryFunction.Equals) .filter("customer.name", "John%", QueryFunction.ILike) .filter("totalAmount", 100, QueryFunction.Greater); - The serialized field is
filters, where each item usescol: { name },val: { value }, andfunction.
Selecting Columns (
select):select(col: (string | string[] | ((item: T) => any)), options?: SelectCol): QueryParams<T>: Specifies which properties to retrieve.col: Can be a property name string, an array of property names, or a lambda function for typed selection.options: ASelectColobject for advanced options (aliasing, aggregation - though aggregation is less common in basic serverless functions).
eqp.select(["id", "orderNumber", "customer.name"]);selectCols?: SelectCol[]: Array ofSelectColobjects.SelectCol { name, aggregateFunction?, dateModifier?, script?, groupBy?, selectAsTitle? }
Including Related Entities (
include):include(...colNames): Includes related entities and returns the root query with.incand.selfhelpers.colNames: Property names of navigation properties..incis the latestIncludeQuery;.selfresumes the root query.
// Example: Include order items and the product for each item const orderQuery = new EntityQueryParams("GsbPrtOrder") .filter("id", "some-order-id") .include(new IncludeQuery("items").select(["id", "quantity"])) .self.select(["id", "orderNumber"]); // let result = await entityService.query(orderQuery);includes?: IncludeQuery[]: Array ofIncludeQueryobjects.
Sorting (
sortBy):sortBy(colName: (string | ((item: T) => any)), sortType: _enums.QuerySortType): QueryParams<T>colName: Property name or lambda.sortType:_enums.QuerySortType.Ascor_enums.QuerySortType.Desc.
eqp.sortBy("createdDate", QuerySortType.Descending);sortCols?: SortCol[]: Array ofSortColobjects.SortCol { col: SelectCol, sortType: _enums.QuerySortType }
Pagination:
skip(startIndex)sets the zero-based offset.take(count)/limit(count)bounds the returned records.returnCount()requests the total matching count.
Fluent Interface:
type,filter,search,select,include,sortBy,skip,take,limit,pickEntity,returnCount, andsetNamereturn the query.- CLI and MCP callers send the serialized
filters,selectCols,includes, andsortColsfields rather than method calls.
Example of EntityQueryParams for an update operation:
As seen in Function (1).json (e.g., "Cancel Order" function):
// To update entities, you set the 'entity' property on the EntityQueryParams object
// with the new values. The query part defines WHICH entities to update.
let whQuantitesUpdateReq = new EntityQueryParams(_defs.GsbPrtOrderQuantity);
whQuantitesUpdateReq.filter("order_id", _instance.entity_id);
// This is the payload for the update:
let updatePayload = { status: _enums.OrderQuantityStatus.Cancelled } as _defs.GsbPrtOrderQuantity;
whQuantitesUpdateReq.entity = updatePayload; // GsbEntityService.updateQuery uses this
// await entityService.updateQuery(whQuantitesUpdateReq);
5. Working with Operations
In the Function (1).json structure, some functions have an operations field. This field contains a JSON string representing an array of operation objects. Each operation defines a step in a process.
// Snippet from Function (1).json
{
// ... other function properties ...
"operations": "[{\"id\":\"GUID\",\"orderNumber\":1,\"operationType\":10, ... }, ...]",
// ...
}
operationType: Indicates the type of operation (e.g., 7 for "Script", 10 for "Set Entity Properties", 9 for "Get Entity", 13 for "Notification").scriptCode: IfoperationTypeis for a script, this field contains the JavaScript code (often base64 encoded or escaped). This script runs within the same GSB function context.setEntityOptions: For operations that modify entities, this defines which properties to set.getEntityOptions: For operations that fetch entities, this defines query parameters.notification: Defines notification settings if the operation sends one.
When a function includes an operations definition, the GSB platform likely processes these operations sequentially. If an operation is a script, that script is executed. These scripts can use all the same _runtime, _instance, services, etc., as a primary function defined in the main code property.
This allows for a mix of declarative (JSON-defined operations) and imperative (JavaScript) logic within a single GSB function definition.
6. Function Structure and Examples
Based on Function (1).json, a common pattern for GSB serverless functions is:
// 1. Instantiate necessary services (ensure they are in 'references' array as objects with id properties)
let entityService = new GsbEntityService(_runtime);
let logService = new GsbLogService(_runtime);
// let Utils = new GsbUtil(); // Usually available globally as Utils if referenced
// 2. Access instance data (if needed)
let currentOrder = _instance.entity as _defs.GsbPrtOrder;
let params = _instance.prms;
// 3. Define the main logic in an async function
async function mainProcess() {
try {
// 4. Implement the function's logic
// Example: Fetch related data
let customerReq = new EntityQueryParams(_defs.GsbPartyCustomer);
customerReq.filter("id", currentOrder.customer_id);
let customerResp = await entityService.get(customerReq);
let customer = customerResp.entity;
if (!customer) {
await logService.logWarning("Customer not found for order: " + currentOrder.id, "mainProcess");
return _runtime.error(undefined, "Customer lookup returned no record.", "Customer not found.", undefined, 404);
}
// Example: Update the order
let orderUpdate = new _defs.GsbPrtOrder({
id: currentOrder.id,
status: _enums.OrderStatus.Processed,
processedDate: new Date()
});
await entityService.saveEnt(orderUpdate);
await logService.logInfo("Order processed: " + currentOrder.id, "mainProcess");
// 5. End the function execution
return _runtime.success("processed", { orderId: currentOrder.id });
} catch (error) {
// 6. Handle errors
await logService.logError("Error in mainProcess", "mainProcess", error);
return _runtime.error(error, "Order processing failed.", "The order could not be processed.", undefined, 500);
}
}
// 7. Invoke the main async function
mainProcess().then().catch(err => {
// Fallback error handling, though _runtime.error should ideally be caught within mainProcess
// This outer catch might be useful for programming errors in mainProcess itself before try/catch
return _runtime.error(err, "Unhandled rejection in mainProcess.", "The order could not be processed.", undefined, 500);
});
Example: Cancel Order and Related Entities (derived from Function (1).json)
This example demonstrates updating an order and its related quantities.
let entityService = new GsbEntityService(_runtime);
let orderToCancel = _instance.entity as _defs.GsbPrtOrder; // Assume _instance.entity is the order
async function cancelFullOrder() {
try {
// 1. Update Order status
let updatedOrder = new _defs.GsbPrtOrder({
id: orderToCancel.id,
status: _enums.OrderStatus.Cancelled
});
await entityService.saveEnt(updatedOrder);
console.log('Order status set to Cancelled.'); // Or use GsbLogService
// 2. Update related OrderQuantities status
let quantityUpdateQuery = new EntityQueryParams(_defs.GsbPrtOrderQuantity);
quantityUpdateQuery.filter("order_id", orderToCancel.id);
// The 'entity' property on EntityQueryParams is used by updateQuery as the payload
quantityUpdateQuery.entity = { status: _enums.OrderQuantityStatus.Cancelled } as _defs.GsbPrtOrderQuantity;
let updateResult = await entityService.updateQuery(quantityUpdateQuery);
console.log(`Updated ${updateResult.affectedRowCount} order quantities.`); // GsbQueryOpResponse might be actual type
// Optionally, cancel related invoice if exists
if (!Utils.isIdEmpty(orderToCancel.invoice_id)) {
let updatedInvoice = new _defs.GsbPrtInvoice({
id: orderToCancel.invoice_id,
status: _enums.InvoiceStatus.Cancelled
});
await entityService.saveEnt(updatedInvoice);
console.log('Related invoice cancelled.');
}
return _runtime.end(200, "Order and related entities cancelled.", "cancelled", undefined, _enums.ProcessAction.CancelWorkflow);
} catch (error) {
console.error("Error cancelling order:", error); // Use GsbLogService for persistent logs
return _runtime.error(error, "Order cancellation failed.", "The order could not be cancelled.", undefined, 500);
}
}
cancelFullOrder().then();
7. Error Handling and Logging
Robust error handling and logging are vital for maintainable serverless functions.
try...catchBlocks: Surround all potentially failing operations (especially I/O like service calls) withtry...catchblocks._runtime.error(): Use this to terminate the function when an error prevents successful completion. Keep the exception, internal message, safe user message, process action, and HTTP status in their dedicated arguments, then return immediately.GsbLogService: Use for detailed, persistent logging.- Log informational messages for key steps.
- Log warnings for recoverable issues or unusual conditions.
- Log errors with as much context as possible, including the operation name and the exception object.
async function someOperation() {
let logService = new GsbLogService(_runtime);
try {
await logService.logInfo("Starting operation X", "someOperation");
// ... potentially failing code ...
let result = await entityService.getById(_defs.MyEntity, "non-existent-id");
if (!result) {
await logService.logWarning("Entity not found, but proceeding.", "someOperation", { id: "non-existent-id" });
}
// ... more code ...
await logService.logInfo("Operation X completed", "someOperation");
return _runtime.success("completed");
} catch (e) {
await logService.logError("Critical failure in operation X", "someOperation", e);
return _runtime.error(e, "Operation X failed.", "The operation could not be completed.", undefined, 500);
}
}
8. Best Practices
- Explicit Dependencies: Always list the services your function uses in its
referencesarray as objects with id properties (e.g.,[{"id": "99e4c845-3032-458f-996f-8db3302f4e38"}]forGsbEntityService). - Asynchronous Code: Correctly use
async/awaitfor all Promises. Avoid blocking operations. - Error Handling: Implement comprehensive
try...catchblocks and use_runtime.error()andGsbLogService.logError(). - Type Safety: Use
_defsto cast entities and instantiate new ones (let order = _instance.entity as _defs.GsbPrtOrder;). Use_enumsfor status codes, types, etc. - Modularity: Keep functions focused on a single responsibility. For complex logic, consider breaking it into smaller helper functions within the script or multiple GSB functions orchestrated by a workflow.
- Readability: Write clean, well-commented code.
- Idempotency: If a function might be retried, design it to be idempotent (running it multiple times with the same input has the same effect as running it once).
- Input Validation: Validate input parameters (
_instance.prms,_instance.entity) at the beginning of your function. - Service Usage:
- Instantiate services once at the top of your script if they are used multiple times.
- Use
GsbEntityService.saveEnt()for simple saves if you have a typed entity object. UseGsbEntityService.save()withGsbSaveRequestif you need more control or don't have a fully typed object. - Leverage
QueryParamseffectively for precise data retrieval and updates.
- Performance:
- Only select the data you need using
QueryParams.select(). - Be mindful of N+1 query problems when fetching related data; use
QueryParams.include()where appropriate.
- Only select the data you need using
- Security:
- Be cautious when constructing queries or commands from user input to prevent injection attacks (though GSB services and
QueryParamsgenerally mitigate SQL injection). - Do not log sensitive information.
- Be cautious when constructing queries or commands from user input to prevent injection attacks (though GSB services and
- Configuration: Avoid hardcoding IDs or configuration values. If possible, retrieve them from GSB settings or entity configurations.
UtilsObject: Make use of theGsbUtil(viaUtils) for common tasks like ID checking (Utils.isIdEmpty), ID generation (Utils.newId()), and comparisons (Utils.idEquals).
By following this guide and utilizing the provided services and concepts, developers can effectively build powerful and reliable serverless functions within the GSB platform.
9. Creating GSB Serverless Functions
Function Creation Process
To create a new GSB serverless function, follow these steps:
Define function structure: Create a new JSON object with the following properties:
id: A unique identifier (UUID) for the functionname: A descriptive name for the functioncode: JavaScript code for the function's executionreferences: Array of objects with id properties for each service the function depends onoperations(optional): JSON string defining declarative operations
Write function code: The function code should follow this pattern:
// Initialize required services let entityService = new GsbEntityService(_runtime); let logService = new GsbLogService(_runtime); // Access the current entity if needed let entity = _instance.entity as _defs.YourEntityType; async function mainProcess() { try { // Your function logic here // End function execution with success return _runtime.success("completed", resultData); // OR with specific process action // return _runtime.end(200, "Completed.", "completed", resultData, _enums.ProcessAction.Continue); } catch (error) { await logService.logError("Error in function", "mainProcess", error); return _runtime.error(error, "Function failed.", "The operation could not be completed.", undefined, 500); } } // Execute the main function mainProcess().then();Add required service references: Include all necessary services in the
referencesarray as objects with id properties:"references": [ {"id": "99e4c845-3032-458f-996f-8db3302f4e38"}, // GsbEntityService {"id": "ca071e22-184e-443b-b7e5-05ad29a1dc13"}, // GsbLogService {"id": "085d21cb-349f-4a99-b5dc-679c8ee7947e"}, // GsbUtil {"id": "1c9bc14d-2151-4e14-b6c8-63c0421c1243"} // QueryParams ]
Using Declarative Operations
Functions can use a combination of code and declarative operations:
{
"operations": "[
{
\"id\": \"GUID\",
\"orderNumber\": 1,
\"operationType\": 10,
\"title\": \"Set Entity Status\",
\"setEntityOptions\": {
\"setProps\": [{
\"name\": \"status\",
\"value\": 2
}]
}
},
{
\"id\": \"GUID\",
\"orderNumber\": 2,
\"operationType\": 7,
\"title\": \"Process Data\",
\"scriptCode\": \"// JavaScript code here\"
}
]"
}
Common operation types:
7: Script execution8: Transaction commit9: Get entity10: Set entity properties13: Notification
10. Calling Functions
Calling Functions from UI
In the GSB UI, functions can be triggered through various means:
- Button Actions: Buttons can be configured to call functions
- Workflow Tasks: User tasks in workflows can call functions upon completion
- Event Handlers: UI events (form submit, field change) can trigger functions
The UI typically sends:
- The function ID or name to execute
- The current entity context
- Optional parameters
Calling Functions from Other Functions
Functions can call other functions using the GsbEntityService.runWfFunction() method:
// Define the function request
let functionRequest = {
function: {
// Either use ID
id: "function-uuid-here",
// OR use name (one of these is required)
name: "Function Name Here"
},
instance: {
// The entity to pass to the function (optional)
entity: myEntity,
// Additional parameters to pass (optional)
prms: {
param1: "value1",
param2: "value2"
}
}
};
// Call the function
let result = await entityService.runWfFunction(functionRequest);
// The result contains the function's response
let functionResponse = result.response;
Function Call Response
When a function is called using runWfFunction, the response object contains:
A
responsefield with whatever was set in the called function using:_instance.response = {...}return _runtime.success("route-result", responseData)return _runtime.end(statusCode, message, "route-result", responseData)
Status information and execution results from the function
Example:
// In the called function:
_instance.response = {
success: true,
data: { id: "123", status: "completed" }
};
return _runtime.success("completed", _instance.response);
// In the calling function:
let result = await entityService.runWfFunction(request);
console.log(result.response.success); // true
console.log(result.response.data.id); // "123"
Passing Data Between Functions
You can pass data between functions in several ways:
- Entity Context: Pass an entity as the context object
- Parameters: Use the
prmsobject to pass custom parameters - Response Data: Return data in the response that the calling function can access
Example of a complete function call with response handling:
async function callAnotherFunction() {
try {
let entityToPass = await entityService.getById(_defs.GsbPrtOrder, "order-id");
let functionRequest = {
function: { name: "Calculate Order Total" },
instance: {
entity: entityToPass,
prms: { applyDiscounts: true }
}
};
let result = await entityService.runWfFunction(functionRequest);
if (result.response && result.response.success) {
// Use the calculated total
let calculatedTotal = result.response.totalPrice;
// Continue processing...
} else {
throw new Error(result.response?.errorMessage || "Function execution failed");
}
} catch (error) {
return _runtime.error(error, "Function call failed.", "The operation could not be completed.", undefined, 500);
}
}
How to manage functions
For convenience you can create the functions under [projectRoot]/.gsb/functions/[functionName].ts its not meant to be run locally, but to be used as a reference for the AI to build the function. It will give linter errors, just ignore them.
Example: 1- Create a file called .gsb/functions/myFunction.ts
//does nothing hust returns a success message
return _runtime.success("success", {ret : _instance.entity?.a + _instance.prms?.b});
2- Test the function remotely use the "testFunction" tool to test the function remotely. test operation does not save the function to the GSB Backend, it only runs and returns the result. test operation can be used with both code and operations. you can pass instance with prms and entity to the function.
{
"function": {
"name": "myFunction",
"code": "// Returns the workflow result and response payload.
return _runtime.success("success", {ret : _instance.entity?.a + _instance.prms?.b});"
},
"instance": {
"entity": {
"a": 1
},
"prms": {
"b": 2
}
}
3- Save te function to GSB Backend use the "save" tool to save the function to the GSB Backend
{
"entDefName": "GsbWfFunction",
"entity": {
"name": "myFunction",//required
"title": "My Function",//required
"code": "// Returns the workflow result and response payload.
return _runtime.success("success", {ret : _instance.entity?.a + _instance.prms?.b});"
}
}
4- Use the function use the "runWfFunction" tool to run the function. From user interface you can use the "runFunction" method from gsb-entity-service.
{
"function": {
"name": "myFunction"
},
"instance": {
"entity": {
"a": 1
},
"prms": {
"b": 2
}
}
}