@tereo-io/sdk

中文文档
0.5.0发布于 2026/11/02Apache-2.0TypeScriptES Module构建来源已验证

一个SDK,让应用服务通过一个@dianemo/core请求处理程序到达Tereo: auth、files、webhooks和audit

中文使用文档
文档目录6 节
  1. 发送通知
  2. createCoreClient(options)
  3. 如何连接
  4. 布局
  5. 状态
  6. 命令

@ tereo-io/sdk

SDK应用服务用于访问 tereo: auth、files、webhooks和 审核一组凭据- client_id 和 client_secret 一个管理员从 POST /v1/admin/services.

npm install @tereo-io/sdk

它只是ESM,在Node.js 22上构建和测试,并且需要一个 ioredis 客户端(redis,下方)。

import { createCoreClient, requestIdMiddleware } from "@tereo-io/sdk";
import { Redis } from "ioredis";

const core = createCoreClient({
  service: "product-service",          // its slug, as core knows it
  url: process.env.CORE_URL,           // these four default to the env names shown
  clientId: process.env.CORE_CLIENT_ID,
  clientSecret: process.env.CORE_CLIENT_SECRET,
  requestHandlerKey: process.env.REQUEST_HANDLER_KEY,
  redis: new Redis(process.env.REDIS_URL!, { maxRetriesPerRequest: null }),
  outbox: { db: prisma },              // optional: the transactional outbox
});
await core.start();

// What this service owns, and the access it needs, is not sent at boot: the build writes a
// provisioning document (see docs/provisioning.md) and the deploy pipeline hands it to core.

app.use(requestIdMiddleware());
app.use(core.auth.requireAuth);

await core.files.uploadObject("product-images", "a.png", { file, contentType: "image/png" });

// Audit entries and events that must not be lost: in the same transaction as the change.
await prisma.$transaction(async (tx) => {
  const item = await tx.item.create({ data });
  await core.outbox.audit(tx, { userId, action: "CREATE", entityType: "Item", entityId: item.id });
  await core.outbox.emit(tx, { organizationId, eventType: "product.item.created", entityType: "Item", entityId: item.id, data: item });
});

await core.stop();

发送通知

Core通过一个管理员配置的连接发送消息(首先是电子邮件)。要求 服务的预配文档中的发件人角色 (access.roles); 在它被应用之前,请求会被403拒绝:

// provisioning document: service.access.roles includes "notification_sender"

// Direct: throws on failure; an idempotencyKey is generated when you give none.
const { id } = await core.notifications.send({
  channel: "email",
  recipients: [{ address: "[email protected]" }, { userId, as: "cc" }],
  content: { subject: "Order shipped", text: "…", html: "…" },
});
const sent = await core.notifications.get(id); // status and deliveries

// Not to be lost: in the transaction that makes the change; the reaper sends it after commit.
await prisma.$transaction(async (tx) => {
  await tx.order.update({ where: { id: orderId }, data: { shippedAt: new Date() } });
  await core.outbox.notify(tx, { channel: "email", recipients, content });
});

notify 拒绝未知频道、少于1个或多于50个收件人,或拒绝不包含 to 收件人 (一个没有 as 算得上 to) 立刻。核心 当reaper转发该行时,验证其余部分(允许的发送者、用户、附件);4 xx 除了401,403,408,409或429死信。

createCoreClient(options)

每个进程一个——第二个调用会被抛出,直到第一个客户端停止。

选项默认的
service(需要)服务很糟糕。这个 source 其审计条目,以及审计读取的范围为
urlCORE_URL核心的基URL
clientId / clientSecretCORE_CLIENT_ID / CORE_CLIENT_SECRET从 添加服务 在核心
requestHandlerKeyREQUEST_HANDLER_KEY加密请求处理程序存储的内容; 32个字符
redis(需要)一个具有 maxRetriesPerRequest: null. 归来电者所有
productionNODE_ENV === "production"生产处理程序优先于其他共享Redis的处理程序
auth{}publicRoutes, allowedClients, introspectionCacheTtlSeconds 和 defaultTestPermissions 用于 auth.requireAuth ——看 身份验证
eventTypes任何类型事件类型 webhooks.emit 和 outbox.emit 接受
outbox无事务性outbox-请参阅 发件箱。没有它,阅读 core.outbox 抛出
channelPrefix无核心的 REDIS_KEY_PREFIX,当它设置一个时:文件缓存在它下面的通道上听到core的无效
testModefalse对于应用程序的测试: 接受 test-token-… 令牌和存根 auth 查找和 files ——看 测试。抛出,除非 NODE_ENV 是 test 或者 development,与 production: true

一个失踪的 url, clientId, clientSecret 或者 requestHandlerKey (选项和env都) 抛出 一次,命名该选项及其env变量。

返回的 CoreClient:

成员
authAuthServiceClient -令牌自省, requireAuth/requirePermission/requireScope、用户和组织查找。 文档/ auth.md
filesFileServiceClient -对象,存储桶,权限,版本,预签名url,存档,共享,生命周期,多部分。 文档/ files.md
webhooksWebhookServiceClient -最努力事件发射; createWebhookVerifier 对于接收者。 文档/ webhooks.md
auditAuditLogServiceClient -尽最大努力写入和读取此服务的条目。 docs/audit.md
notificationsNotificationsClient - - - - - - send 一封电子邮件通过核心, get 一个带着送货回来。见 发送通知
outboxOutboxClient -在应用程序自己的事务中编写的审核条目,事件和通知。 文档/ outbox.md
start()启动请求处理程序、文件缓存的失效订阅服务器和发件箱收割者。开机时呼叫一次
stop()停止收割者、缓存订阅者和处理程序;然后可能会创建另一个客户机

