mcpField
B
building-nango-functions-locally

building-nango-functions-locally

github.com/nangohq开发3389 次安装10 周前更新
代码仓库

关于 building-nango-functions-locally

用于在已检出的 Nango 项目中构建 action 和 sync 的技能。它要求为 sync 设计适配服务商的检查点与删除策略,并使用项目本地的验证、测试和部署流程。

主要功能

  1. 01使用 createAction() 构建 action
  2. 02使用 createSync() 构建 sync
  3. 03规划检查点和删除处理
  4. 04验证服务商输入与数据模式
  5. 05使用本地编译、试运行、测试和部署流程

适用场景

  1. 01实现由用户触发的服务商 API action
  2. 02构建定时或 webhook 驱动的数据同步
  3. 03通过检查点恢复分页同步
  4. 04在本地验证并测试 Nango 函数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nangohq/skills --skill building-nango-functions-locally

安装后在 Claude Code 中输入/building-nango-functions-locally确认已加载。

附带文件references/actions.mdreferences/syncs.md

详细介绍

在本地构建 Nango 函数

在已检出的 Nango 项目中,使用本地 CLI 验证和测试工作流,构建可部署的 Nango 操作和同步。

实现范围

  • 构建或修改 Nango 函数实现
  • 在 Nango 中使用 createAction() 构建操作
  • 在 Nango 中使用 createSync() 构建同步
  • 使用当前工作流技能处理编译、dryrun、测试和部署机制

同步策略关卡(写代码前必须完成)

如果任务涉及同步,请在写代码前阅读 references/syncs.md,并首先说明以下其中一条路径:

  • 检查点方案:
    • 更改来源(updated_at、modified_since、变更记录端点、游标、页令牌、偏移量/页码、since_id 或 webhook)
    • 检查点架构
    • 检查点如何改变提供方请求或恢复状态
    • 请求仍会遍历完整数据集,还是只返回变更的行
    • 删除策略
  • 全量刷新阻碍因素:
    • 文档或示例载荷中的具体提供方限制
    • 检查点为何无法在此处发挥作用

无效的同步实现:

  • 因为更简单而采用全量刷新
  • 使用 saveCheckpoint() 却不使用 getCheckpoint()
  • 读取或保存检查点,却不在请求参数或分页状态中使用它
  • 在新同步中使用 syncType: 'incremental' 或 nango.lastSyncDate
  • 全量刷新没有 checkpoint 架构,或未在每一页后保存检查点——如果运行超出执行时间窗口,就会从第 1 页重新开始
  • 将 saveCheckpoint() 放在仅在还有更多页面时才执行的条件中(例如 if (nextCursor) { await nango.saveCheckpoint(...) })。每个成功页面都必须调用 saveCheckpoint(),包括最后一页;否则,单页同步会在没有检查点记录的情况下执行 clearCheckpoint(),并失败并报错 checkpoint_conflict。
  • 在 clearCheckpoint() 之前调用 trackDeletesEnd(),或完全没有先调用 clearCheckpoint()
  • 对仅返回变更内容的检查点(modified_after、updated_after、变更记录端点)使用 trackDeletesStart() / trackDeletesEnd()。这些请求会省略未变更的行,因此 trackDeletesEnd() 会误删这些记录。
  • 在已有明确删除记录事件的增量同步中使用 trackDeletesStart() / trackDeletesEnd()

选择路径

操作:

  • 使用 createAction() 构建的一次性、由用户触发的请求
  • 写代码前阅读 references/actions.md

同步:

  • 使用 createSync() 构建的定时或 webhook 驱动的缓存更新
  • 首先完成同步策略关卡
  • 写代码前阅读 references/syncs.md

必需输入(缺少时询问用户)

始终需要:

  • 集成 ID(提供方名称)
  • 脚本/函数名称(kebab-case)
  • API 参考 URL 或示例响应
  • 如果当前工作流将验证或 dryrun 该函数,则需要连接 ID

操作专属:

  • 用例摘要
  • 输入参数
  • 输出字段
  • 如有需要,提供元数据 JSON
  • 如果当前工作流将验证或 dryrun 该操作,则需要测试输入 JSON(无输入操作请使用 {})

