API reference
save() — Entity Service
Purpose : Creates new entities or updates existing ones. When to use : Creating new entities Updating existing entities Saving complex nested data struc…
@gsb-core/mcp-docs:save General Description
The save operation creates a new entity or updates an existing one in the database.
Detailed Description
This operation handles both creating new entities and updating existing ones. When saving an entity without an ID, a new entity is created, and an ID is automatically generated. When saving an entity with an existing ID, the entity is updated. The operation validates the entity against its definition before saving, ensuring data integrity.
The save operation supports complex JSON structures with nested objects and arrays, automatically handling relationships between entities. GSB will intelligently process the data, performing inserts or updates for all nested entities and managing relationships automatically based on the presence of primary keys.
Primary Key Matching
An ID is not the only way to identify an existing entity. An entity definition can mark multiple properties as primary keys. When the save payload supplies any configured primary key and its value matches an existing row, GSB updates that row instead of inserting another one. For example, if GsbUser.email is a primary key, saving a user with an existing email updates that user even when the payload omits id.
Partial primary keys form a composite match. GSB attempts that match only when the payload supplies every property marked isPartialPrimaryKey. If the complete value set identifies an existing row, save updates it; otherwise save creates a row. Do not treat one member of a partial-key set as independently identifying the entity.
Participation of reference properties and their companion *_id fields in partial-primary-key matching is not yet a documented contract. Use id or verified scalar key properties for that case until the reference-field behavior has been tested.
Atomic Calculated Updates
Use _CALC(expression) as a property value when an update must be calculated from the value currently stored in the database. Direct properties on the row being saved use square-bracket references such as [viewCount]. The calculation runs as part of the save transaction, avoiding a read-modify-write race in application code.
This example atomically increments an existing counter:
await save({
entDefName: "Article",
entity: {
id: articleId,
viewCount: "_CALC([viewCount] + 1)",
},
});
The expression is not limited to increments. Integer and decimal literals can be combined with referenced direct properties, arithmetic, comparisons, parentheses, and supported functions and keywords. For example:
await save({
entDefName: "Order",
entity: {
id: orderId,
totalCount: "_CALC([totalCount] + 1)",
adjustedTotal:
"_CALC([_ROUND](([_COALESCE]([subtotal], 0) + [_COALESCE]([shipping], 0)) * 1.20 - [_ABS]([discount]), 2))",
},
});
GSB evaluates the complete expression against the current stored row inside the save transaction. Expressions may be as deeply composed as the business calculation requires, provided every property, token, and character belongs to the documented allowlists.
When a key-based save may create the row, append : createValue after the expression. GSB evaluates the expression for a matching row and uses the suffix as the initial value when no row exists:
await save({
entDefName: "AccountBalance",
entity: {
partner_id: partnerId,
currency_id: currencyId,
balance: `_CALC([_COALESCE]([balance], 0) + ${change}) : ${change}`,
},
});
Supported expression tokens
Reference direct properties from the row being saved as [propertyName]. Use supported functions and keywords in bracketed underscore form, such as [_COALESCE] or [_CASE]:
| Group | Supported tokens |
|---|---|
| Null and conditions | NULL, CASE, WHEN, THEN, ELSE, END, COALESCE, NULLIF, IS, NOT, AND, OR |
| Numeric | ABS, ROUND, CEILING, FLOOR, POWER, SQRT, EXP, LOG |
| Text | LEN, LENGTH, LTRIM, RTRIM, SUBSTRING, SUBSTR, UPPER, LOWER, REPLACE, CONCAT |
| Aggregate | COUNT, SUM, AVG, MIN, MAX |
For example, _CALC([_CASE] [_WHEN] [amount] > 0 [_THEN] [_ROUND]([amount], 2) [_ELSE] 0 [_END]) combines keywords, a numeric function, and the entity's amount property. This table is the supported allowlist; do not assume arbitrary SQL functions are available.
Allowed characters and string limitation
Outside recognized bracketed property and operator names, calculation expressions accept only digits, spaces, and these punctuation characters:
0123456789+-*/.,():?\ <>=
The apostrophe character (') is not allowed, so _CALC does not currently support quoted string literals or concatenating a static string such as 'prefix-'. Text functions including CONCAT, REPLACE, UPPER, and LOWER operate on referenced entity string properties, for example [_CONCAT]([firstName], [lastName]). Supply fixed text through an ordinary saved property or calculate it outside _CALC rather than embedding a quoted literal.
The exact wrapper token is _CALC with one leading underscore; __CALC is not valid syntax. Treat the expression as executable data-layer syntax: validate and convert interpolated values to expected primitive types, and never concatenate untrusted text into it.
Input Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| request | object | Yes | The request object containing the entity definition and entity to save. |
| request.entityDef | object | Yes | The entity definition object with id and/or name properties. |
| request.entity | object | Yes | The entity object to save, which can include nested objects and arrays for related entities. |
| request.entityDef.name | string | Yes* | Name of the entity definition. Required if entDefId is not provided. |
| request.entityDef.id | string | Yes* | ID of the entity definition. Required if entDefName is not provided. |
| request.entity | object | Yes | The entity object to save, which can include nested objects and arrays for related entities. |
| token | string | No | Authentication token for your request. If not provided, the system will use the default API key from environment variables. |
| tenantCode | string | No | Tenant code to specify which tenant's data to access. If not provided, the system will extract it from the token or use the default tenant code from environment variables. |
Response
Success Response
{
"success": true,
"id": "string", // ID of the created or updated entity
"isUpdate": boolean // Whether this was an update (true) or create (false) operation
}
Error Response
{
"success": false,
"message": "Error message describing what went wrong"
}
Example Usage
Creating a New Entity
const result = await save({
entDefName: "Customer",
entity: {
firstName: "John",
lastName: "Doe",
email: "john@example.com",
status: "active"
}
});
if (result.success) {
console.log("New customer created with ID:", result.id);
console.log("Is update?", result.isUpdate); // false
} else {
console.error("Error:", result.error);
}
Updating an Existing Entity
const result = await save({
entDefName: "Customer",
entity: {
id: "existing-customer-id",
firstName: "John",
lastName: "Doe",
email: "john.updated@example.com",
status: "inactive"
}
});
if (result.success) {
console.log("Customer updated successfully with ID:", result.id);
console.log("Is update?", result.isUpdate); // true
} else {
console.error("Error:", result.error);
}
Saving Complex Nested Data
const result = await save({
entDefName: "Order",
entity: {
orderNumber: "ORD-12345",
orderDate: "2023-06-15",
status: "pending",
customer: {
id: "existing-customer-id", // Existing customer - will be updated
firstName: "John",
lastName: "Doe",
email: "john@example.com"
},
items: [
{
id: "existing-item-id", // Existing item - will be updated
productName: "Smartphone",
quantity: 1,
unitPrice: 999.99
},
{
// No ID - new item will be created
productName: "Phone Case",
quantity: 2,
unitPrice: 29.99
}
],
shippingAddress: {
// New address will be created and linked to the order
street: "123 Main St",
city: "Anytown",
state: "CA",
zipCode: "12345"
},
paymentDetails: {
id: "payment-123", // Existing payment - will be updated
method: "credit_card",
amount: 1059.97
}
}
});
if (result.success) {
console.log("Order saved with ID:", result.id);
}
Using Entity Definition ID
const result = await save({
entDefId: "customer-def-id",
entity: {
firstName: "Jane",
lastName: "Smith",
email: "jane@example.com",
status: "active"
}
});
if (result.success) {
console.log("Entity saved with ID:", result.id);
}
Using Entity Definition Object
const result = await save({
entityDef: {
name: "Product"
},
entity: {
name: "Smartphone",
description: "Latest model smartphone",
price: 999.99,
inStock: true
}
});
Additional Information
- When creating a new entity, the system automatically generates an ID and sets system fields like createDate and createdBy.
- When updating an entity, the system automatically updates the lastUpdateDate and lastUpdatedBy fields.
- Required fields as defined in the entity definition must be provided.
- Validation rules defined in the entity definition are enforced during saving.
- For saving multiple entities at once, use the saveMulti operation instead.
- The operation returns both the ID of the saved entity and a boolean indicating whether it was an update operation.
- If the entity has unique constraints, the operation will fail if the constraints are violated.
- Access permissions are enforced based on the provided token.
- References to other entities can be included in the entity object using their IDs.
- For saving mapped items in a many-to-many relationship, use the saveMappedItems operation instead.
Complex Data Handling
- GSB automatically processes nested objects and arrays as related entities.
- For each nested entity:
- If an ID is provided and exists in the database, the entity will be updated.
- If no ID is provided or the ID doesn't exist, a new entity will be created.
- Relationships between entities are automatically maintained.
- One-to-many and many-to-many relationships are handled through arrays of objects.
- One-to-one relationships are handled through nested objects.
- Nested entities execute their configured workflow and serverless save triggers just like entities saved directly.
- The system intelligently determines whether to perform inserts or updates based on the presence of primary keys.
- All operations are performed in a single transaction, ensuring data consistency.
- If any part of the complex save operation fails, the entire transaction is rolled back.