Pikbase Docs
Open console (opens the console)
Esc

Type to search.

Build

Serverless functions

Author and run backend logic using the documented function runtime.

Contract source@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 (like GsbEntityService, 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 references array 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. result still 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 internal message, a safe caller-facing userMessage, the workflow action, and the HTTP status code.

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. Omitted httpStatus is 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 is result: "Error" and action: "BreakFunction".
  • Use responseService.validation({ issues, ... }) for request validation. Its default code is INVALID_REQUEST and 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.error in 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 by GsbLogService).
  • _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 by instance?.moduleSettings.
  • _runtime.variation: The same variation exposed by instance?.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 sendEmail function uses SmtpService and the injected nodemailer global. After the EU1 backend upgrade, plain email and an in-memory PDF attachment were both delivered successfully. The attachment arrived with MIME type application/pdf and disposition attachment.
  • Declarative notification operation type 13 successfully sends ordinary email. A tested GsbFile PDF 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.ses configuration. A missing setting is a configuration failure, not a reason to embed provider credentials in function source.
  • Task scheduling uses the runtime-provided bullmq and ioredis modules. The current groupOperations model schedules the SMTP-backed sendEmail function 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 TaskSchedulerService source 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 type 12 document 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:

  • OrderStatus
  • InvoiceStatus
  • PaymentStatus
  • OrderQuantityStatus
  • QuerySortType
  • QueryFunction (for query conditions)
  • QueryRelation (AND/OR for query conditions)
  • ProcessAction (for workflow control)
  • DataPassType
  • OrderSubProcessType
  • TriggerGroup

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. Prefer httpCallAsync.

  • httpCallAsync(dto: HttpCallRequest): Promise: Calls an HTTP/HTTPS API asynchronously.

    • dto: An HttpCallRequest object.
    // 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: An EntityQueryParams object 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: A GsbSaveRequest object, which includes entDefName and entity data.
    // 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 _defs class. The service infers entDefName.
    // 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 be GsbQueryOpResponse based on library, often GsbSaveResponse is used but affectedRowCount is the key)
    • Updates entities matching a query. The req object's entity property 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);
  • saveMulti(req: GsbSaveMultiRequest, tenantCode?, token?): Promise<GsbSaveMultiResponse>: Saves multiple entities of the same type.
    • req: A GsbSaveMultiRequest object with entDefName and an entities array.
  • 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_id and entity.customer) has an ID, optionally creating one.
  • sortByOrderNum(arr: any[], prop = undefined): any[]: Sorts an array of objects by an orderNumber property (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 its id.

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.YourEntity or "YourEntityName").
    • entDefName: String name of the entity definition.
    • entDefId: ID of the entity definition.
  • Filtering (filter):

    • filter(propName, value, queryFunction?, relation?) adds a Filter to filters and returns the query.
    • Property names may be strings (including dotted paths) or typed expressions such as (item) => item.customer.name.
    • Use QueryFunction for comparisons and QueryRelation.And / QueryRelation.Or to 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 uses col: { name }, val: { value }, and function.
  • 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: A SelectCol object for advanced options (aliasing, aggregation - though aggregation is less common in basic serverless functions).
      eqp.select(["id", "orderNumber", "customer.name"]);
    • selectCols?: SelectCol[]: Array of SelectCol objects.
      • SelectCol { name, aggregateFunction?, dateModifier?, script?, groupBy?, selectAsTitle? }
  • Including Related Entities (include):

    • include(...colNames): Includes related entities and returns the root query with .inc and .self helpers.
      • colNames: Property names of navigation properties.
      • .inc is the latest IncludeQuery; .self resumes 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 of IncludeQuery objects.
  • Sorting (sortBy):

    • sortBy(colName: (string | ((item: T) => any)), sortType: _enums.QuerySortType): QueryParams<T>
      • colName: Property name or lambda.
      • sortType: _enums.QuerySortType.Asc or _enums.QuerySortType.Desc.
      eqp.sortBy("createdDate", QuerySortType.Descending);
    • sortCols?: SortCol[]: Array of SortCol objects.
      • 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, and setName return the query.
    • CLI and MCP callers send the serialized filters, selectCols, includes, and sortCols fields 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: If operationType is 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);
});

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...catch Blocks: Surround all potentially failing operations (especially I/O like service calls) with try...catch blocks.
  • _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 references array as objects with id properties (e.g., [{"id": "99e4c845-3032-458f-996f-8db3302f4e38"}] for GsbEntityService).
  • Asynchronous Code: Correctly use async/await for all Promises. Avoid blocking operations.
  • Error Handling: Implement comprehensive try...catch blocks and use _runtime.error() and GsbLogService.logError().
  • Type Safety: Use _defs to cast entities and instantiate new ones (let order = _instance.entity as _defs.GsbPrtOrder;). Use _enums for 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. Use GsbEntityService.save() with GsbSaveRequest if you need more control or don't have a fully typed object.
    • Leverage QueryParams effectively 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.
  • Security:
    • Be cautious when constructing queries or commands from user input to prevent injection attacks (though GSB services and QueryParams generally mitigate SQL injection).
    • Do not log sensitive information.
  • Configuration: Avoid hardcoding IDs or configuration values. If possible, retrieve them from GSB settings or entity configurations.
  • Utils Object: Make use of the GsbUtil (via Utils) 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:

  1. Define function structure: Create a new JSON object with the following properties:

    • id: A unique identifier (UUID) for the function
    • name: A descriptive name for the function
    • code: JavaScript code for the function's execution
    • references: Array of objects with id properties for each service the function depends on
    • operations (optional): JSON string defining declarative operations
  2. 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();
  3. Add required service references: Include all necessary services in the references array 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 execution
  • 8: Transaction commit
  • 9: Get entity
  • 10: Set entity properties
  • 13: Notification

10. Calling Functions

Calling Functions from UI

In the GSB UI, functions can be triggered through various means:

  1. Button Actions: Buttons can be configured to call functions
  2. Workflow Tasks: User tasks in workflows can call functions upon completion
  3. 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:

  1. A response field 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)
  2. 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:

  1. Entity Context: Pass an entity as the context object
  2. Parameters: Use the prms object to pass custom parameters
  3. 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
        }
    }
}