object-functions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Steedos Object Functions | Steedos 对象函数

Steedos Object Functions | Steedos 对象函数

Overview | 概述

Overview | 概述

Object functions are server-side JavaScript functions defined as
.function.yml
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 端点暴露,从按钮、触发器或其他函数调用。

File Location | 文件位置

File Location | 文件位置

steedos-packages/
└── my-package/
    └── main/default/
        └── functions/
            ├── approve_order.function.yml
            ├── cancel_order.function.yml
            └── sync_to_erp.function.yml
steedos-packages/
└── my-package/
    └── main/default/
        └── functions/
            ├── approve_order.function.yml
            ├── cancel_order.function.yml
            └── sync_to_erp.function.yml

Function Structure | 函数结构

Function Structure | 函数结构

yaml
undefined
yaml
undefined

functions/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' };
undefined
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' };
undefined

Function Properties | 函数属性

Function Properties | 函数属性

PropertyTypeRequiredDescription
name
stringYes⚠️ MUST start with
{objectApiName}_
prefix, e.g.
orders_approve_order
objectApiName
stringYesAssociated object API name
description
stringNoHuman-readable description
isEnabled
booleanYesEnable/disable function
is_rest
booleanYesExpose as REST API endpoint
locked
booleanNoLock from editing
script
stringYesInline JavaScript code (YAML block scalar `
属性类型必填描述
name
string⚠️ 必须以
{objectApiName}_
前缀开头,例如
orders_approve_order
objectApiName
string关联对象的API名称
description
string易读的描述信息
isEnabled
boolean启用/禁用函数
is_rest
boolean是否作为REST API端点暴露
locked
boolean是否锁定禁止编辑
script
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 utilities
javascript
// 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
is_rest: true
, the function is accessible at:
POST /api/v6/functions/{objectApiName}/{shortFunctionName}
GET  /api/v6/functions/{objectApiName}/{shortFunctionName}
⚠️ The
objectApiName_
prefix MUST be removed from the function name in the URL.
Example: a function named
orders_approve_order
(objectApiName:
orders
) is called as:
POST /api/v6/functions/orders/approve_order
POST /api/v6/functions/leads/convert_lead
is_rest: true
时,函数可通过以下地址访问:
POST /api/v6/functions/{objectApiName}/{shortFunctionName}
GET  /api/v6/functions/{objectApiName}/{shortFunctionName}
⚠️ URL中的函数名称必须去掉
objectApiName_
前缀。
示例:名为
orders_approve_order
(objectApiName:
orders
)的函数调用地址为:
POST /api/v6/functions/orders/approve_order
POST /api/v6/functions/leads/convert_lead

Complete Examples | 完整示例

Complete Examples | 完整示例

Example 1: Simple Status Update | 简单状态更新

Example 1: Simple Status Update | 简单状态更新

yaml
undefined
yaml
undefined

functions/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' };
undefined
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' };
undefined

Example 2: Complex Business Logic | 复杂业务逻辑

Example 2: Complex Business Logic | 复杂业务逻辑

yaml
undefined
yaml
undefined

functions/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 };
undefined
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 };
undefined

Example 3: Soft Delete | 软删除

Example 3: Soft Delete | 软删除

yaml
undefined
yaml
undefined

functions/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' };
undefined
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' };
undefined

Example 4: External API Integration | 外部 API 集成

Example 4: External API Integration | 外部 API 集成

yaml
undefined
yaml
undefined

functions/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 };
undefined
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 };
undefined

Example 5: Batch Processing | 批量处理

Example 5: Batch Processing | 批量处理

yaml
undefined
yaml
undefined

functions/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:
Approved ${successCount} orders, ${failCount} failed
, successCount, failCount, errors };
undefined
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:
Approved ${successCount} orders, ${failCount} failed
, successCount, failCount, errors };
undefined

Calling Functions from Buttons | 从按钮调用函数

Calling Functions from Buttons | 从按钮调用函数

Functions with
is_rest: true
can be called from
amis_button
schemas:
yaml
undefined
is_rest: true
的函数可从
amis_button
schema中调用:
yaml
undefined

In 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" } } } ] } } } }
undefined
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" } } } ] } } } }
undefined

Best Practices | 最佳实践

Best Practices | 最佳实践

  1. Use
    directUpdate
    /
    directInsert
    when appropriate
    : These bypass triggers to avoid infinite loops when updating related records
  2. Validate input early: Check
    input
    parameters and record existence before processing
  3. Return meaningful results: Always return an object with a
    message
    and relevant data
  4. Handle errors with throw: Use
    throw new Error('message')
    for validation failures - the platform returns appropriate HTTP error responses
  5. Access user context: Use
    ctx.userId
    and
    ctx.getUser()
    for permission checks
  6. Use lodash from global:
    const { _ } = global;
    gives you lodash utilities
  1. 适当使用
    directUpdate
    /
    directInsert
    :这些方法会绕过触发器,避免更新关联记录时出现无限循环
  2. 提前验证输入:处理前检查
    input
    参数和记录是否存在
  3. 返回有意义的结果:始终返回包含
    message
    和相关数据的对象
  4. 使用throw处理错误:验证失败时使用
    throw new Error('message')
    ——平台会返回相应的HTTP错误响应
  5. 访问用户上下文:使用
    ctx.userId
    ctx.getUser()
    进行权限检查
  6. 从global中使用lodash
    const { _ } = global;
    可获取lodash工具库