同步专属:

  • 模型名称(单数、PascalCase)
  • 频率(每小时、每 5 分钟等)
  • 检查点架构(时间戳、游标、页令牌、偏移量/页码、since_id 或复合类型)
  • 检查点如何改变提供方请求或恢复状态
  • 删除策略(已删除记录端点/webhook,或说明为何必须全量刷新)
  • 如果建议全量刷新,需提供文档/示例响应中阻碍使用检查点的具体提供方限制
  • 如有需要,提供元数据 JSON(team_id、workspace_id)

如果缺少任何必需的外部值,请先检查仓库和提供方文档,然后提出有针对性的问题。对于同步,只要提供方支持,就选择检查点和删除策略。如果找不到可行的检查点策略,请在编写全量刷新代码前准确说明原因。

不可妥协的规则

共享平台约束
  • Nango 函数使用 createAction() / createSync()。
  • 不能添加任意软件包。仅当所选工作流支持时,才使用相对导入;内置模块包括 zod、crypto/node:crypto 和 url/node:url。
  • 在函数代码之外,使用 Nango HTTP API 查询连接、获取凭据和调用代理。不要臆造 CLI 令牌或连接命令。
  • 在每个服务提供商调用上方添加 API 文档链接注释。
  • Action 输出不得超过 2MB。
  • 文件上传和下载无法通过 action 实现(沙盒运行时:没有 fs、没有 axios,输出上限为 2MB)。请改用 {integration}/proxy/ 中的代理脚本,并使用 @nangohq/node——参见 references/actions.md。
  • HTTP 重试默认值为 0;请有意设置 retries。通常最多设为 3;对于 sync 的服务提供商调用,除非文档证明更高值安全且必要,否则实际上禁止设置超过 3。除非 API 支持幂等性,否则避免对非幂等写入进行重试。
  • 不要设置已弃用的函数定义路由字段:action 的 endpoint 和 sync 的 endpoints。通过 SDK/API 按 action 名称触发 action,并通过 records API 获取 sync 记录。
Sync 规则
  • Sync 记录需要稳定的字符串 id。
  • 新建 sync 时应定义 checkpoint 架构,先调用 nango.getCheckpoint(),并在每个页面或批次处理后调用 nango.saveCheckpoint()。
  • 只有在 checkpoint 会改变请求或恢复状态(since、updated_after、cursor、page_token、offset、page、since_id 等)时,它才有效。不使用 checkpoint 却保存 checkpoint,不算增量同步。
  • 新建 sync 不得使用 syncType: 'incremental' 或 nango.lastSyncDate。
  • 默认使用 nango.paginate(...) + nango.batchSave(...)。当 cursor、link 或 offset 分页适用时,避免手动编写 while (true) 循环。
  • 服务提供商返回删除记录、墓碑记录或删除 webhook 时,优先使用 batchDelete()。
  • 只有在服务提供商无法返回变更、删除或恢复状态,或者数据集很小时,才使用全量刷新。
  • 使用全量刷新时,请引用文档或负载中的具体服务提供商限制。“这样更简单”并不足以作为理由。
  • 全量刷新 sync 也需要定义 checkpoint 架构(涵盖页面/游标/偏移量),以记录分页进度,而不仅仅是增量 sync 需要定义。Nango sync 在有时间限制的执行窗口内运行;如果全量刷新没有 checkpoint,那么每次运行超出执行窗口时都会从第 1 页重新开始,浪费计算资源重复获取相同的前几页,并且永远无法到达其余页面。
  • deleteRecordsFromPreviousExecutions() 已弃用。全量刷新时,每次执行都要调用 trackDeletesStart()(安全且幂等——不会覆盖已打开窗口的开始位置),然后在每页处理后调用 saveCheckpoint(),最后一页处理完后调用 clearCheckpoint(),并且仅在 clearCheckpoint() 之后调用 trackDeletesEnd()。
  • 全量刷新中,每次成功处理页面后都要调用 saveCheckpoint(),包括最后一页,并且必须在调用 clearCheckpoint() 之前执行。绝不要以“还有更多页面”为条件来决定是否保存。如果某条独立执行路径完全没有创建 checkpoint(例如没有处理任何页面),则不要在该路径调用 clearCheckpoint();这会在运行时抛出 checkpoint_conflict。此例外不能代替保存终止页面的 checkpoint。
  • 绝不要将 trackDeletesStart() / trackDeletesEnd() 与仅包含变更数据的 checkpoint(modified_after、updated_after、仅返回变更记录的端点等)结合使用。它们会省略未变更的行,因此 trackDeletesEnd() 会将这些行删除。
  • 使用 checkpoint 的全量刷新仍然是全量刷新。仅在完成并清除 checkpoint 的那次运行中调用 trackDeletesEnd()。
  • 如果 sync 需要元数据(例如 team_id、workspace_id、guild_id),请设置 autoStart: false。调用方设置元数据前,sync 无法运行,因此自动启动会导致失败。
