mcpField
S
salla-app-functions-handler

salla-app-functions-handler

github.com/sallaapp开发278 次安装4 天前更新
代码仓库

关于 salla-app-functions-handler

用于编写 Salla App Function 处理器的编码技能,面向应用开发者。它要求依据触发器类型构建精简处理器,并将业务逻辑交给自有 API,同时说明响应契约和沙箱限制。

主要功能

  1. 01编码前读取触发器载荷类型
  2. 02将处理器设计为轻量 API 适配层
  3. 03遵循必需的响应契约
  4. 04遵守 V8 isolate 限制
  5. 05限制并验证出站 fetch 调用

适用场景

  1. 01实现 Salla 触发器处理器
  2. 02将 API 响应映射为触发器响应
  3. 03避免在函数中使用不支持的 Node.js API
  4. 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 约定的对象 (响应):

字段类型必填含义
successboolean是逻辑是否已完成?(同步:决定继续还是拒绝)
dataobject否要返回的负载 / 应用到实体的数据(同步操作)
errorstring否面向用户的错误信息;同步操作中 success:false 时必填
statusnumber否可选 HTTP 状态(通过构造器设置);默认为 200
messagestring否可选说明性消息(通过构造器设置)

两种返回方式——均有效:

// 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。

评论

支持 Markdown