关于 salla-app-functions-handler
用于编写 Salla App Function 处理器的编码技能,面向应用开发者。它要求依据触发器类型构建精简处理器,并将业务逻辑交给自有 API,同时说明响应契约和沙箱限制。
主要功能
- 01编码前读取触发器载荷类型
- 02将处理器设计为轻量 API 适配层
- 03遵循必需的响应契约
- 04遵守 V8 isolate 限制
- 05限制并验证出站 fetch 调用
适用场景
- 01实现 Salla 触发器处理器
- 02将 API 响应映射为触发器响应
- 03避免在函数中使用不支持的 Node.js API
- 04防范缓慢或格式异常的 API 响应
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-app-functions-handler安装后在 Claude Code 中输入/salla-app-functions-handler确认已加载。
详细介绍
App Functions — 编写处理器
从触发器的默认 template 和 types 开始——使用 salla_functions action=get、app_id、trigger 获取两者。原样复制 template,并将所有代码写在正文中。保持包装器的首行和末行完全不变(完整规则和本地类型检查 → salla-app-functions-validate)。
此技能假设你已在 salla-app-functions-design 中决定同步与异步模式(同步的 merchant_actions 触发器会阻止或修改操作;异步事件则会即发即弃)。
保持处理器精简——委托给你自己的 API
最佳实践:函数应是一个轻量的转换层,而非业务逻辑。 在你自己的后端 API 中完成实际工作,并仅使用 App Function 来:(1) 读取触发器负载,(2) 调用你的 API,(3) 将其响应转换为触发器所需的 Resp。沙盒是一个受限的 V8 隔离环境,时间预算严格(同步 5 s / 异步 30 s),且没有 npm——繁重逻辑、SDK、持久化和长时间运行的工作都应放在你的服务端,而不是这里。因此,同步操作(例如 shipment.creating)应执行:验证输入 → fetch 你的 API → 将结果映射为触发器的 Resp。保持精简简洁,既能满足时间预算,也便于根据触发器的类型进行类型检查。
1. 首先从获取的类型中读取负载字段名称(严格要求)
严格规则——适用于编写或修改任何 App Function。 在编写或更改负载上的任何属性访问之前,获取 salla_functions action=get(app_id、trigger)返回的 types 数组中的每个 URL,并从这些 .d.ts 类型定义中读取准确的字段名称和结构。处理器读取的类型化负载是 context.payload.data——所有属性访问都必须基于 .d.ts 实际声明的名称(以及其中引用的任何嵌套结构 / 枚举),而不是你预期的名称。对于运输 AWB 函数,这意味着要从其类型中读取实际的 Shipments 负载字段(例如,地址、包裹和重量字段在 .d.ts 中的名称),而不是假设它们叫 sender_address / receiver_address / weight。同一组 types URL 也会用于 salla-app-functions-validate 中的本地类型检查,如果猜测的字段不存在,构建就会失败;但先阅读这些类型,才能从一开始就正确编写函数正文。
检查点:“编写或修改正文之前,处理器从 context.payload.data 读取的每个字段都已根据重新获取的 types .d.ts 中实际声明的字段进行确认——没有任何猜测?”
export default async (context: Shipments): Promise<Resp> => {
// ↑ template's first line, unchanged ↓ ALL your code goes inside the body
console.log("Shipment cancelled event triggered");
return Resp.success().setData({});
};
上下文对象
示意结构——权威类型以 salla_functions action=get 返回的触发器 types .d.ts 为准;请根据这些类型进行检查(data 的结构因触发器而异)。
context.merchant; // { id: number | string, … }
context.payload; // { event, created_at, merchant, data: { … } } — data varies by trigger
context.settings; // per-merchant App Settings; optional (undefined/null until configured)
在函数内部调用 Salla Admin API 通常无需配置令牌——请根据触发器的 types / 函数上下文确认运行时注入的确切认证信息,然后直接调用 Admin API。为所调用的端点申请最低限度的 OAuth 作用域(作用域设置和令牌处理 → salla-app-auth);基础 URL、端点和错误结构 → salla-api-core。设置:context.settings?.apiKey(可选链——配置前为 undefined)。
响应约定(必须返回)
每个处理器都必须返回符合 Response 约定的对象
(响应):
| 字段 | 类型 | 必填 | 含义 |
|---|---|---|---|
success | boolean | 是 | 逻辑是否已完成?(同步:决定继续还是拒绝) |
data | object | 否 | 要返回的负载 / 应用到实体的数据(同步操作) |
error | string | 否 | 面向用户的错误信息;同步操作中 success:false 时必填 |
status | number | 否 | 可选 HTTP 状态(通过构造器设置);默认为 200 |
message | string | 否 | 可选说明性消息(通过构造器设置) |
两种返回方式——均有效:
// 1. Plain object — simplest, lowest overhead (good for sync actions, simple events)
return { success: true, data: {} }; // data is optional; include it (even {}) on sync actions
// 2. Response utility class — recommended for Customer Events and complex Merchant Events
return Resp.success().setData({ order_id: id }); // .setData({}) is the recommended convention
return Resp.error().setMessage("Something went wrong").setStatus(500);
构造器方法(可链式调用):Resp.success() / Resp.error() 用于开始构造响应;.setData(obj)
是可选的,但建议使用——即使数据为空也传入 {},尤其是在同步操作中,返回的 data 会合并到实体或应用到实体上;.setStatus(code) 是可选的(默认为
200);.setMessage(msg) 是可选的。Resp 是预先声明的运行时全局变量——可直接使用,无需导入。
以实体命名的构造器。 一些触发器会公开特定实体的构造器,而不是通用的
Resp——它们具有相同的 .success() / .error() 形式,并带有实体设置器。shipment.creating
必须使用 Shipment 类(上下文类型为 Shipments,返回类型为 Promise<Shipment>):
return Shipment.success().setShipmentNumber(id)(成功时始终调用 .setShipmentNumber()),return Shipment.error().setMessage("…")。使用前,请从触发器的
types 中确认准确的构造器和设置器(参见 salla-app-functions-validate)。
同步操作:返回值的作用
对于同步操作(例如 shipment.creating),商家会阻塞等待你的处理结果:
success: true→ 操作继续,你的data会合并到最终实体中或应用到该实体。success: false→ 操作会被拒绝,并立即向商家显示你的error/.setMessage()。请提供清晰且可执行的说明。
对于异步事件,返回值仅供参考——会记录下来,但不会影响已完成的操作。无论如何,每条执行路径都必须始终返回响应。
沙盒限制(V8 isolate — 非完整 Node.js)
App Functions 运行在 V8 isolate 中,而非 Node.js 运行时 (Node.js 支持)。原生 Node API 受到限制:
- 不支持外部包——无法导入 npm 包或外部库(计划在未来版本支持)。只能使用预先声明的全局变量和 Web 标准 API。
- 不支持的 Node 核心模块(使用时会报错):
fs、fs/promises、net、tls、dgram、http/https服务器、child_process、cluster、worker_threads、repl、readline、zlib。 - 部分支持:
crypto= 仅 Web Crypto API(globalThis.crypto);buffer= 基础支持;stream= 仅 WHATWG 流;os/path/util/events= 部分支持。 - 出站 HTTP → 使用标准
fetch()API(不支持http/https客户端模块,也不支持服务器)。这是访问外部 API 或 Salla API 的唯一支持方式。
为所有外部 fetch 设置超时上限
为每个 fetch 设置 AbortController,以免某个响应缓慢的上游耗尽预算。每次调用都应远低于触发器的预算——同步操作的目标是总耗时 < 500 ms,异步事件最多可用 30 s(请确认具体限制 → salla-app-functions-design)。应用响应前先验证并整理响应:提取所需的特定字段并且只返回这些字段,以免格式错误或恶意的上游响应正文流入实体。
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 2000); // < 2s per call in a sync action
try {
const res = await fetch("https://api.example.com/data", {
signal: controller.signal,
});
if (!res.ok) return Resp.error().setMessage("Upstream error").setStatus(502);
const body = await res.json();
// Validate the shape and extract only the fields you trust — don't return raw JSON.
if (typeof body?.tracking_number !== "string") {
return Resp.error().setMessage("Invalid upstream response").setStatus(502);
}
return Resp.success().setData({ tracking_number: body.tracking_number });
} catch (error) {
return Resp.error().setMessage("Upstream timeout").setStatus(504);
} finally {
clearTimeout(timeout);
}
出站调用安全:
- 应用前先验证 — 检查
res.ok,然后验证响应正文的结构,并且只返回所需的字段(见上文),以免错误正文或额外字段传入实体。 - 信任自己的端点 — 使用硬编码的 URL;如果 URL 来自设置,则在
fetch前将其与自己控制的主机允许列表进行匹配。 - 清理商家输入 —
context.settings由商家控制。验证类型和格式,并针对目标位置对每个值进行编码或转义(路径/查询参数编码、标头值),再将其放入出站请求。 - 避免在日志中记录机密信息 — 切勿记录
context.settings或令牌。
检查项:“所有路径都会返回 Resp.success().setData(...),不使用 npm/不支持的核心功能,每个 fetch 都有超时上限,响应在使用前经过验证和整理,设置已清理,且未记录机密信息?”
→ salla-app-functions-validate。