object-functions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSteedos Object Functions | Steedos 对象函数
Steedos Object Functions | Steedos 对象函数
Overview | 概述
Overview | 概述
Object functions are server-side JavaScript functions defined as files. They encapsulate business logic and can be exposed as REST API endpoints, called from buttons, triggers, or other functions.
.function.yml对象函数是定义为 文件的服务端 JavaScript 函数。它们封装业务逻辑,可以作为 REST API 端点暴露,从按钮、触发器或其他函数调用。
.function.yml对象函数是定义为 文件的服务端JavaScript函数。它们封装业务逻辑,可作为REST API端点暴露,也可从按钮、触发器或其他函数调用。
.function.yml对象函数是定义为 文件的服务端 JavaScript 函数。它们封装业务逻辑,可以作为 REST API 端点暴露,从按钮、触发器或其他函数调用。
.function.ymlFile Location | 文件位置
File Location | 文件位置
steedos-packages/
└── my-package/
└── main/default/
└── functions/
├── approve_order.function.yml
├── cancel_order.function.yml
└── sync_to_erp.function.ymlsteedos-packages/
└── my-package/
└── main/default/
└── functions/
├── approve_order.function.yml
├── cancel_order.function.yml
└── sync_to_erp.function.ymlFunction Structure | 函数结构
Function Structure | 函数结构
yaml
undefinedyaml
undefinedfunctions/orders_approve_order.function.yml
functions/orders_approve_order.function.yml
name: orders_approve_order
objectApiName: orders
description: Approve an order and update status
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'submitted') {
throw new Error('Only submitted orders can be approved');
}
await objects.orders.directUpdate(input.id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
return { message: 'Order approved successfully' };
undefinedname: orders_approve_order
objectApiName: orders
description: Approve an order and update status
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'submitted') {
throw new Error('Only submitted orders can be approved');
}
await objects.orders.directUpdate(input.id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
return { message: 'Order approved successfully' };
undefinedFunction Properties | 函数属性
Function Properties | 函数属性
| Property | Type | Required | Description |
|---|---|---|---|
| string | Yes | ⚠️ MUST start with |
| string | Yes | Associated object API name |
| string | No | Human-readable description |
| boolean | Yes | Enable/disable function |
| boolean | Yes | Expose as REST API endpoint |
| boolean | No | Lock from editing |
| string | Yes | Inline JavaScript code (YAML block scalar ` |
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
| string | 是 | ⚠️ 必须以 |
| string | 是 | 关联对象的API名称 |
| string | 否 | 易读的描述信息 |
| boolean | 是 | 启用/禁用函数 |
| boolean | 是 | 是否作为REST API端点暴露 |
| boolean | 否 | 是否锁定禁止编辑 |
| string | 是 | 内联JavaScript代码(使用YAML块标量` |
Script Context | 脚本上下文
Script Context | 脚本上下文
Available Variables | 可用变量
Available Variables | 可用变量
javascript
// ctx - Execution context
ctx.input // Function input parameters (from API body or caller)
ctx.userId // Current user ID
ctx.spaceId // Current workspace ID
ctx.broker // Moleculer service broker
ctx.getUser(userId, spaceId) // Get user details
// objects - Object API access
objects.orders.findOne(id)
objects.orders.find({ filters, fields, top, skip, sort })
objects.orders.insert(doc)
objects.orders.update(id, doc)
objects.orders.directUpdate(id, doc) // Bypass triggers
objects.orders.directInsert(doc) // Bypass triggers
objects.orders.delete(id)
objects.orders.count({ filters })
// global - Utility libraries
global._ // lodash
global.moment // moment.js (date library)
global.validator // validator.js
global.filters // filter utilitiesjavascript
// ctx - 执行上下文
ctx.input // 函数输入参数(来自API请求体或调用者)
ctx.userId // 当前用户ID
ctx.spaceId // 当前工作区ID
ctx.broker // Moleculer服务代理
ctx.getUser(userId, spaceId) // 获取用户详情
// objects - 对象API访问
objects.orders.findOne(id)
objects.orders.find({ filters, fields, top, skip, sort })
objects.orders.insert(doc)
objects.orders.update(id, doc)
objects.orders.directUpdate(id, doc) // 绕过触发器
objects.orders.directInsert(doc) // 绕过触发器
objects.orders.delete(id)
objects.orders.count({ filters })
// global - 工具库
global._ // lodash
global.moment // moment.js(日期处理库)
global.validator // validator.js
global.filters // 过滤工具API Endpoint | API 端点
API Endpoint | API 端点
When , the function is accessible at:
is_rest: truePOST /api/v6/functions/{objectApiName}/{shortFunctionName}
GET /api/v6/functions/{objectApiName}/{shortFunctionName}⚠️ The prefix MUST be removed from the function name in the URL.
objectApiName_Example: a function named (objectApiName: ) is called as:
orders_approve_orderordersPOST /api/v6/functions/orders/approve_order
POST /api/v6/functions/leads/convert_lead当时,函数可通过以下地址访问:
is_rest: truePOST /api/v6/functions/{objectApiName}/{shortFunctionName}
GET /api/v6/functions/{objectApiName}/{shortFunctionName}⚠️ URL中的函数名称必须去掉前缀。
objectApiName_示例:名为(objectApiName: )的函数调用地址为:
orders_approve_orderordersPOST /api/v6/functions/orders/approve_order
POST /api/v6/functions/leads/convert_leadComplete Examples | 完整示例
Complete Examples | 完整示例
Example 1: Simple Status Update | 简单状态更新
Example 1: Simple Status Update | 简单状态更新
yaml
undefinedyaml
undefinedfunctions/orders_submit_order.function.yml
functions/orders_submit_order.function.yml
name: orders_submit_order
objectApiName: orders
description: Submit order for approval
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'draft') {
throw new Error('Only draft orders can be submitted');
}
if (!record.customer) {
throw new Error('Customer is required');
}
await objects.orders.directUpdate(input.id, {
status: 'submitted',
submitted_at: new Date(),
submitted_by: ctx.userId
});
return { message: 'Order submitted for approval' };
undefinedname: orders_submit_order
objectApiName: orders
description: Submit order for approval
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'draft') {
throw new Error('Only draft orders can be submitted');
}
if (!record.customer) {
throw new Error('Customer is required');
}
await objects.orders.directUpdate(input.id, {
status: 'submitted',
submitted_at: new Date(),
submitted_by: ctx.userId
});
return { message: 'Order submitted for approval' };
undefinedExample 2: Complex Business Logic | 复杂业务逻辑
Example 2: Complex Business Logic | 复杂业务逻辑
yaml
undefinedyaml
undefinedfunctions/km_updates_adopt_update.function.yml
functions/km_updates_adopt_update.function.yml
name: km_updates_adopt_update
objectApiName: km_updates
description: Adopt a knowledge management update into materials
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const update = await objects.km_updates.findOne(input.id);
if (!update) {
throw new Error('Update record not found');
}
// Find related material
const material = await objects.materials.findOne(update.material_id);
if (!material) {
throw new Error('Related material not found');
}
// Copy fields from update to material
const updateData = {
name: update.name,
description: update.description,
category: update.category,
status: 'active',
updated_from: input.id,
adopted_at: new Date(),
adopted_by: ctx.userId
};
await objects.materials.directUpdate(material._id, updateData);
// Mark update as adopted
await objects.km_updates.directUpdate(input.id, {
status: 'adopted',
adopted_at: new Date()
});
return { message: 'Update adopted successfully', materialId: material._id };
undefinedname: km_updates_adopt_update
objectApiName: km_updates
description: Adopt a knowledge management update into materials
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const update = await objects.km_updates.findOne(input.id);
if (!update) {
throw new Error('Update record not found');
}
// Find related material
const material = await objects.materials.findOne(update.material_id);
if (!material) {
throw new Error('Related material not found');
}
// Copy fields from update to material
const updateData = {
name: update.name,
description: update.description,
category: update.category,
status: 'active',
updated_from: input.id,
adopted_at: new Date(),
adopted_by: ctx.userId
};
await objects.materials.directUpdate(material._id, updateData);
// Mark update as adopted
await objects.km_updates.directUpdate(input.id, {
status: 'adopted',
adopted_at: new Date()
});
return { message: 'Update adopted successfully', materialId: material._id };
undefinedExample 3: Soft Delete | 软删除
Example 3: Soft Delete | 软删除
yaml
undefinedyaml
undefinedfunctions/km_updates_trash_record.function.yml
functions/km_updates_trash_record.function.yml
name: km_updates_trash_record
objectApiName: km_updates
description: Mark record as trashed instead of deleting
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.km_updates.findOne(input.id);
if (!record) {
throw new Error('Record not found');
}
await objects.km_updates.directUpdate(input.id, {
is_deleted: true,
deleted_at: new Date(),
deleted_by: ctx.userId
});
return { message: 'Record moved to trash' };
undefinedname: km_updates_trash_record
objectApiName: km_updates
description: Mark record as trashed instead of deleting
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.km_updates.findOne(input.id);
if (!record) {
throw new Error('Record not found');
}
await objects.km_updates.directUpdate(input.id, {
is_deleted: true,
deleted_at: new Date(),
deleted_by: ctx.userId
});
return { message: 'Record moved to trash' };
undefinedExample 4: External API Integration | 外部 API 集成
Example 4: External API Integration | 外部 API 集成
yaml
undefinedyaml
undefinedfunctions/orders_sync_to_erp.function.yml
functions/orders_sync_to_erp.function.yml
name: orders_sync_to_erp
objectApiName: orders
description: Sync order to external ERP system
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const order = await objects.orders.findOne(input.id);
if (!order) {
throw new Error('Order not found');
}
const customer = await objects.customers.findOne(order.customer);
const fetch = require('node-fetch');
const response = await fetch(process.env.ERP_API_URL + '/orders', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ERP_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_number: order.order_number,
customer_name: customer?.name,
amount: order.total_amount,
items: order.line_items
})
});
const result = await response.json();
await objects.orders.directUpdate(input.id, {
erp_sync_id: result.id,
erp_sync_status: 'synced',
erp_synced_at: new Date()
});
return { message: 'Synced to ERP', erpId: result.id };
undefinedname: orders_sync_to_erp
objectApiName: orders
description: Sync order to external ERP system
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const order = await objects.orders.findOne(input.id);
if (!order) {
throw new Error('Order not found');
}
const customer = await objects.customers.findOne(order.customer);
const fetch = require('node-fetch');
const response = await fetch(process.env.ERP_API_URL + '/orders', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ERP_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_number: order.order_number,
customer_name: customer?.name,
amount: order.total_amount,
items: order.line_items
})
});
const result = await response.json();
await objects.orders.directUpdate(input.id, {
erp_sync_id: result.id,
erp_sync_status: 'synced',
erp_synced_at: new Date()
});
return { message: 'Synced to ERP', erpId: result.id };
undefinedExample 5: Batch Processing | 批量处理
Example 5: Batch Processing | 批量处理
yaml
undefinedyaml
undefinedfunctions/orders_batch_approve.function.yml
functions/orders_batch_approve.function.yml
name: orders_batch_approve
objectApiName: orders
description: Approve multiple orders at once
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const ids = input.ids;
if (!ids || !_.isArray(ids) || ids.length === 0) {
throw new Error('No records selected');
}
let successCount = 0;
let failCount = 0;
const errors = [];
for (const id of ids) {
try {
const order = await objects.orders.findOne(id);
if (order && order.status === 'submitted') {
await objects.orders.directUpdate(id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
successCount++;
} else {
failCount++;
errors.push({ id, reason: 'Not in submitted status' });
}
} catch (e) {
failCount++;
errors.push({ id, reason: e.message });
}
}
return {
message: ,
successCount,
failCount,
errors
};
Approved ${successCount} orders, ${failCount} failedundefinedname: orders_batch_approve
objectApiName: orders
description: Approve multiple orders at once
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const ids = input.ids;
if (!ids || !_.isArray(ids) || ids.length === 0) {
throw new Error('No records selected');
}
let successCount = 0;
let failCount = 0;
const errors = [];
for (const id of ids) {
try {
const order = await objects.orders.findOne(id);
if (order && order.status === 'submitted') {
await objects.orders.directUpdate(id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
successCount++;
} else {
failCount++;
errors.push({ id, reason: 'Not in submitted status' });
}
} catch (e) {
failCount++;
errors.push({ id, reason: e.message });
}
}
return {
message: ,
successCount,
failCount,
errors
};
Approved ${successCount} orders, ${failCount} failedundefinedCalling Functions from Buttons | 从按钮调用函数
Calling Functions from Buttons | 从按钮调用函数
Functions with can be called from schemas:
is_rest: trueamis_buttonyaml
undefinedis_rest: trueamis_buttonyaml
undefinedIn a .button.yml amis_schema:
In a .button.yml amis_schema:
amis_schema: |-
{
"type": "service",
"body": {
"type": "button",
"label": "Approve",
"onEvent": {
"click": {
"actions": [
{
"actionType": "ajax",
"api": {
"url": "/api/v6/functions/orders/approve_order",
"method": "post",
"requestAdaptor": "api.data = { id: api.body.recordId }",
"messages": { "success": "Approved" }
}
}
]
}
}
}
}
undefinedamis_schema: |-
{
"type": "service",
"body": {
"type": "button",
"label": "Approve",
"onEvent": {
"click": {
"actions": [
{
"actionType": "ajax",
"api": {
"url": "/api/v6/functions/orders/approve_order",
"method": "post",
"requestAdaptor": "api.data = { id: api.body.recordId }",
"messages": { "success": "Approved" }
}
}
]
}
}
}
}
undefinedBest Practices | 最佳实践
Best Practices | 最佳实践
- Use /
directUpdatewhen appropriate: These bypass triggers to avoid infinite loops when updating related recordsdirectInsert - Validate input early: Check parameters and record existence before processing
input - Return meaningful results: Always return an object with a and relevant data
message - Handle errors with throw: Use for validation failures - the platform returns appropriate HTTP error responses
throw new Error('message') - Access user context: Use and
ctx.userIdfor permission checksctx.getUser() - Use lodash from global: gives you lodash utilities
const { _ } = global;
- 适当使用/
directUpdate:这些方法会绕过触发器,避免更新关联记录时出现无限循环directInsert - 提前验证输入:处理前检查参数和记录是否存在
input - 返回有意义的结果:始终返回包含和相关数据的对象
message - 使用throw处理错误:验证失败时使用——平台会返回相应的HTTP错误响应
throw new Error('message') - 访问用户上下文:使用和
ctx.userId进行权限检查ctx.getUser() - 从global中使用lodash:可获取lodash工具库
const { _ } = global;