约定
  • 字段大小写应与外部 API 保持一致。透传字段保留服务提供方的大小写;非透传字段使用该 API 中占多数的大小写形式。
  • 优先使用明确的字段名。
  • 为 ID、时间戳、枚举值和 URL 添加 .describe() 示例。
  • 避免使用 any;改用内联映射类型。
  • 列表操作应提供 cursor,以及采用该 API 中占多数的大小写形式的下一游标字段(next_cursor、nextCursor 等)。
  • 仅在需要自定义验证或日志记录时使用 nango.zodValidateInput();否则,依赖架构和所选的验证工作流。
架构语义
  • 默认将非必填输入设为 .optional()。
  • 仅在 null 有实际含义时使用 .nullable(),通常用于清除更新;如果调用方也可以省略该字段,则同时添加 .optional()。
  • 原始服务提供方架构应与服务提供方保持一致:对于省略的字段使用 .optional(),对于显式的 null 使用 .nullable(),只有当服务提供方确实两种情况都有时才使用 .nullish()。
  • 最终操作输出和规范化后的同步模型应优先使用 .optional();除非 null 有实际意义,否则将上游的 null 规范化为省略。
  • 对于非必填输入和规范化输出,生成的架构默认使用 .optional();只有上游契约有充分依据时才放宽限制。
  • 优先使用 .nullable(),而不是 z.union([z.null(), T]) 或 z.union([T, z.null()])。
  • 仅当输出架构允许时才返回 null。
  • z.object() 默认会剥离未知键。对于服务提供方透传,可使用 z.object({}).passthrough()、z.record(z.unknown()) 或仅带最少精化的 z.unknown()。
字段命名和大小写规则
  • 使用符合 API 中占多数的大小写形式的明确后缀:ID(user_id、userId)、名称(channel_name、channelName)、电子邮件(user_email、userEmail)、URL(callback_url、callbackUrl)和时间戳(created_at、createdAt)。

映射示例(API 要求使用不同的参数名):

const InputSchema = z.object({
    userId: z.string()
});

const config: ProxyConfiguration = {
    endpoint: 'users.info',
    params: {
        user: input.userId
    },
    retries: 3
};

如果 API 使用 snake_case,则改用 user_id。目标是与 API 保持一致。

参考资料

  • 操作模式、CRUD 示例、元数据用法和 ActionError 示例:references/actions.md
  • 同步模式、具体检查点示例、删除策略和完整刷新回退方案:references/syncs.md

有用的 Nango 文档(快速链接)

API 文档无法呈现时

如果网页抓取返回的文档不完整(由 JS 渲染):

  • 请用户提供一个示例响应
  • 如果工作区中已有 Nango 操作或同步,请参考其模式
  • 使用技能专属的验证或 dryrun 工作流,直至通过

