UNPKG

@gsb-core/mcp-docs

Version:

Documentation for GSB MCP implementations

882 lines (717 loc) 41.6 kB
export const gsbFunctionsMarkdown = ` # GSB Serverless Functions: Comprehensive Guide This document provides a comprehensive guide to building backend serverless functions within the GSB (GSB Business Suite) 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\`). All I/O operations, especially calls to GSB services, are asynchronous. Therefore, functions heavily rely on JavaScript\'s \`async/await\` syntax and \`Promise\`s. ## 2. Core Concepts ### \`_runtime\` Object The \`_runtime\` object is crucial for controlling the function\'s lifecycle and interacting with the GSB environment. Key methods include: - \`_runtime.end(statusCode, message, data, error, processAction)\`: Ends the function execution successfully. - \`statusCode\` (optional): HTTP-like status code (e.g., 200). - \`message\` (optional): A success message. - \`data\` (optional): Any data to return as the function\'s result. - \`error\` (optional): Error details if any. - \`processAction\` (optional, from \`_enums.ProcessAction\`): Specifies an action for a workflow, like \`_enums.ProcessAction.CancelWorkflow\`. - \`_runtime.success(message, data, processAction)\`: A shorthand for \`_runtime.end()\` indicating success. - \`_runtime.error(errorObjectOrMessage, data)\`: Ends the function execution due to an error. It\'s good practice to pass an \`Error\` object or a descriptive message. - \`_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. ### \`_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. \`\`\`javascript async function myAsyncFunction() { try { let entityService = new GsbEntityService(_runtime); let order = await entityService.getById(_defs.GsbPrtOrder, 'some-order-id'); // ... process order ... } catch (error) { _runtime.error(error); } } \`\`\` ## 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: \`\`\`javascript 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:** \`\`\`javascript 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\`. \`\`\`javascript // 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. \`\`\`javascript // 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\`. \`\`\`javascript 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:** \`\`\`javascript 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. \`\`\`javascript // 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\`. \`\`\`javascript // 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. \`\`\`javascript // Example: Update entities matching a query // let whQuantitesUpdateReq = new EntityQueryParams(_defs.GsbPrtOrderQuantity); // whQuantitesUpdateReq.prop(p => p.order_id).isEqual(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. \`\`\`javascript // 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:** \`\`\`javascript 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>\` \`\`\`javascript // 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); // _runtime.error("Processing failed."); // } \`\`\` ### \`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. \`\`\`javascript // 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). \`\`\`javascript // 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:** \`\`\`javascript // 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 (\`where\` / \`prop\` / \`property\`):** - \`where(propName: any, value: any, queryFunction?: _enums.QueryFunction, relation?: _enums.QueryRelation): QueryParams<T>\`: Adds a simple filter condition. - \`propName\`: Name of the property (e.g., \`"status"\`, \`"customer.name"\`). - \`value\`: The value to compare against. - \`queryFunction\` (optional): From \`_enums.QueryFunction\` (e.g., \`_enums.QueryFunction.Equal\`, \`_enums.QueryFunction.Like\`, \`_enums.QueryFunction.GreaterThanOrEqual\`). Defaults to \`Equal\`. - \`relation\` (optional): From \`_enums.QueryRelation\` (e.g., \`_enums.QueryRelation.And\`, \`_enums.QueryRelation.Or\`). Defaults to \`And\`. - \`prop(conditionFn: (item: T) => any): SingleQuery\` or \`property(conditionFn: (item: T) => any): SingleQuery\`: Provides a typed way to specify the property using a lambda expression. Returns a \`SingleQuery\` object on which you can chain comparison methods. \`\`\`javascript // Example using prop: eqp.prop(p => p.status).isEqual(_enums.OrderStatus.Pending); eqp.prop(p => p.customer.name).isLike("John%"); eqp.prop(p => p.totalAmount).isGreater(100); eqp.prop(p => p.category_id).in(['catId1', 'catId2']); \`\`\` - \`SingleQuery\` methods: \`isEqual()\`, \`isLike()\`, \`isGreater()\`, \`isSmaller()\`, \`contains()\`, \`in()\`, \`fullTextSearch()\`, \`is()\`, \`not()\`, \`funcVal()\`. - \`query?: SingleQuery[]\`: Array to hold multiple filter conditions. - \`filter?: any\`: Can hold complex filter structures. - **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). \`\`\`javascript eqp.select(p => [p.id, p.orderNumber, p.customer.name]); // or eqp.select("id").select("orderNumber").select("customer.name"); \`\`\` - \`selectCols?: SelectCol[]\`: Array of \`SelectCol\` objects. - \`SelectCol { cName?: string, aggregateFunction?: any, colName?: string, script?: any, groupBy?: any, fullName?: any, title?: any }\` - **Including Related Entities (\`include\`):** - \`include<R = T>(...colNames: (string | string[] | ((item: T) => any))[]): { self: QueryParams<T>; inc: IncludeQuery<R> }\`: Includes related entities (joins). - \`colNames\`: Property names of navigation properties. - Returns an object where \`inc\` is an \`IncludeQuery\` instance that you can further configure (e.g., with its own \`select\`, \`where\`, nested \`include\`). \`\`\`javascript // Example: Include order items and the product for each item let orderQuery = new EntityQueryParams(_defs.GsbPrtOrder); orderQuery.prop(p => p.id).isEqual('some-order-id'); let itemsInclude = orderQuery.include(o => o.items).inc; // itemsInclude is an IncludeQuery<_defs.GsbPrtOrderItem> itemsInclude.select(oi => [oi.id, oi.quantity]); let productInclude = itemsInclude.include(oi => oi.product).inc; // productInclude is an IncludeQuery<_defs.GsbInvProduct> productInclude.select(p => [p.id, p.name, p.price]); // let result = await entityService.query(orderQuery); \`\`\` - \`includes?: IncludeQuery[]\`: Array of \`IncludeQuery\` objects. - \`incS(propnames?: any): QueryParams<T>\`: Shortcut for including simple properties. - \`incQ(q: IncludeQuery | IncludeQuery[]): QueryParams<T>\`: Adds pre-configured \`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\`. \`\`\`javascript eqp.sortBy(p => p.createdDate, _enums.QuerySortType.Desc); \`\`\` - \`sortCols?: SortCol[]\`: Array of \`SortCol\` objects. - \`SortCol { col: SelectCol, sortType: _enums.QuerySortType }\` - **Pagination:** - \`startIndex?: any\` - \`count?: any\` - \`calcTotalCount?: any\` (boolean): If true, the query response will include the total count of matching records. - **Static Helper \`QueryParams.ApplyWhere\`:** - \`static ApplyWhere(eqp: QueryParams<any>, propName: any, value: any, relation?: any, queryName?: any): QueryParams<any>\`: A utility to apply a where condition. - **Fluent Interface:** - Many methods like \`where\`, \`select\`, \`sortBy\`, \`include\` return \`this\` or a related query object, allowing for chaining. - \`apply(callback: (qb: this) => void): this\`: Allows applying a set of configurations via a callback. **Example of \`EntityQueryParams\` for an update operation:** As seen in \`Function (1).json\` (e.g., "Cancel Order" function): \`\`\`javascript // 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.prop(p => p.order_id).isEqual(_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. \`\`\`json // 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: \`\`\`javascript // 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.prop(c => c.id).isEqual(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"); _runtime.error("Customer not found."); return; } // 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 _runtime.success("Order processed successfully", { orderId: currentOrder.id }); } catch (error) { // 6. Handle errors await logService.logError("Error in mainProcess", "mainProcess", error); _runtime.error(error.message || "An unexpected error occurred."); } } // 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 _runtime.error("Unhandled promise rejection in function: " + (err.message || err)); }); \`\`\` ### Example: Cancel Order and Related Entities (derived from \`Function (1).json\`) This example demonstrates updating an order and its related quantities. \`\`\`javascript 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.prop(oq => oq.order_id).isEqual(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.'); } _runtime.success("Order and related entities cancelled.", null, _enums.ProcessAction.CancelWorkflow); } catch (error) { console.error("Error cancelling order:", error); // Use GsbLogService for persistent logs _runtime.error(error); } } 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 occurs that prevents successful completion. Provide a meaningful message or error object. - **\`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. \`\`\`javascript 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"); _runtime.success("Operation X done."); } catch (e) { await logService.logError("Critical failure in operation X", "someOperation", e); _runtime.error(error.message || "An unexpected error occurred."); } } \`\`\` ## 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: \`\`\`javascript // 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 _runtime.success("Success message", resultData); // OR with specific process action // _runtime.end(200, "Message", data, undefined, _enums.ProcessAction.SomeAction); } catch (error) { await logService.logError("Error in function", "mainProcess", error); _runtime.error(error); } } // Execute the main function mainProcess().then(); \`\`\` 3. **Add required service references**: Include all necessary services in the \`references\` array as objects with id properties: \`\`\`json "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: \`\`\`json { "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: \`\`\`javascript // 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 = {...}\` - \`_runtime.success("Message", responseData)\` - \`_runtime.end(statusCode, message, data)\` 2. Status information and execution results from the function Example: \`\`\`javascript // In the called function: _instance.response = { success: true, data: { id: "123", status: "completed" } }; _runtime.success("Operation completed successfully"); // 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: \`\`\`javascript 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) { _runtime.error(error); } } \`\`\` ### 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 \`\`\`typescript //does nothing hust returns a success message _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. \`\`\`json { "function": { "name": "myFunction", "code": "//does nothing hust returns a success message\n_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 \`\`\`json { "entDefName": "GsbWfFunction", "entity": { "name": "myFunction",//required "title": "My Function",//required "code": "//does nothing hust returns a success message\n_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. \`\`\`json { "function": { "name": "myFunction" }, "instance": { "entity": { "a": 1 }, "prms": { "b": 2 } } } \`\`\` `;