测试- testMode的测试令牌和存根,以及无操作客户端——都在 docs/testing.md.

如何连接

每个电话都要经过一个 @dianemo/core 请求处理程序 (src/handler.ts) 与一个客户端 核心, CORE_CLIENT: OAuth客户端凭据 ${url}/oauth/token。它的双胞胎, CORE_PUBLIC_CLIENT (<CORE_CLIENT>:public),发送与基本验证相同的凭据 自我反省。处理程序的Redis键是前缀 @tereo-io/sdk:<clientId>,所以复制的 一个服务共享令牌和速率限制,一个Redis上的两个服务永远不会混合它们。

处理程序重试5xx,429和丢弃的连接 (ECONNRESET, ETIMEDOUT). 这是a Retry从不写两次:

  • audit.log/logAsync/logBatchAsync 和 webhooks.emit/emitAsync/emitBatchAsync 发送一个 idempotencyKey 每次调用生成一次 (除非调用者给出了一个),核心重复数据消除了 24小时。
  • 创建核心无法重复数据删除- createShare, initiateMultipart, createBucket, createArchive, grantBucketPermission 和 restoreObjectVersion -通过 CORE_ONCE_CLIENT (<CORE_CLIENT>:once),它共享令牌,但在5xx或丢失时立即失败 连接,因为core可能已经写过了。一个4xx,包括429,像往常一样重试。

当core应答401缓存的服务时,令牌被撤销或使用密钥签名,core自 旋转- SDK丢弃令牌并使用新令牌重新尝试一次调用。调用的体是 流 (uploadObject) 未重试。

呼叫可能做的是服务的供应文档授予的内容-请参阅 文档/ provisioning.md 和核心的 配置 笔记。读取缓存 (自省、用户、 组织,桶,对象元数据)生活在同一个Redis中,在同一个Redis下 @tereo-io/sdk:<clientId> 前缀,所以没有服务读取另一个; Redis失败 直达核心。山 requestIdMiddleware() 给予彼此 请求一个 X-Request-ID 并在处理时将其转发给core。

内核中的错误以 RequestError: code 是SDK的调用代码 (FS_0013), httpStatus 回答的状态核心 (网络故障后未定义) 和 coreCode 核心的 error 代码(not_found, idempotency_key_conflict)。 statusCode 保持500,什么? 转发错误的应用程序应该会回答。 getOriginalStatus(err) 和 getOriginalResponseData(err) 给出状态和身体。在调试时记录4xx (但429)-通常是预期的,如 getUser 把404变成 null ——以及其他任何错误。认证中间件抛出 UnauthorizedError / ForbiddenError (包括 HttpError) 到应用程序的错误处理程序。核心将拒绝的输入,或者URL或标头无法携带 完好无损,在送出任何东西之前被拒绝 InvalidRequestInputError —路径参数,即 空的, . 或者 ..,对象密钥核心按形状拒绝,标记超过核心的限制。每个路径参数 作为一个段发送转义。

请求处理程序及其传输级别类型分别发布为 @tereo-io/sdk/handler,因为它们中的一些与sdk级别的类型共享名称。

布局

一个包,文件夹按种类,没有每个区域的模块:

路径持有
src/index.ts每个出口: createCoreClient,然后是发件箱、供应类型、授权、文件、webhook、审计、错误
src/coreClient.tscreateCoreClient 和 CoreClientOptions
src/handler.ts请求处理程序(每个进程一个); CORE_CLIENT / CORE_PUBLIC_CLIENT, requests,传输级别类型
src/clients/auth.ts, files.ts, webhooks.ts, audit.ts, notifications.ts, outbox.ts
src/templates/一个客户端模板, tereo,及其请求id拦截器
src/requests/请求定义,镜像core的URL区域: auth/, files/, webhooks/, audit/, notifications/
src/types/SDK级别类型,每个区域一个文件
src/utils/错误、缓存、测试一下令牌、webhook验证、审核助手、outbox表SQL
src/tests/单元测试

状态

一个包、核心路径上的一个客户端、outbox和预配文档的类型。

这取决于发布的 @dianemo/core ^2.0.0. 中没有 tereo 取决于这个 包中。

命令

npm run build   # tsc
npm run check   # tsc --noEmit
npm test        # vitest
npm run clean   # remove dist/

版本与发布记录

@tereo-io/sdk 共发布 7 个版本,首次发布于 2026/10/15;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.5.0latest2026/11/02今天
0.4.12026/10/2212 天前
0.4.02026/10/2013 天前
0.3.02026/10/1717 天前
0.2.02026/10/1518 天前
0.1.02026/10/1519 天前

关于 @tereo-io/sdk 的常见问题

@tereo-io/sdk 是什么?

一个SDK,让应用服务通过一个@dianemo/core请求处理程序到达Tereo: auth、files、webhooks和audit

如何安装 @tereo-io/sdk?

在项目目录运行 npm install @tereo-io/sdk,或使用 pnpm add / yarn add / bun add。

@tereo-io/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@tereo-io/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@tereo-io/sdk 可以商用吗?

@tereo-io/sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@tereo-io/sdk 还在维护吗?

最新版本 0.5.0 发布于 2026/11/02(今天);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/10/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。