工作流(必需)

  1. 判断这是一个操作还是一次同步。
  2. 阅读对应的参考文件:references/actions.md 或 references/syncs.md。
  3. 对于同步,检查提供方文档或有效负载中的检查点和删除信息,判断端点返回的是完整数据还是变更的行,并完成 Sync Strategy Gate。
  4. 收集必需的输入和外部值。查询连接、凭据或发现信息时,使用 Nango HTTP API。
  5. 确认这是一个 Zero YAML TypeScript 项目(没有 nango.yaml),且当前位于 Nango 根目录(存在 .nango/)。
  6. 在 {integrationId}/actions/ 或 {integrationId}/syncs/ 下创建或更新函数,应用共享的架构和大小写规则,然后在 index.ts 中注册。
  7. 使用 nango dryrun ... --validate -e dev --no-interactive --auto-confirm 进行验证。
  8. 如果验证无法通过,请停止并报告缺少的外部状态或输入。
  9. 验证通过后,运行 nango dryrun ... --save,然后运行 nango generate:tests,再运行 npm test。
  10. 仅在用户要求时,使用 nango deploy dev 部署。

前置条件(编写代码前执行)

确认 TypeScript 项目(没有 nango.yaml)

此技能仅支持使用 createAction() / createSync() 的 TypeScript 项目。

ls nango.yaml 2>/dev/null && echo "YAML PROJECT DETECTED" || echo "OK - No nango.yaml"

如果看到 YAML PROJECT DETECTED:

  • 立即停止。
  • 告知用户先升级到 TypeScript 格式。
  • 不要尝试混用 YAML 和 TypeScript。

参考:https://nango.dev/docs/implementation-guides/platform/migrations/migrate-to-zero-yaml

验证 Nango 项目根目录

确认 Nango 根目录前,不要创建文件:

ls -la .nango/ 2>/dev/null && pwd && echo "IN NANGO PROJECT ROOT" || echo "NOT in Nango root"

如果看到 NOT in Nango root:

  • cd 到包含 .nango/ 的目录
  • 重新运行检查
  • 不要使用绝对路径来规避问题

所有文件路径都必须相对于 Nango 根目录。在 Nango 根目录中创建文件时,如果额外添加路径前缀,会创建嵌套目录并导致构建失败。

项目结构和命名

./
|-- .nango/
|-- index.ts
|-- hubspot/
|   |-- actions/
|   |   `-- create-contact.ts
|   `-- syncs/
|       `-- fetch-contacts.ts
`-- slack/
    `-- actions/
        `-- post-message.ts
  • 提供方目录:小写(hubspot、slack)
  • 操作文件:kebab-case(create-contact.ts)
  • 同步文件:kebab-case(许多团队使用 fetch- 前缀,但它是可选的)
  • 每个文件只包含一个函数
  • 所有操作和同步都必须在 index.ts 中导入
在 index.ts 中注册脚本(必需)

仅使用副作用导入。包含 .js 扩展名。

// index.ts
import './github/actions/get-top-contributor.js';
import './github/syncs/fetch-issues.js';

注册不正确的表现:文件可以编译,但会看到 No entry points found in index.ts...,或者函数始终不会出现。

Dryrun、模拟和测试(必需)

必需的循环:

  1. 反复运行 nango dryrun ... --validate -e dev --no-interactive --auto-confirm,直到验证通过。
  2. 操作:始终传入 --input '{...}'(无输入操作使用 --input '{}')。
  3. 同步:需要模拟恢复运行时,使用 --checkpoint '{...}'。
  4. 如果验证无法通过,请停止并说明所需但缺少的外部状态或输入。
  5. 验证通过后,运行 nango dryrun ... --save -e dev --no-interactive --auto-confirm,生成 <script-name>.test.json。
  6. 运行 nango generate:tests,然后运行 npm test。

示例:

# Validate an action
nango dryrun <action-name> <connection-id> --validate -e dev --no-interactive --auto-confirm --input '{"key":"value"}'

# Validate a no-input action
nango dryrun <action-name> <connection-id> --validate -e dev --no-interactive --auto-confirm --input '{}'

# Validate a sync
nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirm

# Validate a resumed sync with a checkpoint
nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirm --checkpoint '{"updated_after":"2024-01-15T00:00:00Z"}'

# Record action mocks after validation passes
nango dryrun <action-name> <connection-id> --save -e dev --no-interactive --auto-confirm --input '{"key":"value"}'

# Record sync mocks after validation passes
nango dryrun <sync-name> <connection-id> --save -e dev --no-interactive --auto-confirm

# Stub metadata when needed
nango dryrun <script-name> <connection-id> --validate -e dev --no-interactive --auto-confirm --metadata '{"team_id":"123"}'

