@ 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 其审计条目,以及审计读取的范围为 |
url | CORE_URL | 核心的基URL |
clientId / clientSecret | CORE_CLIENT_ID / CORE_CLIENT_SECRET | 从 添加服务 在核心 |
requestHandlerKey | REQUEST_HANDLER_KEY | 加密请求处理程序存储的内容; 32个字符 |
redis | (需要) | 一个具有 maxRetriesPerRequest: null. 归来电者所有 |
production | NODE_ENV === "production" | 生产处理程序优先于其他共享Redis的处理程序 |
auth | {} | publicRoutes, allowedClients, introspectionCacheTtlSeconds 和 defaultTestPermissions 用于 auth.requireAuth ——看 身份验证 |
eventTypes | 任何类型 | 事件类型 webhooks.emit 和 outbox.emit 接受 |
outbox | 无 | 事务性outbox-请参阅 发件箱。没有它,阅读 core.outbox 抛出 |
channelPrefix | 无 | 核心的 REDIS_KEY_PREFIX,当它设置一个时:文件缓存在它下面的通道上听到core的无效 |
testMode | false | 对于应用程序的测试: 接受 test-token-… 令牌和存根 auth 查找和 files ——看 测试。抛出,除非 NODE_ENV 是 test 或者 development,与 production: true |
一个失踪的 url, clientId, clientSecret 或者 requestHandlerKey (选项和env都) 抛出
一次,命名该选项及其env变量。
返回的 CoreClient:
| 成员 | |
|---|---|
auth | AuthServiceClient -令牌自省, requireAuth/requirePermission/requireScope、用户和组织查找。 文档/ auth.md |
files | FileServiceClient -对象,存储桶,权限,版本,预签名url,存档,共享,生命周期,多部分。 文档/ files.md |
webhooks | WebhookServiceClient -最努力事件发射; createWebhookVerifier 对于接收者。 文档/ webhooks.md |
audit | AuditLogServiceClient -尽最大努力写入和读取此服务的条目。 docs/audit.md |
notifications | NotificationsClient - - - - - - send 一封电子邮件通过核心, get 一个带着送货回来。见 发送通知 |
outbox | OutboxClient -在应用程序自己的事务中编写的审核条目,事件和通知。 文档/ 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.ts | createCoreClient 和 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/