首页
/ Puter API 贡献指南:从 Controller 与 Driver 到 puter.js 的七步落地法

Puter API 贡献指南:从 Controller 与 Driver 到 puter.js 的七步落地法

2026-09-05 14:57:38作者:廉皓灿Ida

本文为 Puter 官方 API 贡献文档(doc/contributing-apis.md)的扩展解读,面向希望在 Puter 中新增或维护一个「公共 API」的贡献者与 AI Agent。读完本篇,你将掌握:如何判断 API 该放进核心还是扩展层、Controller 与 Driver 两种入口的选型与注册方式、各类鉴权与计费门禁(requireAuth / requireSubscription / requireReputation / requireCredits 等)的真实语义,以及覆盖后端、SDK、类型、文档、测试与安全检查的完整交付清单。

Puter 的「公共 API」指一切可被外部应用调用的表面:HTTP 端点、/drivers/* RPC 方法,以及包裹它们的 puter.js 方法。由于 puter.js 以无版本钉扎的方式从固定线上地址长期服务(文档原文表述为从 https://js.puter.com/v2/ 提供服务且不做版本固定),每一个既有应用都会在你部署的瞬间拿到你的改动——这决定了整篇指南的第一原则。

规则零:不要破坏调用方

puter.js 和它底下的后端端点都具备同一性质:任何可观察行为(参数处理、响应字段、错误码、排序顺序)都可能已经被某个现网应用依赖。因此:

  • 所有改动默认必须向后兼容,除非维护者事先明确同意了破坏性变更;
  • 响应字段一经发布即为永久承诺——只返回调用方需要的内容,不多不少;
  • 错误码使用稳定的 snake_case 命名。

配套文档:doc/architecture.md(后端分层架构)、doc/pagination.md(列表 API 的分页约定)、src/puter-js/tests/api/README.md(SDK 测试环境说明)。

核心还是扩展:API 的归属决策

第一个决策是 API 放在哪里。官方判据非常清晰:如果核心功能从不调用它,优先做成扩展(extension)而不是接入核心

扩展位于 extensions/ 目录,与核心分层栈平行,通过 extension.import(...) 惰性访问核心实例,并通过 registerDriver / registerController 或轻量路由助手注册自己的表面:

const services = extension.import('service');
const stores = extension.import('store');

extension.registerDriver('myFeature', MyFeatureDriver);  // 一等 driver
extension.post('/my-feature/frobnicate', opts, handler); // 或者普通路由

扩展内部同样遵循 driver/controller → service → store 的分层结构;除非它真的只需要两三个路由处理器——那时轻量的 extension.get/post/... 助手就够了。这些路由助手接受与核心 Controller 完全相同的 RouteOptions,所以 subdomainrequireAuthadminOnly、body parser 等都能照常工作(参见 doc/architecture.md 的 Extensions 一节)。

依赖方向测试:把扩展移除后 Puter 必须仍然能正常工作。一旦核心开始需要调用你的 API,它就该进核心。extensions/whoami.ts 是文档点名的反面教材——一个对所有已认证客户端「承重」的「扩展」,架构文档把它列为决策时要记住的警示案例;而 extensions/thumbnails.tsextensions/serverInfo.tsextensions/devWatcher.ts 则是「非关键、可整体移除」的正面范例。

新增 API 的七步法

官方流程要求七步全部走完,PR 才算完整。

第 1 步:先设计表面

写代码之前,先画出方法签名、选项、返回形态与错误分支,并找到两三个最相似的既有 API,对齐它们的约定:

  • 新的参数与字段名一律 camelCase(既有的 snake_case 名称保持原位不动);
  • 任何返回列表的 API 遵循 doc/pagination.md 的约定:入参 limit/cursor,出参 { items, cursor, total } 信封。该文档还规定:cursor 是不透明 base64 JSON、仅由后端在 src/backend/util/pagination.ts 中产生与消费;请求未带任何分页参数时,端点必须永远返回旧式的裸数组全量结果,保证老客户端不坏;
  • 优先使用 options 对象而非不断增长的位置参数,但在兄弟 API 已有惯例时,保留「单参数快捷调用」形态。

第 2 步:后端实现

按分层栈(doc/architecture.md)实现:边缘层是 controller 或 driver,业务逻辑在 service,持久化在 store。边缘层负责解析与校验输入、应用各类门禁;service 假定调用方已获授权。

Controller 还是 Driver?

两者都是被支持的一等做法,选型依据是控制粒度:

维度 Controller Driver
适用场景 需要精细控制 URL 形状、HTTP 动词、逐路由门禁、响应与流式格式 API 是一组实现命名接口(puter-kvstoreputer-chat-completion…)的 RPC 方法,通用的单一调用信封、可互换实现、逐方法策略机制已够用
基类 / 注册 继承 PuterControllersrc/backend/controllers/types.ts),核心注册于 src/backend/controllers/index.ts 继承 PuterDriversrc/backend/drivers/types.ts),核心注册于 src/backend/drivers/index.ts
声明方式 @Controller(prefix) 类装饰器 + @Get/@Post/… 方法装饰器(src/backend/core/http/decorators.ts),或直接覆写 registerRoutes(router) 命令式注册 @Driver(interfaceName, opts) 装饰器(src/backend/drivers/decorators.ts),或在类上命令式声明 driverInterface / driverName / isDefault
扩展接入 extension.registerController(...) 或普通路由助手 extension.registerDriver(...)

从源码看,@Controller 装饰器(src/backend/core/http/decorators.ts)把路径前缀存到原型元数据上,并为未自定义 registerRoutes 的类安装一个默认遍历器,把方法装饰器收集到的路由逐条喂给 PuterRouter——所以「纯装饰器、无方法体」的 Controller 可以完全不加命令式代码。@Driver 装饰器(src/backend/drivers/decorators.ts)则在类初始化阶段就把 rateLimitconcurrentrequireSubscriptionrequireReputation 等策略块做急切校验(eager validation),使配置错误在模块加载时就暴露,而不是等第一个请求打到路由才报错。同一接口允许多个实现并存(例如多个 puter-chat-completion 实现),default 标记缺省实现。

中间件与门禁

  • Controller 路由(扩展路由同形)接受 RouteOptions:鉴权门禁(requireAuthrequireUserActornoUserSessionadminOnlyallowedAppIds 及 access-token 控制)、subdomain 路由、逐路由 rateLimit、body parser 以及任意附加 middleware。这些鉴权字段语义微妙且默认拒绝,选择前先读每个字段的 JSDoc。
  • Driver 方法的策略来自 @Driver 选项:逐方法 rateLimit(limit / window / backend)、concurrent 在途并发上限(可 bySubscription 按订阅档缩放)、requireSubscriptionrequireReputationnoUserSession,由 /drivers/call 表面统一执行。
  • 只允许付费账户到达的表面声明 requireSubscription——Controller 上是路由选项,Driver 上是逐方法 @Driver({ requireSubscription }) 块(/drivers/call 是共享单路由,驱动的要求无法写在路由选项里)。取值语义:true 接受任何非免费计划(扩展注册的计划也算,核心无需点名);字符串数组如 ['business', 'pro'] 只接受所列计划;false 等价于不声明;空数组是启动错误而非静默失效——因为它读起来像「仅订阅者」却实际放行所有人。默认关闭,判定来自计量服务缓存的逐 actor 订阅,成本只是一次 map 查找。它与 requireCredits 的区别:前者问的是账户处于哪个计划,后者问的是预算还剩多少。没有任何付费计划的部署(自托管)可用 meteringEnforcement.subscriptions: false 整体关闭。
  • 只允许信任度足够账户到达的表面声明 requireReputation(同样是路由选项或 @Driver 块)。取值只命名一个层级(tier);该层级对应多少分由配置项 reputationGate.tiers 决定,因此门槛可以随部署调参而不必改表面代码。当前配置未定义的层级惰性失效(人人放行)——一个从不给账户计分的安装不应拿它从未计算的分数挡流量;reputationGate.enabled: false 可一次性停掉所有已声明的门禁。该机制是选入式、隐含 requireAuth,拒绝时返回裸 403 reputation_required,不透露分数与层级。文档还指出:目前代码树中尚无任何地方真正声明它——机制先于启用落地。
  • 账户必须已验证特定因子——requireVerified(邮箱,且仅在 strict_email_verification_required 下)、requirePhoneVerifiedrequireCardVerified(见 src/backend/core/http/types.ts)。这些是选入式的,且要求因子确实已完成验证,区别于默认开启的那道门禁——后者只挡掉「仍挂着滥用工具申请触发的待验证状态」的账户。
  • 替调用方消耗计量资源的端点——搬运文件内容、发起对象存储请求等任何账户会被计费的动作——应声明 requireCredits: true,在 handler 运行前就用 402 挡掉预算耗尽的账户。只描述或删除资源的端点则刻意不声明:预算耗尽的账户仍要能看到自己有什么、能清理、能进账单页。Driver 没有路由选项可写这项,因此自行调用 assertActorHasCreditssrc/backend/services/metering/enforcement.ts)——src/backend/drivers/kv/KVStoreDriver.ts 就是每个方法统一执行一次的范例。

第 3 步:puter.js

  • src/puter-js/src/modules/ 对应模块中添加方法,对齐同模块兄弟方法的调用约定:返回 promise;局部惯用模式是「位置参数快捷形式 + options 形式」双轨。
  • 廉价前置条件在客户端就地校验并抛出 { message, code } 对象;后端错误原样透传,不要吞掉或重新包装。

第 4 步:类型

  • 在实现处用 JSDoc 描述方法:@param/@returns 写实现,每种被接受的调用形态一个 @overload 块,新形态用 @typedef {Object} + @property。JSDoc 是唯一事实源——声明文件由它生成,无需手工同步。
  • 多个文件需要的形态放进模块的 types.js(如 src/puter-js/src/modules/kv/types.js);跨模块共享的放 src/puter-js/src/lib/types.js;只有一个消费者的形态可以留在原地。
  • 运行 npm run check:puterjs:types——它生成声明并在不启用 skipLibCheck 的情况下对发布面做类型检查。永远不要手编 src/puter-js/types/ 下的任何文件:那是 gitignore 的构建产物,随 SDK 构建生成、打进 npm 包,下次构建即被覆盖。
  • 若消费者应能直接 import 新类型,在 src/puter-js/index.d.ts 中命名它。该文件是包内唯一手写的声明文件:它决定什么是公开的,不转导出其他任何东西。

第 5 步:文档

src/docs/src/<Area>/<method>.md 添加方法页——frontmatter(titledescriptionplatforms)、语法、参数、返回值,以及至少一个可运行示例——并更新所属区域总览页(<Area>.md)。直接复制既有页面的结构即可,例如 src/docs/src/KV/add.md 这样的叶子页面与其所在区域。

第 6 步:测试

  • 后端:与源码同置的 Vitest 测试;优先使用内存测试服务器(src/backend/testUtil.ts 中的 setupPuterTestEnv)而不是打 mock。
  • SDK:把用例加进 src/puter-js/tests/api/suites/<area>.suite.ts(新套件需在 suites/index.ts 注册)。一个套件同时跑在 node、浏览器与 workerd 三个平台(npm run test:puterjs)——永远不要写按平台分叉的测试;且 runner 执行的是构建产物,需先 npm run build:workerLib 重新构建。仓库现存的套件覆盖 ai、apps、auth、components、events、fs、hosting、kv、net、os、perms、sharing、system、util、workers 等区域。
  • 桌面渲染 UIputer.ui.*):按 src/puter-js/TESTING.md 为每个功能补 Playwright spec。

第 7 步:安全检查

开 PR 前逐行扫 diff:错误与日志不泄漏内部实现、响应不过度宽泛、鉴权门禁齐备。凡涉及鉴权、权限或数据导出的改动,在 PR 描述中显式标注。

维护既有 API

变更默认是增量的

  • 新参数必须可选,且默认值精确复现旧行为;
  • 永不重命名、改用途或移除既有参数、响应字段、错误码;不改类型、顺序保证、以及「字段何时出现」的规则;
  • 可能让既有调用方意外的新行为,放到选入式开关(opt-in flag)后面;
  • 文档、类型、测试与行为变更同一个 PR 提交——签名变了文档没变就是 bug,文档是用户据以编码的契约;
  • bug 修复必须附带一个「修复前会失败」的回归测试。对「改变可观察行为」的修复保持警惕:可能有人依赖这个 bug,拿不准就问维护者。

破坏性变更稀少且刻意,顺序固定:维护者明确签核 → 文档化的迁移路径 → 上线计划(通常是先加新表面,再废弃旧表面,旧表面很久以后才移除,甚至永不移除)。重构的副产物绝不允许构成破坏。

废弃:旧表面继续可用。在类型声明中标 @deprecated,在其文档页注明替代项,并在示例中停止使用它。移除是另一个独立的、需维护者批准的决定。

完成定义(Definition of Done)

对照官方清单逐项核验,全部勾掉才算完成:

  • [ ] 向后兼容(或破坏已被明确批准)
  • [ ] 归属正确:核心从不调用它则进扩展;无论放哪都遵守分层结构
  • [ ] puter.js 方法符合兄弟方法约定;错误是 { message, code } 且 code 稳定
  • [ ] 类型已更新且与运行时行为一致
  • [ ] 文档页与区域总览已更新,且含可运行示例
  • [ ] 测试齐备:后端 + 三平台 SDK 套件(桌面渲染 UI 另有 e2e)
  • [ ] 已对 diff 做完安全检查
  • [ ] 已端到端实际跑过一遍
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384