硬性规则:

  • 将 <script-name>.test.json 视为生成的输出。绝不要创建、编辑、重命名或移动它。
  • 如果模拟数据有误或已过时,请修正代码并使用 --save 重新录制。
  • 不要在 *.test.json 中硬编码错误有效负载;对于 404、401、429 或超时情况,请使用带 vi.spyOn(...) 的 Vitest 测试。
  • 连接 ID 是第二个位置参数;不要使用 --connection-id。
  • 当多个集成中的脚本名称重叠时,使用 --integration-id <integration-id>。
  • 对于新的增量同步,优先使用 --checkpoint;--lastSyncDate 是旧模式。
  • 如果 nango 不在 PATH 中,请使用 npx nango ...。
  • CLI 升级提示可能会阻塞自动化;必要时设置 NANGO_CLI_UPGRADE_MODE=ignore。

参考:https://nango.dev/docs/implementation-guides/platform/functions/testing

部署(可选)

将函数部署到 Nango 账户中的某个环境:

nango deploy dev

# Deploy only one function
nango deploy --action <action-name> dev
nango deploy --sync <sync-name> dev

参考:https://nango.dev/docs/implementation-guides/use-cases/actions/implement-an-action

最终检查清单

操作:

  • 已验证 Nango 根目录
  • 已参考 references/actions.md 中的操作模式
  • 架构和类型清晰,缺失值规则符合服务商与标准化契约的要求
  • 在必要时,createAction() 包含输入、输出和作用域;省略已弃用的 endpoint
  • 字段采用透传大小写形式或 API 的主流大小写形式
  • 服务商调用包含 API 文档链接注释,并设置了有意为之的重试
  • 预期的失败情况使用 nango.ActionError
  • 已在 index.ts 中注册
  • 使用 --validate -e dev --no-interactive --auto-confirm --input '{...}' 运行 Dryrun 成功
  • 在 --validate 之后,通过 nango dryrun ... --save 生成了 <action-name>.test.json
  • 已运行 nango generate:tests,且 npm test 通过

同步:

  • 已验证 Nango 根目录
  • 已参考 references/syncs.md 中的同步模式
  • 已定义模型映射表,ID 为稳定字符串;除非 null 值有特殊意义,否则标准化模型优先使用 .optional()
  • 优先选择增量同步,并设置检查点架构;只有文档或负载明确说明合理时,才采用全量刷新
  • 在开头读取 nango.getCheckpoint(),并在每页或每批次后调用 nango.saveCheckpoint()
  • 检查点数据会改变服务商请求或恢复状态(since、updated_after、cursor、page_token、offset、page、since_id 等)
  • 仅同步变更数据的检查点同步(modified_after、updated_after、变更记录端点)不使用 trackDeletesStart() / trackDeletesEnd()
  • 如果未使用检查点,响应会准确说明为什么不存在可行的检查点策略
  • 原始服务商架构正确区分省略值与 null,字段采用透传大小写形式或 API 的主流大小写形式
  • 除非 API 确实无法使用 Nango 的分页器,否则使用 nango.paginate()
  • 服务商 API 调用使用 retries: 3;除非有记录在案的例外,否则同步重试值不超过 3
  • 删除策略与同步类型匹配:仅当服务商返回明确的删除记录时,增量同步才使用 batchDelete();否则,全量刷新回退方案会在获取/保存数据前调用 trackDeletesStart(),并且仅在成功完成全量获取和保存后调用 trackDeletesEnd()
  • 全量刷新同步具有 checkpoint 架构,会从中恢复分页,并在每页后调用 saveCheckpoint(),以免执行时限超时后从第 1 页重新开始
  • 仅在调用 clearCheckpoint() 后,且在完成最后一页的运行中,才执行全量刷新的 trackDeletesEnd()
  • 如有需要,已处理元数据
  • 已在 index.ts 中注册
  • 使用 --validate -e dev --no-interactive --auto-confirm 运行 Dryrun 成功
  • 在 --validate 之后,通过 nango dryrun ... --save 生成了 <sync-name>.test.json
  • 已运行 nango generate:tests,且 npm test 通过

评论

支持 Markdown